From 4fb3deb2118cd0a34796814bef0675c05d86dbd8 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 25 Sep 2023 15:28:27 -0500 Subject: [PATCH 001/141] OxxiusCombiner: Update by Tristan Martinez, Oxxius Replace all .h and .cpp with new files from Tristan. Update .vcxproj and Makefile.am accordingly. Add previously missing .vcxproj.filters. --- DeviceAdapters/OxxiusCombiner/Cobolt08_01.cpp | 169 ++ DeviceAdapters/OxxiusCombiner/Cobolt08_01.h | 41 + .../OxxiusCombiner/CoherentObis.cpp | 345 +++ DeviceAdapters/OxxiusCombiner/CoherentObis.h | 39 + .../OxxiusCombiner/GenericLaser.cpp | 104 + DeviceAdapters/OxxiusCombiner/GenericLaser.h | 43 + DeviceAdapters/OxxiusCombiner/Makefile.am | 12 +- DeviceAdapters/OxxiusCombiner/OnBoardHW.cpp | 125 -- DeviceAdapters/OxxiusCombiner/OnBoardHW.h | 67 - .../OxxiusCombiner/OxxiusCombiner.vcxproj | 32 +- .../OxxiusCombiner.vcxproj.filters | 69 + .../OxxiusCombiner/OxxiusCombinerHub.cpp | 778 +++++++ .../OxxiusCombiner/OxxiusCombinerHub.h | 143 ++ .../OxxiusCombiner/OxxiusFlipMirror.cpp | 128 ++ .../OxxiusCombiner/OxxiusFlipMirror.h | 41 + DeviceAdapters/OxxiusCombiner/OxxiusLBX.cpp | 545 +++++ DeviceAdapters/OxxiusCombiner/OxxiusLBX.h | 46 + DeviceAdapters/OxxiusCombiner/OxxiusLCX.cpp | 575 +++++ DeviceAdapters/OxxiusCombiner/OxxiusLCX.h | 47 + DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp | 170 ++ DeviceAdapters/OxxiusCombiner/OxxiusMDual.h | 37 + .../OxxiusCombiner/OxxiusShutter.cpp | 148 ++ DeviceAdapters/OxxiusCombiner/OxxiusShutter.h | 50 + .../OxxiusCombiner/Oxxius_combiner.cpp | 1920 ----------------- .../OxxiusCombiner/Oxxius_combiner.h | 349 --- 25 files changed, 3550 insertions(+), 2473 deletions(-) create mode 100644 DeviceAdapters/OxxiusCombiner/Cobolt08_01.cpp create mode 100644 DeviceAdapters/OxxiusCombiner/Cobolt08_01.h create mode 100644 DeviceAdapters/OxxiusCombiner/CoherentObis.cpp create mode 100644 DeviceAdapters/OxxiusCombiner/CoherentObis.h create mode 100644 DeviceAdapters/OxxiusCombiner/GenericLaser.cpp create mode 100644 DeviceAdapters/OxxiusCombiner/GenericLaser.h delete mode 100644 DeviceAdapters/OxxiusCombiner/OnBoardHW.cpp delete mode 100644 DeviceAdapters/OxxiusCombiner/OnBoardHW.h create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj.filters create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.h create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.cpp create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.h create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusLBX.cpp create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusLBX.h create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusLCX.cpp create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusLCX.h create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusMDual.h create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusShutter.cpp create mode 100644 DeviceAdapters/OxxiusCombiner/OxxiusShutter.h delete mode 100644 DeviceAdapters/OxxiusCombiner/Oxxius_combiner.cpp delete mode 100644 DeviceAdapters/OxxiusCombiner/Oxxius_combiner.h diff --git a/DeviceAdapters/OxxiusCombiner/Cobolt08_01.cpp b/DeviceAdapters/OxxiusCombiner/Cobolt08_01.cpp new file mode 100644 index 000000000..a49336791 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/Cobolt08_01.cpp @@ -0,0 +1,169 @@ +#include "Cobolt08_01.h" +using namespace std; + +Cobolt08_01::Cobolt08_01(const char* nameAndSlot) : initialized_(false) +{ + string tSlot = string(nameAndSlot); + + name_.assign(tSlot);// set laser name + tSlot = tSlot.substr(tSlot.length() - 1, 1); + slot_ = (unsigned int)atoi(tSlot.c_str());// set laser slot + + parentHub_; + busy_ = false; + laserOn_ = false; + + alarm_ = ""; + + InitializeDefaultErrorMessages(); + SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); + + // parent ID display + CreateHubIDProperty(); +} + + +Cobolt08_01::~Cobolt08_01() +{ + Shutdown(); +} + + + +int Cobolt08_01::Initialize() +{ + + if (!initialized_) { + + parentHub_ = static_cast(GetParentHub()); + if (!parentHub_) { + return DEVICE_COMM_HUB_MISSING; + } + + + + RETURN_ON_MM_ERROR(UpdateStatus()); + + initialized_ = true; + + } + + return DEVICE_OK; + +} + + +int Cobolt08_01::Shutdown() +{ + initialized_ = false; + return DEVICE_OK; +} + + + + +int Cobolt08_01::Fire(double deltaT) +{ + string activate_query = "dl 1"; //SOUR1:AM:STAT ON + string deactivate_query = "dl 0"; + + if (laserOn_ == false) { + laserOn_ = true; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, activate_query.c_str(), false)); + } + CDeviceUtils::SleepMs((long)(deltaT)); + laserOn_ = false; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, deactivate_query.c_str(), false)); + //At the end, the laser is set to off + + return DEVICE_OK; +} + +//Handlers + +int Cobolt08_01::OnAlarm(MM::PropertyBase* pProp, MM::ActionType) +{ + unsigned int alarmInt = 99; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "?F", false)); + + parentHub_->ParseforInteger(alarmInt); + + switch (alarmInt) { + case 0: + alarm_ = "No Alarm"; + break; + case 1: + alarm_ = "Out-of-bounds diode current"; + break; + case 2: + alarm_ = "Unexpected laser power value"; + break; + case 3: + alarm_ = "Out-of-bounds supply voltage"; + break; + case 4: + alarm_ = "Out-of-bounds internal temperature"; + break; + case 5: + alarm_ = "Out-of-bounds baseplate temperature"; + break; + case 7: + alarm_ = "Interlock circuit open"; + break; + case 8: + alarm_ = "Soft reset"; + break; + default: + alarm_ = "Other alarm"; + } + + pProp->Set(alarm_.c_str()); + + return DEVICE_OK; +} + + +int Cobolt08_01::OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + double status; + ostringstream query; + + query << "?l"; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, query.str().c_str(), false)); + parentHub_->ParseforDouble(status); + + if (status == 1) { + laserOn_ = true; + } + else { + laserOn_ = false; + } + + if (laserOn_) { + pProp->Set("ON"); + } + else { + pProp->Set("OFF"); + } + } + else if (eAct == MM::AfterSet) { + string newEmissionStatus, newCommand = ""; + + pProp->Get(newEmissionStatus); + + if (newEmissionStatus.compare("ON") == 0) { + newCommand = "dl 1"; //original: SOUR1:AM:STAT ON + laserOn_ = true; + } + else if (newEmissionStatus.compare("OFF") == 0) { + newCommand = "dl 0"; + laserOn_ = false; + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.c_str(), false)); + } + + return DEVICE_OK; +} diff --git a/DeviceAdapters/OxxiusCombiner/Cobolt08_01.h b/DeviceAdapters/OxxiusCombiner/Cobolt08_01.h new file mode 100644 index 000000000..a2001d803 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/Cobolt08_01.h @@ -0,0 +1,41 @@ +#pragma once +#include "GenericLaser.h" +using namespace std; + +class Cobolt08_01 : public GenericLaser +{ +public: + Cobolt08_01(const char* nameAndSlot); //OK + ~Cobolt08_01(); //OK + + int Initialize(); //OK + int Shutdown(); //OK + + int Fire(double deltaT); //OK + + int OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnCurrentSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnControlMode(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnAnalogMod(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnDigitalMod(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnState(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnAlarm(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnFire(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + + int OnPowerReadback(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnCurrentReadback(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnOperatingMode(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + +private: + bool initialized_; + + //// SPECIFIC TO Cobolt08_01 + bool laserOn_; + //double maxPower_; + //double minPower_; + string alarm_; + //double wavelength_; + //string description_; + +}; \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/CoherentObis.cpp b/DeviceAdapters/OxxiusCombiner/CoherentObis.cpp new file mode 100644 index 000000000..c17e358fb --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/CoherentObis.cpp @@ -0,0 +1,345 @@ +#include "CoherentObis.h" +using namespace std; + +CoherentObis::CoherentObis(const char* nameAndSlot) +{ + initialized_ = false; + + string tSlot = string(nameAndSlot); + + name_.assign(tSlot);// set laser name + tSlot = tSlot.substr(tSlot.length() - 1, 1); + slot_ = (unsigned int)atoi(tSlot.c_str());// set laser slot + + parentHub_; + busy_ = false; + laserOn_ = false; + + alarm_ = ""; + + InitializeDefaultErrorMessages(); + SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); + + // parent ID display + CreateHubIDProperty(); +} + + +CoherentObis::~CoherentObis() +{ + Shutdown(); +} + + +int CoherentObis::Initialize() +{ + + if (!initialized_) { + + parentHub_ = static_cast(GetParentHub()); + if (!parentHub_) { + return DEVICE_COMM_HUB_MISSING; + } + + string cmdmaxpwr = "SOUR1:POW:LIM:HIGH?"; + string cmdminpwr = "SOUR1:POW:LIM:LOW?"; + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, cmdmaxpwr.c_str(), true); + parentHub_->ParseforDouble(maxPower_); + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, cmdminpwr.c_str(), true); + parentHub_->ParseforDouble(minPower_); + + + + // Set property list + // ----------------- + // Name (read only) + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Name, name_.c_str(), MM::String, true)); + + // store the device serial number + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "SYST:INF:SNUM?", false); + parentHub_->ParseforString(serialNumber_); + + // Description (read only) + ostringstream descriPt1; + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "*IDN?", false); + parentHub_->ParseforString(description_); //example: "Coherent, Inc - OBIS 405nm 50mW C - V1.0.1 - Dec 14 2010" + + //we ignore "spa" + + //define the laser's wavelength + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "SYST1:INF:WAV?", false); + parentHub_->ParseforDouble(wavelength_); + + //let's skip the nominal power retrieve + + //let's assume there are no MPA or AOMs + + std::ostringstream InfoMessage1; // Debug purposes only + InfoMessage1 << "INFOS RECUPEREES: maxpower:"<LogMessage(this, InfoMessage1.str().c_str(), false); + + descriPt1 << "OBIS source on slot " << slot_; + descriPt1 << ", " << string(serialNumber_); + + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Description, descriPt1.str().c_str(), MM::String, true)); + + // Alarm (read only) + CPropertyAction* pAct = new CPropertyAction(this, &GenericLaser::OnAlarm); + RETURN_ON_MM_ERROR(CreateProperty("Alarm", "None", MM::String, true, pAct)); + //No status + //No Control mode + + + + //Emission selector + pAct = new CPropertyAction(this, &GenericLaser::OnEmissionOnOff); + RETURN_ON_MM_ERROR(CreateProperty("Emission", "", MM::String, false, pAct)); + AddAllowedValue("Emission", "ON"); + AddAllowedValue("Emission", "OFF"); + + //No Current set point ? + maxPower_ = 100.0; + + //Set Power + pAct = new CPropertyAction(this, &GenericLaser::OnPowerSetPoint); + RETURN_ON_MM_ERROR(CreateProperty("Power set point", "0.0", MM::Float, false, pAct)); + SetPropertyLimits("Power set point", 0.0, maxPower_); + + //Read Power + pAct = new CPropertyAction(this, &GenericLaser::OnPowerReadback); + RETURN_ON_MM_ERROR(CreateProperty("Read Power point", "0.0", MM::Float, false, pAct)); + + + //Read Current + pAct = new CPropertyAction(this, &GenericLaser::OnCurrentReadback); + RETURN_ON_MM_ERROR(CreateProperty("Read Current point", "0.0", MM::Float, false, pAct)); + + //Fire + pAct = new CPropertyAction(this, &GenericLaser::OnFire); + RETURN_ON_MM_ERROR(CreateProperty("Fire", "0", MM::Float, false, pAct)); + + //Modes + pAct = new CPropertyAction(this, &GenericLaser::OnOperatingMode); + RETURN_ON_MM_ERROR(CreateProperty("Mode", "", MM::String, false, pAct)); + + + RETURN_ON_MM_ERROR(UpdateStatus()); + + initialized_ = true; + + } + + return DEVICE_OK; + +} + + +int CoherentObis::Fire(double deltaT) +{ + string activate_query = "dl 1"; //SOUR1:AM:STAT ON + string deactivate_query = "dl 0"; + + if (laserOn_ == false) { + laserOn_ = true; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, activate_query.c_str(), false)); + } + CDeviceUtils::SleepMs((long)(deltaT)); + laserOn_ = false; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, deactivate_query.c_str(), false)); + //At the end, the laser is set to off + + return DEVICE_OK; +} + +//Handlers + +int CoherentObis::OnAlarm(MM::PropertyBase* pProp, MM::ActionType) +{ + unsigned int alarmInt = 99; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "?F", false)); + + parentHub_->ParseforInteger(alarmInt); + + switch (alarmInt) { + case 0: + alarm_ = "No Alarm"; + break; + case 1: + alarm_ = "Out-of-bounds diode current"; + break; + case 2: + alarm_ = "Unexpected laser power value"; + break; + case 3: + alarm_ = "Out-of-bounds supply voltage"; + break; + case 4: + alarm_ = "Out-of-bounds internal temperature"; + break; + case 5: + alarm_ = "Out-of-bounds baseplate temperature"; + break; + case 7: + alarm_ = "Interlock circuit open"; + break; + case 8: + alarm_ = "Soft reset"; + break; + default: + alarm_ = "Other alarm"; + } + + pProp->Set(alarm_.c_str()); + + return DEVICE_OK; +} + + +int CoherentObis::OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + double status; + ostringstream query; + + query << "?l"; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, query.str().c_str(), false)); + parentHub_->ParseforDouble(status); + + if (status == 1) { + laserOn_ = true; + } + else { + laserOn_ = false; + } + + if (laserOn_) { + pProp->Set("ON"); + } + else { + pProp->Set("OFF"); + } + } + else if (eAct == MM::AfterSet) { + string newEmissionStatus, newCommand = ""; + + pProp->Get(newEmissionStatus); + + if (newEmissionStatus.compare("ON") == 0) { + newCommand = "dl 1"; //original: SOUR1:AM:STAT ON + laserOn_ = true; + } + else if (newEmissionStatus.compare("OFF") == 0) { + newCommand = "dl 0"; + laserOn_ = false; + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.c_str(), false)); + } + + return DEVICE_OK; +} + +/* +int CoherentObis::OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + float absSetPoint_; + string command = "SOUR1:POW:LEV:IMM:AMPL?"; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, command.c_str(), false)); + parentHub_->ParseforFloat(absSetPoint_); + + pProp->Set(absSetPoint_); + } + else if (eAct == MM::AfterSet) { + double GUISetPoint = 0.0; + pProp->Get(GUISetPoint); + + if ((GUISetPoint >= 0.0) && (GUISetPoint <= maxPower_)) { //&& instead of || + string command = "SOUR1:POW:LEV:IMM:AMPL "; + command += to_string(GUISetPoint); + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, command.c_str(), false)); + + } + else { + // If the value entered through the GUI is not valid, read the machine value + OnPowerSetPoint(pProp, MM::BeforeGet); + } + } + return DEVICE_OK; +} +*/ + +int CoherentObis::OnPowerReadback(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + string command = "SOUR1:POW:LEV:IMM:AMPL?"; + float power; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, command.c_str(), false)); + parentHub_->ParseforFloat(power); + pProp->Set(power); + } + return DEVICE_OK; +} + +int CoherentObis::OnCurrentReadback(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + string command = "SOUR1:POW:CURR?"; + float current; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, command.c_str(), false)); + parentHub_->ParseforFloat(current); + pProp->Set(current); + } + return DEVICE_OK; +} + +int CoherentObis::OnOperatingMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if(eAct==MM::BeforeGet) + { + string command = "SOUR:AM:SOUR?"; + string operating_mode; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, command.c_str(), false)); + parentHub_->ParseforString(operating_mode); + pProp->Set(operating_mode.c_str()); + } + else if (eAct == MM::AfterSet) { + string input = ""; + string command; + pProp->Get(input); + if (input == "CWP" || input == "CWC") { + command = "SOUR:AM:INT "; + } + else if (input == "DIG" || input == "ANAL" || input == "MIX" || input == "DIGITAL" || input == "ANALOG" || input == "MIXED" || input == "DIGSO" || input == "MIXSO") { + command = "SOUR:AM:EXT "; + } + command += input; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, command.c_str(), false)); + string rep; + parentHub_->ParseforString(rep); + + + std::ostringstream Info; /////DEBUG + Info << "DEBUG OPERATING MODE: " << rep << "avec la commande encoyée suivante: " << command; + GetCoreCallback()->LogMessage(this, Info.str().c_str(), false); + + } + + return DEVICE_OK; + +} +/* +int CoherentObis::OnFire(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::AfterSet) { + double input; + pProp->Get(input); + return Fire(input); + } + return DEVICE_OK; +} +*/ \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/CoherentObis.h b/DeviceAdapters/OxxiusCombiner/CoherentObis.h new file mode 100644 index 000000000..ca9c1f5e4 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/CoherentObis.h @@ -0,0 +1,39 @@ +#pragma once +#include "GenericLaser.h" +using namespace std; + +class CoherentObis : public GenericLaser +{ +public: + CoherentObis(const char* nameAndSlot); //OK + ~CoherentObis(); //OK + + int Initialize(); //OK + + int Fire(double deltaT); + + // int OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnCurrentSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnControlMode(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnAnalogMod(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnDigitalMod(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnState(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; };//Aucun pour l'instant + int OnAlarm(MM::PropertyBase* pProp, MM::ActionType eAct); + // int OnFire(MM::PropertyBase* pProp, MM::ActionType eAct); + + int OnPowerReadback(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnCurrentReadback(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnOperatingMode(MM::PropertyBase* pProp, MM::ActionType eAct); + +private: + bool initialized_; + + //// SPECIFIC TO CoherentObis + double maxPower_; + double minPower_; + string alarm_; + double wavelength_; + string description_; + +}; diff --git a/DeviceAdapters/OxxiusCombiner/GenericLaser.cpp b/DeviceAdapters/OxxiusCombiner/GenericLaser.cpp new file mode 100644 index 000000000..281fabc10 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/GenericLaser.cpp @@ -0,0 +1,104 @@ +#include "GenericLaser.h" + +int GenericLaser::Shutdown() +{ + initialized_ = false; + return DEVICE_OK; +} + +void GenericLaser::GetName(char* Name) const +{ + CDeviceUtils::CopyLimitedString(Name, name_.c_str()); +} + +bool GenericLaser::Busy() +{ + return busy_; +} + +int GenericLaser::SetOpen(bool openCommand) +{ + string activate_query = "DL 1"; + string deactivate_query = "DL 0"; + + if (openCommand == false) { + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, deactivate_query.c_str(), false)); + laserOn_ = false; + } + else { + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, activate_query.c_str(), false)); + laserOn_ = true; + } + return DEVICE_OK; +} + + +int GenericLaser::GetOpen(bool& isOpen) +{ + unsigned int status = 0; + ostringstream query; + + query << "?CS " << slot_; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, query.str().c_str(), false)); + parentHub_->ParseforInteger(status); + + switch (status) { + case 0: // Emission is OFF + laserOn_ = false; + break; + case 1: // Emission is ON + laserOn_ = true; + break; + default: // Other cases: emission is potentially ON + laserOn_ = true; + } + isOpen = laserOn_; + return DEVICE_OK; +} + + + +int GenericLaser::OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + double relSetPoint_ = 0.0; + string powSetPointQuery = "?PPL"; // Generic query to read the power set point (percentage) + ostringstream powSetPointPhrase; + powSetPointPhrase << powSetPointQuery << slot_; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, powSetPointPhrase.str().c_str(), false)); + parentHub_->ParseforPercent(relSetPoint_); + + pProp->Set(relSetPoint_); + } + else if (eAct == MM::AfterSet) { + + double GUISetPoint = 0.0; + pProp->Get(GUISetPoint); + + if ((GUISetPoint >= 0.0) || (GUISetPoint <= POW_UPPER_LIMIT)) { + string powSetPointCommand = "PPL"; // Generic query to set the laser power (percentage) + + ostringstream powSetPointPhrase; + char* powerSPString = new char[20]; + strcpy( powerSPString, CDeviceUtils::ConvertToString(GUISetPoint) ); + + powSetPointPhrase << powSetPointCommand << slot_ << " " << powerSPString; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, powSetPointPhrase.str().c_str(), false)); + } + else {} // Do nothing if the GUI set point is out-of-bounds + + } + return DEVICE_OK; +} + + +int GenericLaser::OnFire(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::AfterSet) { + double input; + pProp->Get(input); + return Fire(input); + } + return DEVICE_OK; +} \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/GenericLaser.h b/DeviceAdapters/OxxiusCombiner/GenericLaser.h new file mode 100644 index 000000000..2a6d9e3d3 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/GenericLaser.h @@ -0,0 +1,43 @@ +#pragma once +#include "OxxiusCombinerHub.h" +using namespace std; + +#define POW_UPPER_LIMIT 100.0 + +class GenericLaser : public CShutterBase +{ +public: + virtual int Initialize()=0; + int Shutdown(); + + virtual void GetName(char* pszName) const; + virtual bool Busy(); + virtual int SetOpen(bool openCommand = true); + virtual int GetOpen(bool& isOpen); + virtual int Fire(double deltaT) = 0; + + int OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct); + virtual int OnCurrentSetPoint(MM::PropertyBase* , MM::ActionType ) = 0; + virtual int OnEmissionOnOff(MM::PropertyBase* , MM::ActionType ) = 0; + virtual int OnControlMode(MM::PropertyBase* , MM::ActionType ) = 0; + virtual int OnAnalogMod(MM::PropertyBase* , MM::ActionType ) = 0; + virtual int OnDigitalMod(MM::PropertyBase* , MM::ActionType ) = 0; + virtual int OnState(MM::PropertyBase* , MM::ActionType ) = 0; + virtual int OnAlarm(MM::PropertyBase* , MM::ActionType ) = 0; + int OnFire(MM::PropertyBase* , MM::ActionType ); + + virtual int OnPowerReadback(MM::PropertyBase* , MM::ActionType ) = 0; + virtual int OnCurrentReadback(MM::PropertyBase* , MM::ActionType ) = 0; + virtual int OnOperatingMode(MM::PropertyBase* , MM::ActionType ) = 0; + +protected: // Common data to all lasers + string serialNumber_; + string name_; + unsigned int slot_; + OxxiusCombinerHub* parentHub_; + bool busy_; + bool laserOn_; + +private: + bool initialized_; +}; \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/Makefile.am b/DeviceAdapters/OxxiusCombiner/Makefile.am index e0d93215d..0c642455c 100644 --- a/DeviceAdapters/OxxiusCombiner/Makefile.am +++ b/DeviceAdapters/OxxiusCombiner/Makefile.am @@ -1,6 +1,16 @@ AM_CXXFLAGS = $(MMDEVAPI_CXXFLAGS) deviceadapter_LTLIBRARIES = libmmgr_dal_OxxiusCombiner.la -libmmgr_dal_OxxiusCombiner_la_SOURCES = OnBoardHW.cpp OnBoardHW.h Oxxius_combiner.cpp Oxxius_combiner.h +libmmgr_dal_OxxiusCombiner_la_SOURCES = \ + Cobolt08_01.cpp \ + CoherentObis.cpp \ + GenericLaser.cpp \ + OxxiusCombinerHub.cpp \ + OxxiusFlipMirror.cpp \ + OxxiusLBX.cpp \ + OxxiusLCX.cpp \ + OxxiusMDual.cpp \ + OxxiusShutter.cpp + libmmgr_dal_OxxiusCombiner_la_LIBADD = $(MMDEVAPI_LIBADD) libmmgr_dal_OxxiusCombiner_la_LDFLAGS = $(MMDEVAPI_LDFLAGS) diff --git a/DeviceAdapters/OxxiusCombiner/OnBoardHW.cpp b/DeviceAdapters/OxxiusCombiner/OnBoardHW.cpp deleted file mode 100644 index 88596fe6e..000000000 --- a/DeviceAdapters/OxxiusCombiner/OnBoardHW.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// FILE: OnBoardHW.cpp -// PROJECT: Micro-Manager -// SUBSYSTEM: DeviceAdapters -//----------------------------------------------------------------------------- -// DESCRIPTION: Describes the contents of the devices (lasers, modulators) -// operating inside a combiner -// COPYRIGHT: Oxxius SA, 2013-2018 -// LICENSE: LGPL -// AUTHOR: Tristan Martinez -// - -#include "OnBoardHW.h" -#include -#include - -using namespace std; - -OnBoardHW::OnBoardHW(unsigned int numberOfSlots) { - AOM1Position_ = 0; - AOM2Position_ = 0; - - if (numberOfSlots <= MAX_NUMBER_OF_SLOTS) { - slotMax_ = numberOfSlots; - } else { - slotMax_ = MAX_NUMBER_OF_SLOTS; - } - - for(unsigned int i = 0; i 0) & (slot <= MAX_NUMBER_OF_SLOTS) ) { - std::string modelStr = "", - wavelengthStr = "", - nominalPowerStr = "", - newSourceDescription = string(newSourceType), - token = "-"; - std::size_t found; - - found = newSourceDescription.find(token); - if (found!=std::string::npos) { - modelStr = newSourceDescription.substr(0, found); - } - - if (modelStr.compare("LBX") == 0) { // The source is a LBX - sourceType_[slot-1] = 10; - } else if (modelStr.compare("LCX") == 0) { // The source is a LCX - if( slot == AOM1Position_ ) { - sourceType_[slot-1] = 21; - } else if( slot == AOM2Position_ ) { - sourceType_[slot-1] = 22; - } else { - sourceType_[slot-1] = 20; - } - } else { // Should not happen: unkown type - sourceType_[slot-1] = 99; - } - - newSourceDescription.erase(0, found+1); - - found = newSourceDescription.find(token); - if (found!=std::string::npos) { - wavelengthStr = newSourceDescription.substr(0, found); - sourceWavelength_[slot-1] = (unsigned int) atoi(wavelengthStr.c_str()); - } - newSourceDescription.erase(0, found+1); - - nominalPowerStr.assign(newSourceDescription); - sourceNominalPower_[slot-1] = (unsigned int) atoi(nominalPowerStr.c_str()); - } -} - - -void OnBoardHW::GetSerialNumber(unsigned int slot, char * serialN) { - strcpy(serialN, sourceSerialNumber_[slot-1].c_str()); -} - - -void OnBoardHW::SetSerialNumber(unsigned int slot, const char* newSerialN) { - if(sourceType_[slot-1] != 0) { - sourceSerialNumber_[slot-1].assign(newSerialN); - } -} - - -void OnBoardHW::SetAOMPos(unsigned int AOM1, unsigned int AOM2) { - if (AOM1>0) - AOM1Position_ = AOM1 + 1; - - if (AOM2>0) - AOM2Position_ = AOM2 + 1; -} - - -void OnBoardHW::GetNominalPower(unsigned int slot, unsigned int nomPower) { - if(sourceType_[slot-1] != 0) { - nomPower = sourceNominalPower_[slot-1]; - } -} diff --git a/DeviceAdapters/OxxiusCombiner/OnBoardHW.h b/DeviceAdapters/OxxiusCombiner/OnBoardHW.h deleted file mode 100644 index d969c5a82..000000000 --- a/DeviceAdapters/OxxiusCombiner/OnBoardHW.h +++ /dev/null @@ -1,67 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// FILE: OnBoardHW.h -// PROJECT: Micro-Manager -// SUBSYSTEM: DeviceAdapters -//----------------------------------------------------------------------------- -// DESCRIPTION: Describes the contents of the devices (lasers, modulators) -// operating inside a combiner -// COPYRIGHT: Oxxius SA, 2013-2018 -// LICENSE: LGPL -// AUTHOR: Tristan Martinez -// - - -// Laser sources exists in different models... -// SourceType[] depicts the can take the following values: -// -// -// model[0] model[1] -// (major) (minor) -// 1 0 -> standard LBX -// 2 0 -> standard LCX -// 2 1 -> LCX linked to an AOM number 1 -// 2 2 -> LCX linked to an AOM number 2 -// 2 5 -> LCX with power adjustment - -#ifndef _OXXIUS_ONBOARDHW_H_ -#define _OXXIUS_ONBOARDHW_H_ - -#define MAX_NUMBER_OF_SLOTS 6 -#define MAX_CHARACTERS 256 - -#include -#include - -class OnBoardHW -{ -public: - OnBoardHW(unsigned int numberOfSlots); - ~OnBoardHW(); - - bool IsEmpty(); - - unsigned int GetType(unsigned int slot); - void SetType(unsigned int slot, const char* newSourceType); - - void GetSerialNumber(unsigned int slot, char* serialN); - void SetSerialNumber(unsigned int slot, const char* newSerialN); - - void SetAOMPos(unsigned int AOM1, unsigned int AOM2); - - void GetNominalPower(unsigned int slot, unsigned int nomPower); - -private: - unsigned int sourceType_[MAX_NUMBER_OF_SLOTS]; - unsigned int sourceWavelength_[MAX_NUMBER_OF_SLOTS]; - unsigned int sourceNominalPower_[MAX_NUMBER_OF_SLOTS]; - std::vector sourceSerialNumber_; -// std::array sourceSerialNumber_; -// std::array sourceDescription_; - - unsigned int slotMax_; - unsigned int wavelength_; - unsigned int AOM1Position_; - unsigned int AOM2Position_; -}; - -#endif // _OXXIUS_ONBOARDHW_H_ \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj b/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj index 7966a9772..1e3a7d3ce 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj +++ b/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj @@ -91,21 +91,31 @@ - - - - - - - + + {b8c95f39-54bf-40a9-807b-598df2821d55} + - + + + + + + + + + - - {b8c95f39-54bf-40a9-807b-598df2821d55} - + + + + + + + + + diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj.filters b/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj.filters new file mode 100644 index 000000000..eefe3a775 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj.filters @@ -0,0 +1,69 @@ + + + + + {15751bdb-12d5-452c-8197-c6762010b6b3} + + + {d5eef910-aac2-4511-8c33-ad79c7499d4d} + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp new file mode 100644 index 000000000..73872ee42 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp @@ -0,0 +1,778 @@ +#include "OxxiusCombinerHub.h" +#include "GenericLaser.h" +#include "OxxiusShutter.h" +#include "OxxiusMDual.h" +#include "OxxiusFlipMirror.h" +#include "OxxiusLBX.h" +#include "OxxiusLCX.h" +#include "CoherentObis.h" +#include "Cobolt08_01.h" +using namespace std; + + +// Oxxius devices +const char* g_OxxiusCombinerDeviceName = "Combiner"; + +const char* g_LCXLaserDeviceName = "LCX laser source"; +const char* g_LCXLaser1DeviceName = "LCX laser source 1"; +const char* g_LCXLaser2DeviceName = "LCX laser source 2"; +const char* g_LCXLaser3DeviceName = "LCX laser source 3"; +const char* g_LCXLaser4DeviceName = "LCX laser source 4"; +const char* g_LCXLaser5DeviceName = "LCX laser source 5"; +const char* g_LCXLaser6DeviceName = "LCX laser source 6"; + +const char* g_LBXLaserDeviceName = "LBX laser source"; +const char* g_LBXLaser1DeviceName = "LBX laser source 1"; +const char* g_LBXLaser2DeviceName = "LBX laser source 2"; +const char* g_LBXLaser3DeviceName = "LBX laser source 3"; +const char* g_LBXLaser4DeviceName = "LBX laser source 4"; +const char* g_LBXLaser5DeviceName = "LBX laser source 5"; +const char* g_LBXLaser6DeviceName = "LBX laser source 6"; + +const char* g_OBISLaserDeviceName = "Obis laser source"; +const char* g_ObisLaser1DeviceName = "Obis laser source 1"; +const char* g_ObisLaser2DeviceName = "Obis laser source 2"; +const char* g_ObisLaser3DeviceName = "Obis laser source 3"; +const char* g_ObisLaser4DeviceName = "Obis laser source 4"; +const char* g_ObisLaser5DeviceName = "Obis laser source 5"; +const char* g_ObisLaser6DeviceName = "Obis laser source 6"; + +const char* g_OxxiusShutterDeviceName = "Shutter"; +const char* g_OxxiusShutter1DeviceName = "Shutter 1"; +const char* g_OxxiusShutter2DeviceName = "Shutter 2"; +const char* g_OxxiusMDualDeviceName = "MDual"; +const char* g_OxxiusMDualADeviceName = "MDual A"; +const char* g_OxxiusMDualBDeviceName = "MDual B"; +const char* g_OxxiusMDualCDeviceName = "MDual C"; +const char* g_OxxiusFlipMirrorDeviceName = "Flip-Mirror"; +const char* g_OxxiusFlipMirror1DeviceName = "Flip-Mirror 1"; +const char* g_OxxiusFlipMirror2DeviceName = "Flip-Mirror 2"; + + + + +const char* g_slotPrefix[7] = { "","L1 ","L2 ","L3 ","L4 ","L5 ","L6 " }; + +const char* convertable[3] = { "A", "B", "C" }; + +/////////////////////////////////////////////////////////////////////////////// +// Exported MMDevice API +/////////////////////////////////////////////////////////////////////////////// +MODULE_API void InitializeModuleData() +{ + RegisterDevice(g_OxxiusCombinerDeviceName, MM::HubDevice, "Oxxius laser combiner controlled through serial interface"); + + RegisterDevice(g_LCXLaser1DeviceName, MM::ShutterDevice, "LCX Laser on slot 1"); + RegisterDevice(g_LCXLaser2DeviceName, MM::ShutterDevice, "LCX Laser on slot 2"); + RegisterDevice(g_LCXLaser3DeviceName, MM::ShutterDevice, "LCX Laser on slot 3"); + RegisterDevice(g_LCXLaser4DeviceName, MM::ShutterDevice, "LCX Laser on slot 4"); + RegisterDevice(g_LCXLaser5DeviceName, MM::ShutterDevice, "LCX Laser on slot 5"); + RegisterDevice(g_LCXLaser6DeviceName, MM::ShutterDevice, "LCX Laser on slot 6"); + + RegisterDevice(g_LBXLaser1DeviceName, MM::ShutterDevice, "LBX Laser on slot 1"); + RegisterDevice(g_LBXLaser2DeviceName, MM::ShutterDevice, "LBX Laser on slot 2"); + RegisterDevice(g_LBXLaser3DeviceName, MM::ShutterDevice, "LBX Laser on slot 3"); + RegisterDevice(g_LBXLaser4DeviceName, MM::ShutterDevice, "LBX Laser on slot 4"); + RegisterDevice(g_LBXLaser5DeviceName, MM::ShutterDevice, "LBX Laser on slot 5"); + RegisterDevice(g_LBXLaser6DeviceName, MM::ShutterDevice, "LBX Laser on slot 6"); + + RegisterDevice(g_ObisLaser1DeviceName, MM::ShutterDevice, "Obis Laser on slot 1"); + RegisterDevice(g_ObisLaser2DeviceName, MM::ShutterDevice, "Obis Laser on slot 2"); + RegisterDevice(g_ObisLaser3DeviceName, MM::ShutterDevice, "Obis Laser on slot 3"); + RegisterDevice(g_ObisLaser4DeviceName, MM::ShutterDevice, "Obis Laser on slot 4"); + RegisterDevice(g_ObisLaser5DeviceName, MM::ShutterDevice, "Obis Laser on slot 5"); + RegisterDevice(g_ObisLaser6DeviceName, MM::ShutterDevice, "Obis Laser on slot 6"); + + RegisterDevice(g_OxxiusShutter1DeviceName, MM::ShutterDevice, "E-m shutter on channel 1"); + RegisterDevice(g_OxxiusShutter2DeviceName, MM::ShutterDevice, "E-m shutter on channel 2"); + RegisterDevice(g_OxxiusMDualADeviceName, MM::GenericDevice, "M-Dual on channel A"); + RegisterDevice(g_OxxiusMDualBDeviceName, MM::GenericDevice, "M-Dual on channel B"); + RegisterDevice(g_OxxiusMDualCDeviceName, MM::GenericDevice, "M-Dual on channel C"); + RegisterDevice(g_OxxiusFlipMirror1DeviceName, MM::StateDevice, "Flip-Mirror on slot 1"); + RegisterDevice(g_OxxiusFlipMirror2DeviceName, MM::StateDevice, "Flip-Mirror on slot 2"); + +} + +MODULE_API MM::Device* CreateDevice(const char* deviceNameChar) +{ + if (deviceNameChar == 0) + return 0; + + std::string deviceNameAndSlot = string(deviceNameChar); + + if (strcmp(deviceNameChar, g_OxxiusCombinerDeviceName) == 0) { + return new OxxiusCombinerHub(); + } + else if (deviceNameAndSlot.compare(0, strlen(g_LCXLaserDeviceName), g_LCXLaserDeviceName) == 0) { + return new OxxiusLCX(deviceNameChar); + } + else if (deviceNameAndSlot.compare(0, strlen(g_LBXLaserDeviceName), g_LBXLaserDeviceName) == 0) { + return new OxxiusLBX(deviceNameChar); + } + else if (deviceNameAndSlot.compare(0, strlen(g_OxxiusShutterDeviceName), g_OxxiusShutterDeviceName) == 0) { + return new OxxiusShutter(deviceNameChar); + } + else if (deviceNameAndSlot.compare(0, strlen(g_OxxiusMDualDeviceName), g_OxxiusMDualDeviceName) == 0) { + return new OxxiusMDual(deviceNameChar); + } + else if (deviceNameAndSlot.compare(0, strlen(g_OxxiusFlipMirrorDeviceName), g_OxxiusFlipMirrorDeviceName) == 0) { + return new OxxiusFlipMirror(deviceNameChar); + } + else if (deviceNameAndSlot.compare(0, strlen(g_OBISLaserDeviceName), g_OBISLaserDeviceName) == 0) { + return new CoherentObis(deviceNameChar); + } + return 0; +} + +MODULE_API void DeleteDevice(MM::Device* pDevice) +{ + delete pDevice; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Oxxius combiner implementation +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +/////////////////////////////////////////////////////////////////////////////// + + +OxxiusCombinerHub::OxxiusCombinerHub() : initialized_(false) +{ + // Initializing private variables + serialNumber_ = ""; + installedDevices_ = 0; + serialAnswer_ = ""; + interlockClosed_ = false; + keyActivated_ = false; + maxtemperature_ = 20.0; + + InitializeDefaultErrorMessages(); + SetErrorText(ERR_COMBINER_NOT_FOUND, "Hub Device not found. The peer device is expected to be a Oxxius combiner"); + + + // Create pre-initialization properties + // ------------------------------------ + + // Communication port + CPropertyAction* pAct = new CPropertyAction(this, &OxxiusCombinerHub::OnPort); + CreateProperty(MM::g_Keyword_Port, "Undefined", MM::String, false, pAct, true); +} + +OxxiusCombinerHub::~OxxiusCombinerHub() +{ + Shutdown(); +} + +void OxxiusCombinerHub::GetName(char* name) const +{ + CDeviceUtils::CopyLimitedString(name, g_OxxiusCombinerDeviceName); +} + +bool OxxiusCombinerHub::Busy() +{ + return false; + +} + + +int OxxiusCombinerHub::Initialize() +{ + if (!initialized_) { + + // Set proprety list + // - - - - - - - - - + + // Name and description of the combiner: + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Name, g_OxxiusCombinerDeviceName, MM::String, true)); + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Description, "Oxxius L6Cc/L4Cc combiner", MM::String, true)); + + // Serial number of the combiner: + CPropertyAction* pAct = new CPropertyAction(this, &OxxiusCombinerHub::OnSerialNumber); + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_HubID, serialNumber_.c_str(), MM::String, true, pAct)); + + // Interlock circuit: + pAct = new CPropertyAction(this, &OxxiusCombinerHub::OnInterlock); + RETURN_ON_MM_ERROR(CreateProperty("Interlock circuit", "", MM::String, true, pAct)); + + // Emission key: + pAct = new CPropertyAction(this, &OxxiusCombinerHub::OnEmissionKey); + RETURN_ON_MM_ERROR(CreateProperty("EmissionKey", "", MM::String, true, pAct)); + + // Temperature: + pAct = new CPropertyAction(this, &OxxiusCombinerHub::OnTemperature); + RETURN_ON_MM_ERROR(CreateProperty("MaxTemperature", "", MM::Float, true, pAct)); + + + if (!IsCallbackRegistered()) + return DEVICE_NO_CALLBACK_REGISTERED; + + // Enumerates the installed AOMs and their position + bool AOM1en = false, AOM2en = false; + unsigned int ver = 0; + + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "AOM1 EN", false)); + ParseforBoolean(AOM1en); + + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "AOM2 EN", false)); + ParseforBoolean(AOM2en); + + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "SV?", false)); + ParseforVersion(ver); + + // A position equal to "0" stands for an absence of modulator + if (AOM1en) { + bool adcom = false; + string command = ""; + + if (ver < 1016) { //version check + adcom = true; + command = "AOM1 PO"; + } + else { + adcom = false; + command = "AOM1 POS"; + } + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, command.c_str(), adcom)); + ParseforInteger(AOM1pos_); + } + if (AOM2en) { + bool adcom = false; + string command = ""; + + if (ver < 1016) { //version check + adcom = true; + command = "AOM2 PO"; + } + else { + adcom = false; + command = "AOM2 POS"; + } + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, command.c_str(), adcom)); + ParseforInteger(AOM1pos_); + } + + + //Mpa position retreive + for (unsigned int i = 1; i <= MAX_NUMBER_OF_SLOTS; i++) { + string command = "IP"; + std::stringstream ss; + ss << i; + command += ss.str(); + + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, command.c_str(), true)); + if (serialAnswer_ != "????") { + mpa[i] = 1; + } + } + + + + RETURN_ON_MM_ERROR(UpdateStatus()); + + initialized_ = true; + + // RETURN_ON_MM_ERROR( DetectInstalledDevices() ); + } + return DEVICE_OK; +} + + + +int OxxiusCombinerHub::DetectInstalledDevices() +{ + if (initialized_) { + + // Enumerates the lasers (or devices) present on the combiner + unsigned int masque = 1; + unsigned int repartition = 0; + + //sending command ?CL + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "?CL", false)); + ParseforInteger(repartition); + + for (unsigned int querySlot = 1; querySlot <= MAX_NUMBER_OF_SLOTS; querySlot++) { + if ((repartition & masque) != 0) { + string answer; + // A laser source is listed, now querying for detailed information (model, etc) + + std::string detailedInfo, serialNumber; + + //send command to get devices information + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), querySlot, "INF?", false)); + ParseforString(detailedInfo); + + if (detailedInfo != "timeout") { + std::ostringstream nameSlotModel; + + //get the model (LCX or LBX or third-party) + std::string model = detailedInfo.substr(0, 3); + + if (model == "LBX" || model == "LSX") { + nameSlotModel << g_LBXLaserDeviceName << " " << querySlot; + } + else if (model == "LCX" || model == "LPX") { + nameSlotModel << g_LCXLaserDeviceName << " " << querySlot; + } + else if (detailedInfo == "ERR-100") { + //Dans le OBIS + nameSlotModel << g_OBISLaserDeviceName << " " << querySlot; + } + + MM::Device* pDev = ::CreateDevice(nameSlotModel.str().c_str()); + if (pDev) { + AddInstalledDevice(pDev); + installedDevices_++; + } + } + } + masque <<= 1; // Left-shift the bit mask and repeat + } + + // Creating Devices for the two electro-mechanical shutters: + for (unsigned int channel = 1; channel <= 2; channel++) { + std::ostringstream nameModelChannel; + nameModelChannel << g_OxxiusShutterDeviceName << " " << channel; + + MM::Device* pDev = ::CreateDevice(nameModelChannel.str().c_str()); + if (pDev) { + AddInstalledDevice(pDev); + installedDevices_++; + } + } + + // Creating Devices for the "Flip mirror" or MDUAL modules: + /*unsigned int FM1type = 0, FM2type = 0; + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "FM1C", false)); + ParseforInteger(FM1type); + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "FM2C", false)); + ParseforInteger(FM2type);*/ + + + // MDUAL module detection + for (unsigned int j = 0; j <= 2; j++) { + std::string MDSlot; + std::ostringstream com; + com << "IP" << convertable[j]; + + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, com.str().c_str(), true)); + ParseforString(MDSlot); + + com.str(""); + com.clear(); + com << g_OxxiusMDualDeviceName << " " << convertable[j]; + + if (MDSlot != "????") { + MM::Device* pDev = ::CreateDevice(com.str().c_str()); + if (pDev) { + AddInstalledDevice(pDev); + installedDevices_++; + } + + } + + } + + //Flip mirror module creation + + for (unsigned int j = 1; j <= 2; j++) { + unsigned int FMpresence; + std::ostringstream queryPhrase,FMName ; + queryPhrase << "FM" << j << "C"; + + RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, queryPhrase.str().c_str(), false)); + ParseforInteger(FMpresence); + + FMName << g_OxxiusFlipMirrorDeviceName << " " << j; + if ( (FMpresence == 1) || (FMpresence == 2) || (FMpresence == 3) ) { + MM::Device* pDev = ::CreateDevice(FMName.str().c_str()); + if (pDev) { + AddInstalledDevice(pDev); + installedDevices_++; + } + } + } + + } // if (initialized_) + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::Shutdown() +{ + initialized_ = false; + return DEVICE_OK; +} + + +/////////////////////////////////////////////////////////////////////////////// +// Action handlers +/////////////////////////////////////////////////////////////////////////////// + +int OxxiusCombinerHub::OnPort(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(port_.c_str()); + } + else if (eAct == MM::AfterSet) + { + pProp->Get(port_); + pProp->Set(port_.c_str()); + } + return DEVICE_OK; +} + + +int OxxiusCombinerHub::OnSerialNumber(MM::PropertyBase* pProp, MM::ActionType pAct) +{ + if (pAct == MM::BeforeGet) { + QueryCommand(this, GetCoreCallback(), NO_SLOT, "HID?", false); + ParseforString(serialNumber_); + pProp->Set(serialNumber_.c_str()); + } + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::OnInterlock(MM::PropertyBase* pProp, MM::ActionType pAct) +{ + if (pAct == MM::BeforeGet) { + QueryCommand(this, GetCoreCallback(), NO_SLOT, "INT?", false); + ParseforBoolean(interlockClosed_); + + if (interlockClosed_) { + pProp->Set("Closed"); + } + else { + pProp->Set("Open"); + } + } + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::OnEmissionKey(MM::PropertyBase* pProp, MM::ActionType pAct) +{ + if (pAct == MM::BeforeGet) { + QueryCommand(this, GetCoreCallback(), NO_SLOT, "KEY?", false); + ParseforBoolean(keyActivated_); + + if (keyActivated_) { + pProp->Set("Armed"); + } + else { + pProp->Set("Disarmed"); + } + } + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::OnTemperature(MM::PropertyBase* pProp, MM::ActionType pAct) +{ + if (pAct == MM::BeforeGet) { + // create an array of the temperatures of all the lasers + std::vector temperature_array; + float temp_var; + + for (int i = 1; i <= MAX_NUMBER_OF_SLOTS; i++) { + + if (i != GetObPos()) { + QueryCommand(this, GetCoreCallback(), i, "?BT", false); //for LBX and LCX lasers + ParseforFloat(temp_var); + } + else { + QueryCommand(this, GetCoreCallback(), i, "SOUR:TEMP:BAS?", false); // For OBIS laser + ParseforTemperature(temp_var); + } + temperature_array.push_back(temp_var); + } + + float maxTemp = temperature_array[0]; + for (auto i : temperature_array) { + if (i > maxTemp) { + maxTemp = i; + } + } + maxtemperature_ = maxTemp; + + pProp->Set(static_cast(maxtemperature_)); + + } + return DEVICE_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// Generic methods +/////////////////////////////////////////////////////////////////////////////// + +void OxxiusCombinerHub::LogError(int id, MM::Device* device, MM::Core* core, const char* functionName) //print log messages +{ + std::ostringstream os; + char deviceName[MM::MaxStrLength]; + device->GetName(deviceName); + os << "Error " << id << ", " << deviceName << ", " << functionName << endl; + core->LogMessage(device, os.str().c_str(), false); +} + + +/** + * Sends a serial command to a given slot, then stores the result in the receive buffer. + */ +int OxxiusCombinerHub::QueryCommand(MM::Device* device, MM::Core* core, const unsigned int destinationSlot, const char* command, bool adco) +{ + // First check: if the command string is empty, do nothing and return "DEVICE_OK" + if (strcmp(command, "") == 0) return DEVICE_OK; + + char rcvBuf_[RCV_BUF_LENGTH]; + // Compose the command to be sent to the combiner + std::string strCommand, strHZIn, strHZOut; + strCommand.assign(g_slotPrefix[destinationSlot]); + strCommand.append(command); + strHZIn.assign(g_slotPrefix[destinationSlot]); + strHZIn.append("HZ 9876"); + strHZOut.assign(g_slotPrefix[destinationSlot]); + strHZOut.append("HZ 0"); + + /* + std::ostringstream InfoMessage; + InfoMessage << "Now sending command :"; + InfoMessage << string(strCommand.c_str()); + LogError(DEVICE_OK, device, core, InfoMessage.str().c_str()); + */ + std::ostringstream InfoMessage2; + InfoMessage2 << "Send: " << command << " Received: "; + + // Preambule for specific commands + if (adco) { + int ret = core->SetSerialCommand(device, port_.c_str(), strHZIn.c_str(), "\r\n"); + ret = core->GetSerialAnswer(device, port_.c_str(), RCV_BUF_LENGTH, rcvBuf_, "\r\n"); + if (ret != DEVICE_OK) { + LogError(ret, device, core, "QueryCommand-SetSerialCommand - preambule"); + return ret; + } + } + + // Send command through the serial interface + int ret = core->SetSerialCommand(device, port_.c_str(), strCommand.c_str(), "\r\n"); + if (ret != DEVICE_OK) { + LogError(ret, device, core, "QueryCommand-SetSerialCommand"); + return ret; + } + + // Get a response + ret = core->GetSerialAnswer(device, port_.c_str(), RCV_BUF_LENGTH, rcvBuf_, "\r\n"); + + InfoMessage2 << rcvBuf_; + /* DEBUG ONLY */ + // LogError(DEVICE_OK, device, core, InfoMessage2.str().c_str()); + + if (ret != DEVICE_OK) { + LogError(ret, device, core, "QueryCommand-GetSerialAnswer"); + + // Keep on trying until we either get our answer, or 3 seconds have passed + int maxTimeMs = 3000; + // Wait for a (increasing) delay between each try + int delayMs = 10; + // Keep track of how often we tried + int counter = 1; + bool done = false; + MM::MMTime startTime(core->GetCurrentMMTime()); // Let's keep in mind that MMTime is counted in microseconds + + while (!done) { + counter++; + ret = core->GetSerialAnswer(device, port_.c_str(), RCV_BUF_LENGTH, rcvBuf_, "\r\n"); + if ((ret == DEVICE_OK) || ((core->GetCurrentMMTime() - startTime) > (maxTimeMs * 1000.0))) + done = true; + else { + CDeviceUtils::SleepMs(delayMs); + delayMs *= 2; + } + } + ostringstream os; + if (ret == DEVICE_OK) + os << "QueryCommand-GetSerialAnswer: Succeeded reading from serial port after trying " << counter << "times."; + else + os << "QueryCommand-GetSerialAnswer: Failed reading from serial port after trying " << counter << "times."; + + core->LogMessage(device, os.str().c_str(), true); + + serialAnswer_.assign(rcvBuf_); + return ret; + } + serialAnswer_.assign(rcvBuf_); + ret = core->PurgeSerial(device, port_.c_str()); + + /* if( strcmp(serialAnswer_, "timeout") == 0) { + std::ostringstream syntaxErrorMessage; + syntaxErrorMessage << "Time out received against sent command '"; + syntaxErrorMessage << string(strCommand.c_str()); + syntaxErrorMessage << "'"; + + LogError(DEVICE_SERIAL_TIMEOUT, device, core, syntaxErrorMessage.str().c_str()); + return DEVICE_SERIAL_TIMEOUT; + } + */ + // Epilogue for specific commands + if (adco) { + int retEpi = core->SetSerialCommand(device, port_.c_str(), strHZOut.c_str(), "\r\n"); + retEpi = core->GetSerialAnswer(device, port_.c_str(), RCV_BUF_LENGTH, rcvBuf_, "\r\n"); + if (retEpi != DEVICE_OK) { + LogError(retEpi, device, core, "QueryCommand-SetSerialCommand - Epilogue"); + return retEpi; + } + } + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::ParseforBoolean(bool& Bval) +{ + unsigned int intAnswer = (unsigned int)atoi(serialAnswer_.c_str()); + Bval = (intAnswer == 1); + + serialAnswer_.clear(); + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::ParseforFloat(float& Dval) +{ + Dval = (float)atof(serialAnswer_.c_str()); + + serialAnswer_.clear(); + + return DEVICE_OK; +} + +int OxxiusCombinerHub::ParseforDouble(double& Dval) +{ + Dval = (double)atof(serialAnswer_.c_str()); + + serialAnswer_.clear(); + + return DEVICE_OK; +} + + + +int OxxiusCombinerHub::ParseforInteger(unsigned int& Ival) +{ + Ival = (unsigned int)atoi(serialAnswer_.c_str()); + + serialAnswer_.clear(); + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::ParseforString(std::string& Sval) +{ + Sval.assign(serialAnswer_); + + serialAnswer_.clear(); + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::ParseforVersion(unsigned int& Vval) //cast the string into a comparable int +{ + std::string temp1; + std::string temp2(serialAnswer_); + + for (unsigned int i = 0; i <= (temp2.length()) - 1; i++) { + if (temp2.at(i) != '.') { + temp1 += temp2.at(i); + } + } + + stringstream s(temp1); + s >> Vval; + serialAnswer_.clear(); + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::ParseforPercent(double& Pval) //cast the string into a comparable int +{ + std::string percentage; + std::size_t found; + + percentage.assign(serialAnswer_); + + found = percentage.find("%"); + if (found != std::string::npos) { + Pval = atof(percentage.substr(0, found).c_str()); + } + + return DEVICE_OK; +} + + +int OxxiusCombinerHub::ParseforTemperature(float& Tval) // cast a Celsius temperature string into a comparable float +{ + std::string temperature; + std::size_t found; + + temperature.assign(serialAnswer_); + + found = temperature.find("C"); + if (found != std::string::npos) { + Tval = atof(temperature.substr(0, found).c_str()); + } + + return DEVICE_OK; +} + +int OxxiusCombinerHub::ParseforChar(char* Nval) +{ + strcpy(Nval, serialAnswer_.c_str()); + serialAnswer_.clear(); + + return DEVICE_OK; +} + +bool OxxiusCombinerHub::GetAOMpos1(unsigned int slot) +{ + bool res = false; + + if (slot == AOM1pos_) { + res = true; + } + + return res; +} + +bool OxxiusCombinerHub::GetAOMpos2(unsigned int slot) +{ + bool res = false; + + if (slot == AOM2pos_) { + res = true; + } + + return res; +} + +bool OxxiusCombinerHub::GetMPA(unsigned int slot) { + bool res = false; + + if (mpa[slot] == 1) { + res = true; + } + + return res; +} + +int OxxiusCombinerHub::GetObPos() { + return obPos_; +} diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.h b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.h new file mode 100644 index 000000000..de99211c7 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.h @@ -0,0 +1,143 @@ +#pragma once +#include "../../MMDevice/MMDevice.h" +#include "../../MMDevice/DeviceBase.h" +#include "../../MMDevice/ModuleInterface.h" +#include +#include +#include +#include +#include +#include +#include +#include // For system time +#include // For formatting time +#include // For string streams +using namespace std; + +//For Obis +#ifdef WIN32 + #include +#endif +#include "../../MMDevice/DeviceUtils.h" +#include "FixSnprintf.h" +#include +#include +// + +// +class OxxiusLBX; // advance declaration +class OxxiusLCX; // advance declaration +class CoherentObis; //advance declaration +class Cobolt08_01; //advance declaration +class OxxiusShutter; // advance declaration +class OxxiusMDual; // advance declaration +class OxxiusFlipMirror; // advance declaration + +// +#define MAX_NUMBER_OF_SLOTS 6 +#define RCV_BUF_LENGTH 256 +#define NO_SLOT 0 + +////////////////////////////////////////////////////////////////////////////// +// Error codes for LBX and LCX +// +#define ERR_PORT_CHANGE_FORBIDDEN 101 +#define ERR_NO_PORT_SET 102 +#define ERR_COMBINER_NOT_FOUND 201 +#define ERR_UNSUPPORTED_VERSION 202 + +////////////////////////////////////////////////////////////////////////////// +// Error codes for Obis +// +#define OBIS_ERR_PORT_CHANGE_FORBIDDEN 10004 +#define OBIS_ERR_DEVICE_NOT_FOUND 10005 + +#define OBIS_POWERCONVERSION 1000 //convert the power into mW from the W it wants the commands in + +////////////////////////////////////////////////////////////////////////////// +// Miscellaneous definitions +// +// Use the name 'return_value' that is unlikely to appear within 'result'. +#define RETURN_ON_MM_ERROR( result ) do { \ + int return_value = (result); \ + if (return_value != DEVICE_OK) { \ + return return_value; \ + } \ +} while (0) + + + + +////////////////////////////////////////////////////////////////////////////// +// Defining device adaptaters +// + + +class OxxiusCombinerHub : public HubBase +{ +public: + OxxiusCombinerHub(); + ~OxxiusCombinerHub(); + + // MMDevice API + // ------------ + int Initialize(); + int Shutdown(); + + void GetName(char* pszName) const; + bool Busy(); + int DetectInstalledDevices(); + unsigned int GetNumberOfInstalledDevices() { return installedDevices_; }; + // MM::DeviceDetectionStatus DetectDevice(void); + bool SupportsDeviceDetection(void) { return true; }; + + + // Property handlers + int OnPort(MM::PropertyBase* pPropt, MM::ActionType eAct); + int OnSerialNumber(MM::PropertyBase* pPropt, MM::ActionType eAct); + int OnInterlock(MM::PropertyBase* pPropt, MM::ActionType eAct); + int OnEmissionKey(MM::PropertyBase* pPropt, MM::ActionType eAct); + int OnTemperature(MM::PropertyBase* pPropt, MM::ActionType eAct); + + // Custom interface for child devices +// bool IsPortAvailable() {return portAvailable_;} + int QueryCommand(MM::Device* device, MM::Core* core, const unsigned int destinationSlot, const char* command, bool adco); + int ParseforBoolean(bool& destBoolean); + int ParseforFloat(float& destFloat); + int ParseforInteger(unsigned int& destInteger); + int ParseforString(string& destString); + int ParseforVersion(unsigned int& Vval); + int ParseforPercent(double& Pval); + int ParseforTemperature(float& Tval); + int ParseforChar(char* Nval); + + int ParseforDouble(double& destDouble); + // int TempAdminInt(const char* com); + // int TempAdminString(int com, string &res); + + bool GetAOMpos1(unsigned int slot); + bool GetAOMpos2(unsigned int slot); + bool GetMPA(unsigned int slot); + int GetObPos(); + +private: + void LogError(int id, MM::Device* device, MM::Core* core, const char* functionName); + + string port_; + bool initialized_; + unsigned int installedDevices_; + + string type_; + float maxtemperature_; + + string serialAnswer_; + + string serialNumber_; + bool interlockClosed_; + bool keyActivated_; + + unsigned int AOM1pos_; + unsigned int AOM2pos_; + unsigned int mpa[7]; + unsigned int obPos_; +}; diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.cpp new file mode 100644 index 000000000..cddb613e8 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.cpp @@ -0,0 +1,128 @@ +#include "OxxiusFlipMirror.h" + +#include +#include +#include +#include +#include "../../MMDevice/ModuleInterface.h" +using namespace std; + + +/////////////////////////////////////////////////////////////////////////////// +// +// Oxxius Flip-Mirror implementation +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +/////////////////////////////////////////////////////////////////////////////// + +OxxiusFlipMirror::OxxiusFlipMirror(const char* nameAndSlot) : initialized_(false) +{ + parentHub_ = 0; + core_ = GetCoreCallback(); + + std::string fSlot = string(nameAndSlot); + nameF_.assign(fSlot);// set laser name + fSlot = fSlot.substr(fSlot.length() - 1, 1); + slot_ = (unsigned int)atoi(fSlot.c_str());// set laser slot + + numPos_ = 0; + + // Set property list ///////////////////////////////////////////////////////////////////////////////////////////////////// NOT WORKING? (duplicate property name Name(4)) + // ----------------- + // Name (read only) + /*CreateProperty(MM::g_Keyword_Name, nameF_.c_str(), MM::String, true); + + CreateProperty(MM::g_Keyword_Description, "Flip-Mirror module", MM::String, true); + + InitializeDefaultErrorMessages(); + SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); + + // parent ID display + CreateHubIDProperty();*/ +} + + +OxxiusFlipMirror::~OxxiusFlipMirror() +{ + Shutdown(); +} + + +int OxxiusFlipMirror::Initialize() +{ + if (!initialized_) { + parentHub_ = static_cast(GetParentHub()); + if (!parentHub_) { + return DEVICE_COMM_HUB_MISSING; + } + char hubLabel[MM::MaxStrLength]; + parentHub_->GetLabel(hubLabel); + SetParentID(hubLabel); // for backward compatibility + + CPropertyAction* pAct = new CPropertyAction(this, &OxxiusFlipMirror::OnSwitchPos);//setting the possible positions + RETURN_ON_MM_ERROR(CreateProperty("Switch Position", "0", MM::Integer, false, pAct)); + SetPropertyLimits("Switch Position", 0, 1); + + std::ostringstream descriPt2; + descriPt2 << ""; + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Description, descriPt2.str().c_str(), MM::String, true)); + + // Gate, or "closed" position +// RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_Closed_Position, "0", MM::String, false) ); + +// isOpen_ = false; // MDual closed posisiton is + + RETURN_ON_MM_ERROR(UpdateStatus()); + + initialized_ = true; + } + + return DEVICE_OK; +} + + +int OxxiusFlipMirror::Shutdown() +{ + initialized_ = false; + return DEVICE_OK; +} + + +void OxxiusFlipMirror::GetName(char* Name) const +{ + CDeviceUtils::CopyLimitedString(Name, nameF_.c_str()); +} + + +bool OxxiusFlipMirror::Busy() +{ + return false; +} + + +int OxxiusFlipMirror::OnSwitchPos(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + unsigned int currentPos = 0; + std::ostringstream command; + command << "FM" << slot_; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, command.str().c_str(), false)); + parentHub_->ParseforInteger(currentPos); + + //SetPosition(currentPos); + pProp->Set((long)currentPos); + } + else if (eAct == MM::AfterSet) { + long newPosition = 0; + + //GetPosition(newPosition); + pProp->Get(newPosition); + + std::ostringstream newCommand; + newCommand << "FM" << slot_ << " " << newPosition; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, newCommand.str().c_str(), false)); + } + return DEVICE_OK; +} \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.h b/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.h new file mode 100644 index 000000000..250e5e3fb --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.h @@ -0,0 +1,41 @@ +#pragma once + +#include "OxxiusCombinerHub.h" + +#include "../../MMDevice/MMDevice.h" +#include "../../MMDevice/DeviceBase.h" +#include "../../MMDevice/ModuleInterface.h" +#include +#include +#include + +class OxxiusFlipMirror : public CStateDeviceBase +{ +public: + OxxiusFlipMirror(const char* name); + ~OxxiusFlipMirror(); + + // MMDevice API + // ------------ + int Initialize(); + int Shutdown(); + + void GetName(char* pszName) const; + bool Busy(); + unsigned long GetNumberOfPositions() const { return 2; }; + + // Action Interface + // ---------------- + int OnSwitchPos(MM::PropertyBase* pProp, MM::ActionType eAct); + + +private: + bool initialized_; + std::string nameF_; + unsigned int slot_; + MM::Core* core_; + + OxxiusCombinerHub* parentHub_; + + unsigned long numPos_; +}; diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusLBX.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusLBX.cpp new file mode 100644 index 000000000..114d90fe7 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusLBX.cpp @@ -0,0 +1,545 @@ +#include "OxxiusLBX.h" +using namespace std; + +OxxiusLBX::OxxiusLBX(const char* nameAndSlot) : initialized_(false) +{ + string tSlot = string(nameAndSlot); + + name_.assign(tSlot);// set laser name + tSlot = tSlot.substr(tSlot.length() - 1, 1); + slot_ = (unsigned int)atoi(tSlot.c_str());// set laser slot + + parentHub_; + busy_ = false; + laserOn_ = false; + alarm_ = ""; + state_ = ""; + digitalMod_ = ""; + analogMod_ = ""; + controlMode_ = ""; + + //MPA + mpa_number = -1; //probably not needed + + // powerSetPoint_ = 0.0; + maxRelPower_ = 100.0; + nominalPower_ = 100.0; + maxCurrent_ = 125.0; + + InitializeDefaultErrorMessages(); + SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); + + // parent ID display + CreateHubIDProperty(); +} + + +OxxiusLBX::~OxxiusLBX() +{ + Shutdown(); +} + + + +int OxxiusLBX::Initialize() +{ + if (!initialized_) { + + size_t found; + string strSlot; + string spa; + + + parentHub_ = static_cast(GetParentHub()); + if (!parentHub_) { + return DEVICE_COMM_HUB_MISSING; + } + char hubLabel[MM::MaxStrLength]; + parentHub_->GetLabel(hubLabel); + SetParentID(hubLabel); // for backward compatibility + + // Set property list + // ----------------- + // Name (read only) + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Name, name_.c_str(), MM::String, true)); + + // Description (read only) + ostringstream descriPt1; + char sourceSerialNumber[] = "LAS-XXXXXXXXXX"; + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "HID?", false); + parentHub_->ParseforChar(sourceSerialNumber); + + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "IP", true); + parentHub_->ParseforString(spa); + + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "INF?", false); + parentHub_->ParseforString(strSlot); + + // Retrieves and delete the laser's type + found = strSlot.find("-"); + if (found != string::npos) { + strSlot.erase(0, found + 1); + } + + // Retrieves and define the laser's wavelength + found = strSlot.find("-"); + if (found != string::npos) { + waveLength = (unsigned int)atoi(strSlot.substr(0, found).c_str()); + } + + // Retrieves and define the nominal power + strSlot.erase(0, found + 1); + nominalPower_ = (float)atof(strSlot.substr(0, found).c_str()); + + + if (parentHub_->GetMPA(slot_)) { + mpa_number = slot_; + } + else { + mpa_number = -1; + } + + + // LBX model + descriPt1 << "LBX"; + descriPt1 << " source on slot " << slot_; + descriPt1 << ", " << sourceSerialNumber; + + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Description, descriPt1.str().c_str(), MM::String, true)); + + // Alarm (read only) + CPropertyAction* pAct = new CPropertyAction(this, &GenericLaser::OnAlarm); + RETURN_ON_MM_ERROR(CreateProperty("Alarm", "None", MM::String, true, pAct)); + + // Status (read only) + pAct = new CPropertyAction(this, &GenericLaser::OnState); + RETURN_ON_MM_ERROR(CreateProperty("State", "", MM::String, true, pAct)); + + // Emission selector (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnEmissionOnOff); + RETURN_ON_MM_ERROR(CreateProperty("Emission", "", MM::String, false, pAct)); + AddAllowedValue("Emission", "ON"); + AddAllowedValue("Emission", "OFF"); + + // Digital modulation selector (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnDigitalMod); + RETURN_ON_MM_ERROR(CreateProperty("Digital Modulation", "", MM::String, false, pAct)); + AddAllowedValue("Digital Modulation", "ON"); + AddAllowedValue("Digital Modulation", "OFF"); + + // Analog modulation selector (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnAnalogMod); + RETURN_ON_MM_ERROR(CreateProperty("Analog Modulation", "", MM::String, false, pAct)); + AddAllowedValue("Analog Modulation", "ON"); + AddAllowedValue("Analog Modulation", "OFF"); + + // Control mode selector (= APC or ACC) (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnControlMode); + RETURN_ON_MM_ERROR(CreateProperty("Control mode", "", MM::String, false, pAct)); + AddAllowedValue("Control mode", "ACC"); + AddAllowedValue("Control mode", "APC"); + + //Fire property + pAct = new CPropertyAction(this, &GenericLaser::OnFire); + RETURN_ON_MM_ERROR(CreateProperty("Fire", "0", MM::Float, false, pAct)); + + // Define the maximal current and power + maxRelPower_ = 100.0; + maxCurrent_ = 105.0; + + // Power set point (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnPowerSetPoint); + RETURN_ON_MM_ERROR(CreateProperty("Power set point", "0", MM::Float, false, pAct)); + SetPropertyLimits("Power set point", 0, maxRelPower_); + + // Current set point (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnCurrentSetPoint); + RETURN_ON_MM_ERROR(CreateProperty("Current set point", "0", MM::Float, false, pAct)); + SetPropertyLimits("Current set point", 0, maxCurrent_); + + RETURN_ON_MM_ERROR(UpdateStatus()); + + initialized_ = true; + } + + return DEVICE_OK; +} + + +/* +int OxxiusLBX::Shutdown() +{ + initialized_ = false; + return DEVICE_OK; +} +*/ + + +int OxxiusLBX::Fire(double deltaT) +{ + string activate_query = "DL 1"; + string deactivate_query = "DL 0"; + + if (laserOn_ == false) { + laserOn_ = true; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, activate_query.c_str(), false)); + } + CDeviceUtils::SleepMs((long)(deltaT)); + laserOn_ = false; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, deactivate_query.c_str(), false)); + //At the end, the laser is set to off + + return DEVICE_OK; +} + +//RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.c_str(), false)); + +/////////////////////////////////////////////////////////////////////////////// +// Action handlers +/////////////////////////////////////////////////////////////////////////////// + +int OxxiusLBX::OnAlarm(MM::PropertyBase* pProp, MM::ActionType) +{ + unsigned int alarmInt = 99; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "?F", false)); + + parentHub_->ParseforInteger(alarmInt); + + switch (alarmInt) { + case 0: + alarm_ = "No Alarm"; + break; + case 1: + alarm_ = "Out-of-bounds diode current"; + break; + case 2: + alarm_ = "Unexpected laser power value"; + break; + case 3: + alarm_ = "Out-of-bounds supply voltage"; + break; + case 4: + alarm_ = "Out-of-bounds internal temperature"; + break; + case 5: + alarm_ = "Out-of-bounds baseplate temperature"; + break; + case 7: + alarm_ = "Interlock circuit open"; + break; + case 8: + alarm_ = "Soft reset"; + break; + default: + alarm_ = "Other alarm"; + } + + pProp->Set(alarm_.c_str()); + + return DEVICE_OK; +} + + +int OxxiusLBX::OnState(MM::PropertyBase* pProp, MM::ActionType) +{ + unsigned int stateInt = 99; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "?STA", false)); + + parentHub_->ParseforInteger(stateInt); + + switch (stateInt) { + case 1: + state_ = "Warm-up phase"; + break; + case 2: + state_ = "Stand-by state"; + break; + case 3: + state_ = "Emission on"; + break; + case 4: + state_ = "Internal error"; + break; + case 5: + state_ = "Alarm"; + break; + case 6: + state_ = "Sleep state"; + break; + default: + state_ = "Other state"; + } + + pProp->Set(alarm_.c_str()); + + return DEVICE_OK; +} + + +int OxxiusLBX::OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + unsigned int status = 0; + ostringstream query; + + query << "?CS " << slot_; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, query.str().c_str(), false)); + parentHub_->ParseforInteger(status); + + switch (status) { + case 0: // LBX model: Emission off + laserOn_ = false; + break; + case 1: // LBX model: Emission on + laserOn_ = true; + break; + default: + laserOn_ = true; + } + + if (laserOn_) { + pProp->Set("ON"); + } + else { + pProp->Set("OFF"); + } + } + else if (eAct == MM::AfterSet) { + string newEmissionStatus, newCommand = ""; + + pProp->Get(newEmissionStatus); + + if (newEmissionStatus.compare("ON") == 0) { + newCommand = "DL 1"; + laserOn_ = true; + } + else if (newEmissionStatus.compare("OFF") == 0) { + newCommand = "DL 0"; + laserOn_ = false; + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.c_str(), false)); + } + return DEVICE_OK; +} + + +int OxxiusLBX::OnDigitalMod(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + unsigned int querySlot = NO_SLOT; + + if (eAct == MM::BeforeGet) { + string query; + + querySlot = slot_; + query = "?TTL"; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, query.c_str(), false)); + + bool digiM; + parentHub_->ParseforBoolean(digiM); + + if (digiM) + digitalMod_.assign("ON"); + else + digitalMod_.assign("OFF"); + + pProp->Set(digitalMod_.c_str()); + + } + else if (eAct == MM::AfterSet) { + string newModSet, newCommand; + + pProp->Get(newModSet); + digitalMod_.assign(newModSet); + + if (digitalMod_ == "ON") { + querySlot = slot_; + newCommand.assign("TTL 1"); + } + + else if (digitalMod_ == "OFF") { + querySlot = slot_; + newCommand.assign("TTL 0"); + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, newCommand.c_str(), false)); + } + return DEVICE_OK; +} + + +int OxxiusLBX::OnAnalogMod(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + string query; + query = "?AM"; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, query.c_str(), false)); + bool digiM; + parentHub_->ParseforBoolean(digiM); + + if (digiM) { + digitalMod_.assign("ON"); + } + else { + digitalMod_.assign("OFF"); + } + pProp->Set(digitalMod_.c_str()); + + } + else if (eAct == MM::AfterSet) { + ostringstream newCommand; + newCommand << "AM "; + + pProp->Get(digitalMod_); + + if (digitalMod_ == "OFF") + newCommand << "0"; + else + newCommand << "1"; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.str().c_str(), false)); + } + return DEVICE_OK; +} + + +int OxxiusLBX::OnControlMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + unsigned int ctrlM = 1; + string command; + + command = "?APC"; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, command.c_str(), false)); + parentHub_->ParseforInteger(ctrlM); + + if (ctrlM == 1) { + controlMode_.assign("APC"); + } + else if (ctrlM == 0) { + controlMode_.assign("ACC"); + } + + pProp->Set(controlMode_.c_str()); + } + else if (eAct == MM::AfterSet) { + string newCommand; + + pProp->Get(controlMode_); + + if (controlMode_ == "ACC") { + newCommand.assign("APC 0"); + } + else if (controlMode_ == "APC") { + newCommand.assign("APC 1"); + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.c_str(), false)); + } + return DEVICE_OK; +} + +/* +int OxxiusLBX::OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + string command = "?SP"; + unsigned int thisSlot = slot_; + float absSetPoint_; + + + if ((0 < mpa_number) && (mpa_number < 7)) { + thisSlot = NO_SLOT; + command = "?PL"; + stringstream s; + s << mpa_number; + command += s.str(); + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), thisSlot, command.c_str(), false)); + parentHub_->ParseforFloat(absSetPoint_ ); + + pProp->Set(static_cast( absSetPoint_ )); + } + else if (eAct == MM::AfterSet) { + + double GUISetPoint = 0.0; + pProp->Get(GUISetPoint); + + if ((GUISetPoint >= 0.0) || (GUISetPoint <= nominalPower_)) { + string command = "P"; + unsigned int thisSlot = slot_; + + ostringstream newCommand; + char* powerSPString = new char[20]; + strcpy(powerSPString, CDeviceUtils::ConvertToString( static_cast(GUISetPoint) ) ); + + if ((0 < mpa_number) && (mpa_number < 7)) { + thisSlot = NO_SLOT; + command = "IP"; + command += CDeviceUtils::ConvertToString((int)(mpa_number)); + + strcpy(powerSPString, CDeviceUtils::ConvertToString(GUISetPoint)); + } + + newCommand << command << " " << powerSPString; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), thisSlot, newCommand.str().c_str(), false)); + } + else { + // If the value entered through the GUI is not valid, read the machine value + OnPowerSetPoint(pProp, MM::BeforeGet); + } + } + return DEVICE_OK; +} +*/ + + +int OxxiusLBX::OnCurrentSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + float machineSetPoint = 0.0; + string command = "?SC"; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, command.c_str(), false)); + parentHub_->ParseforFloat(machineSetPoint); + + pProp->Set(machineSetPoint); + } + + else if (eAct == MM::AfterSet) { + + double GUISetPoint = 0.0; + pProp->Get(GUISetPoint); + + if ((GUISetPoint >= 0.0) || (GUISetPoint <= maxCurrent_)) { + + ostringstream newCommand; + string command = "C"; + + char* currentSPString = new char[20]; + strcpy(currentSPString, CDeviceUtils::ConvertToString(GUISetPoint)); + + newCommand << command << " " << currentSPString; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.str().c_str(), false)); + + } + } + return DEVICE_OK; +} + +/* +int OxxiusLBX::OnFire(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::AfterSet) { + double input; + pProp->Get(input); + return Fire(input); + } + return DEVICE_OK; +} +*/ \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusLBX.h b/DeviceAdapters/OxxiusCombiner/OxxiusLBX.h new file mode 100644 index 000000000..e5e3034d3 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusLBX.h @@ -0,0 +1,46 @@ +#pragma once +#include "GenericLaser.h" +using namespace std; + +class OxxiusLBX : public GenericLaser +{ +public: + OxxiusLBX(const char* nameAndSlot); + ~OxxiusLBX(); + + int Initialize() ; + // int Shutdown() ; + + int Fire(double deltaT) ; + + // int OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) ; + int OnCurrentSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) ; + int OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct) ; + int OnControlMode(MM::PropertyBase* pProp, MM::ActionType eAct) ; + int OnAnalogMod(MM::PropertyBase* pProp, MM::ActionType eAct) ; + int OnDigitalMod(MM::PropertyBase* pProp, MM::ActionType eAct) ; + int OnState(MM::PropertyBase* pProp, MM::ActionType eAct) ; + int OnAlarm(MM::PropertyBase* pProp, MM::ActionType eAct) ; + // int OnFire(MM::PropertyBase* pProp, MM::ActionType eAct) ; + + int OnPowerReadback(MM::PropertyBase* , MM::ActionType ) { return DEVICE_OK; }; + int OnCurrentReadback(MM::PropertyBase* , MM::ActionType ) { return DEVICE_OK; }; + int OnOperatingMode(MM::PropertyBase* , MM::ActionType ) { return DEVICE_OK; }; + + +private: + bool initialized_; + + //// SPECIFIC TO LBX + float maxRelPower_; + float nominalPower_; + float maxCurrent_; + unsigned int waveLength; + string state_; + string alarm_; + string controlMode_; + string analogMod_; + string digitalMod_; + + int mpa_number; +}; diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusLCX.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusLCX.cpp new file mode 100644 index 000000000..0c2e941bc --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusLCX.cpp @@ -0,0 +1,575 @@ +#include "OxxiusLCX.h" +using namespace std; + +OxxiusLCX::OxxiusLCX(const char* nameAndSlot) +{ + initialized_ = false; + + string tSlot = string(nameAndSlot); + + name_.assign(tSlot);// set laser name + tSlot = tSlot.substr(tSlot.length() - 1, 1); + slot_ = (unsigned int)atoi(tSlot.c_str());// set laser slot + + parentHub_; + busy_ = false; + laserOn_ = false; + alarm_ = ""; + state_ = ""; + digitalMod_ = ""; + analogMod_ = ""; + controlMode_ = ""; + + //Maybe useless + link_AOM1 = false; + link_AOM2 = false; + pow_adj = false; + mpa_number = -1; + + // powerSetPoint_ = 0.0; + maxRelPower_ = 0.0; + nominalPower_ = 0.0; + maxCurrent_ = 125.0; + + InitializeDefaultErrorMessages(); + SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); + + // parent ID display + CreateHubIDProperty(); +} + + +OxxiusLCX::~OxxiusLCX() +{ + Shutdown(); +} + + + +int OxxiusLCX::Initialize() +{ + if (!initialized_) { + + size_t found; + string strSlot; + string spa; + + + parentHub_ = static_cast(GetParentHub()); + if (!parentHub_) { + return DEVICE_COMM_HUB_MISSING; + } + char hubLabel[MM::MaxStrLength]; + parentHub_->GetLabel(hubLabel); + SetParentID(hubLabel); // for backward compatibility + + // Set property list + // ----------------- + // Name (read only) + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Name, name_.c_str(), MM::String, true)); + + // Description (read only) + ostringstream descriPt1; + char sourceSerialNumber[] = "LAS-XXXXXXXXXX"; + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "HID?", false); + parentHub_->ParseforChar(sourceSerialNumber); + + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "IP", true); + parentHub_->ParseforString(spa); + + parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "INF?", false); + parentHub_->ParseforString(strSlot); + + // Retrieves and delete the laser's type + found = strSlot.find("-"); + if (found != string::npos) { + strSlot.erase(0, found + 1); + } + + // Retrieves and define the laser's wavelength + found = strSlot.find("-"); + if (found != string::npos) { + waveLength = (unsigned int)atoi(strSlot.substr(0, found).c_str()); + } + + // Retrieves and define the nominal power + strSlot.erase(0, found + 1); + nominalPower_ = (float)atof(strSlot.substr(0, found).c_str()); + + + + if (parentHub_->GetMPA(slot_)) { + mpa_number = slot_; + } + else { + mpa_number = -1; + } + + + if (parentHub_->GetAOMpos1(slot_)) { //laser has AMO1 + link_AOM1 = true; + } + if (parentHub_->GetAOMpos2(slot_)) { //laser has AMO2 + link_AOM2 = true; + } + else if (spa != "????") { //self modulating lcx + pow_adj = true; + } + + descriPt1 << "LCX"; + + descriPt1 << " source on slot " << slot_; + descriPt1 << ", " << sourceSerialNumber; + + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Description, descriPt1.str().c_str(), MM::String, true)); + + // Alarm (read only) + CPropertyAction* pAct = new CPropertyAction(this, &GenericLaser::OnAlarm); + RETURN_ON_MM_ERROR(CreateProperty("Alarm", "None", MM::String, true, pAct)); + + // Status (read only) + pAct = new CPropertyAction(this, &GenericLaser::OnState); + RETURN_ON_MM_ERROR(CreateProperty("State", "", MM::String, true, pAct)); + + // Emission selector (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnEmissionOnOff); + RETURN_ON_MM_ERROR(CreateProperty("Emission", "", MM::String, false, pAct)); + AddAllowedValue("Emission", "ON"); + AddAllowedValue("Emission", "OFF"); + + // Digital modulation selector (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnDigitalMod); + RETURN_ON_MM_ERROR(CreateProperty("Digital Modulation", "", MM::String, false, pAct)); + AddAllowedValue("Digital Modulation", "ON"); + AddAllowedValue("Digital Modulation", "OFF"); + + // Analog modulation selector (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnAnalogMod); + RETURN_ON_MM_ERROR(CreateProperty("Analog Modulation", "", MM::String, false, pAct)); + AddAllowedValue("Analog Modulation", "ON"); + AddAllowedValue("Analog Modulation", "OFF"); + + // Control mode selector (= APC or ACC) (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnControlMode); + RETURN_ON_MM_ERROR(CreateProperty("Control mode", "", MM::String, false, pAct)); + AddAllowedValue("Control mode", "ACC"); + AddAllowedValue("Control mode", "APC"); + + //Fire property + pAct = new CPropertyAction(this, &GenericLaser::OnFire); + RETURN_ON_MM_ERROR(CreateProperty("Fire", "0", MM::Float, false, pAct)); + + + + // Retrieves and define the laser's maximal power + string maxpowCmd = "DL SPM"; + + maxRelPower_ = 100.0; + + + // Retrieves and define the laser's maximal current + maxCurrent_ = 125.0; + + + // Power set point (write/read) + pAct = new CPropertyAction(this, &GenericLaser::OnPowerSetPoint); + RETURN_ON_MM_ERROR(CreateProperty("Power set point", "0", MM::Float, false, pAct)); + SetPropertyLimits("Power set point", 0, maxRelPower_); + + RETURN_ON_MM_ERROR(UpdateStatus()); + + initialized_ = true; + } + + return DEVICE_OK; +} + + +int OxxiusLCX::Fire(double deltaT) +{ + string activate_query = "DL 1"; + string deactivate_query = "DL 0"; + + if (laserOn_ == false) { + laserOn_ = true; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, activate_query.c_str(), false)); + } + CDeviceUtils::SleepMs((long)(deltaT)); + laserOn_ = false; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, deactivate_query.c_str(), false)); + //At the end, the laser is set to off + + return DEVICE_OK; +} + +//RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.c_str(), false)); + +/////////////////////////////////////////////////////////////////////////////// +// Action handlers +/////////////////////////////////////////////////////////////////////////////// + +int OxxiusLCX::OnAlarm(MM::PropertyBase* pProp, MM::ActionType) +{ + unsigned int alarmInt = 99; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "?F", false)); + + parentHub_->ParseforInteger(alarmInt); + + switch (alarmInt) { + case 0: + alarm_ = "No Alarm"; + break; + case 1: + alarm_ = "Out-of-bounds diode current"; + break; + case 2: + alarm_ = "Unexpected laser power value"; + break; + case 3: + alarm_ = "Out-of-bounds supply voltage"; + break; + case 4: + alarm_ = "Out-of-bounds internal temperature"; + break; + case 5: + alarm_ = "Out-of-bounds baseplate temperature"; + break; + case 7: + alarm_ = "Interlock circuit open"; + break; + case 8: + alarm_ = "Soft reset"; + break; + default: + alarm_ = "Other alarm"; + } + + pProp->Set(alarm_.c_str()); + + return DEVICE_OK; +} + + +int OxxiusLCX::OnState(MM::PropertyBase* pProp, MM::ActionType) +{ + unsigned int stateInt = 99; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "?STA", false)); + + parentHub_->ParseforInteger(stateInt); + + switch (stateInt) { + case 1: + state_ = "Warm-up phase"; + break; + case 2: + state_ = "Stand-by state"; + break; + case 3: + state_ = "Emission on"; + break; + case 4: + state_ = "Internal error"; + break; + case 5: + state_ = "Alarm"; + break; + case 6: + state_ = "Sleep state"; + break; + default: + state_ = "Other state"; + } + + pProp->Set(alarm_.c_str()); + + return DEVICE_OK; +} + + + +int OxxiusLCX::OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + unsigned int status = 0; + ostringstream query; + + query << "?CS " << slot_; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, query.str().c_str(), false)); + parentHub_->ParseforInteger(status); + + switch (status) { + case 2: // LCX model: shutter closed + laserOn_ = false; + break; + case 3: // LCX model: shutter open + laserOn_ = true; + break; + default: + laserOn_ = true; + } + + if (laserOn_) { + pProp->Set("ON"); + } + else { + pProp->Set("OFF"); + } + } + else if (eAct == MM::AfterSet) { + string newEmissionStatus, newCommand = ""; + + pProp->Get(newEmissionStatus); + + if (newEmissionStatus.compare("ON") == 0) { + newCommand = "DL 1"; + laserOn_ = true; + } + else if (newEmissionStatus.compare("OFF") == 0) { + newCommand = "DL 0"; + laserOn_ = false; + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.c_str(), false)); + } + return DEVICE_OK; +} + + +int OxxiusLCX::OnDigitalMod(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + unsigned int querySlot = NO_SLOT; + + if (eAct == MM::BeforeGet) { + string query; + + + querySlot = NO_SLOT; + if (link_AOM1 == true) { + query = "AOM1 TTL"; + } + else if (link_AOM2 == true) { + query = "AOM2 TTL"; + } + else { + query = ""; + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, query.c_str(), false)); + + bool digiM; + parentHub_->ParseforBoolean(digiM); + + if (digiM) + digitalMod_.assign("ON"); + else + digitalMod_.assign("OFF"); + + pProp->Set(digitalMod_.c_str()); + + } + else if (eAct == MM::AfterSet) { + string newModSet, newCommand; + + pProp->Get(newModSet); + digitalMod_.assign(newModSet); + + if (digitalMod_ == "ON") { + querySlot = NO_SLOT; + + if (link_AOM1 == true) { + newCommand.assign("AOM1 TTL 1"); + } + else if (link_AOM2 == true) { + newCommand.assign("AOM2 TTL 1"); + } + else { + newCommand.assign(""); + } + + } + else if (digitalMod_ == "OFF") { + querySlot = NO_SLOT; + + if (link_AOM1 == true) { + newCommand.assign("AOM1 TTL 0"); + } + else if (link_AOM2 == true) { + newCommand.assign("AOM2 TTL 0"); + } + else { + newCommand.assign(""); + } + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, newCommand.c_str(), false)); + } + return DEVICE_OK; +} + + +int OxxiusLCX::OnAnalogMod(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + unsigned int querySlot = NO_SLOT; + + if (eAct == MM::BeforeGet) { + string query; + querySlot = NO_SLOT; + + if (link_AOM1 == true) { + query = "AOM1 AM"; + } + else if (link_AOM2 == true) { + query = "AOM2 AM"; + } + else { + query = ""; + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, query.c_str(), false)); + bool digiM; + parentHub_->ParseforBoolean(digiM); + + if (digiM) { + digitalMod_.assign("ON"); + } + else { + digitalMod_.assign("OFF"); + } + pProp->Set(digitalMod_.c_str()); + + } + else if (eAct == MM::AfterSet) { + ostringstream newCommand; + + querySlot = NO_SLOT; + if (link_AOM1 == true) { + newCommand << "AOM1 AM"; + } + if (link_AOM2 == true) { + newCommand << "AOM2 AM"; + } + else { + return DEVICE_OK; + } + + pProp->Get(digitalMod_); + + if (digitalMod_ == "OFF") + newCommand << "0"; + else + newCommand << "1"; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, newCommand.str().c_str(), false)); + } + return DEVICE_OK; +} + + +int OxxiusLCX::OnControlMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + controlMode_.assign("APC"); + pProp->Set(controlMode_.c_str()); + } + else if (eAct == MM::AfterSet) { + string newCommand; + + pProp->Get(controlMode_); + + controlMode_ = "APC"; + pProp->Set(controlMode_.c_str()); + + } + return DEVICE_OK; +} + +/* +int OxxiusLCX::OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + string command = "?SP"; + unsigned int thisSlot = slot_; + float absSetPoint_; + + if ((0 < mpa_number) && (mpa_number < 7)) { + thisSlot = NO_SLOT; + command = "?PL"; + stringstream s; + s << mpa_number; + command += s.str(); + } + else { + if (link_AOM1 == true) { + thisSlot = NO_SLOT; + command = "?SP1"; + } + else if (link_AOM2 == true) { + thisSlot = NO_SLOT; + command = "?SP2"; + } + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), thisSlot, command.c_str(), false)); + parentHub_->ParseforFloat(absSetPoint_); + + pProp->Set((100 * absSetPoint_) / nominalPower_); + } + else if (eAct == MM::AfterSet) { + + double GUISetPoint = 0.0; + pProp->Get(GUISetPoint); + + if ((GUISetPoint >= 0.0) || (GUISetPoint <= maxRelPower_)) { + string command = "P"; + unsigned int thisSlot = slot_; + + ostringstream newCommand; + char* powerSPString = new char[20]; + strcpy(powerSPString, CDeviceUtils::ConvertToString((GUISetPoint * nominalPower_) / 100)); + + if ((0 < mpa_number) && (mpa_number < 7)) { + thisSlot = NO_SLOT; + command = "IP"; + command += CDeviceUtils::ConvertToString((int)(mpa_number)); + + strcpy(powerSPString, CDeviceUtils::ConvertToString(GUISetPoint)); + } + else { + if (link_AOM1 == true) { + thisSlot = NO_SLOT; + command = "P"; + } + else if (link_AOM2 == true) { + thisSlot = NO_SLOT; + command = "P2"; + } + else if (pow_adj == true) { + command = "IP"; + strcpy(powerSPString, CDeviceUtils::ConvertToString(GUISetPoint)); + } + } + + newCommand << command << " " << powerSPString; + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), thisSlot, newCommand.str().c_str(), false)); + } + else { + // If the value entered through the GUI is not valid, read the machine value + OnPowerSetPoint(pProp, MM::BeforeGet); + } + } + return DEVICE_OK; +} +*/ + +/* +int OxxiusLCX::OnFire(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::AfterSet) { + double input; + pProp->Get(input); + return Fire(input); + } + return DEVICE_OK; +} +*/ \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusLCX.h b/DeviceAdapters/OxxiusCombiner/OxxiusLCX.h new file mode 100644 index 000000000..b361a596b --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusLCX.h @@ -0,0 +1,47 @@ +#pragma once +#include "GenericLaser.h" +using namespace std; + +class OxxiusLCX : public GenericLaser +{ +public: + OxxiusLCX(const char* nameAndSlot); + ~OxxiusLCX(); + + int Initialize(); + + int Fire(double deltaT); + + // int OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnCurrentSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; }; //never called because it's a LCX laser + int OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnControlMode(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnAnalogMod(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnDigitalMod(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnState(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnAlarm(MM::PropertyBase* pProp, MM::ActionType eAct); + // int OnFire(MM::PropertyBase* pProp, MM::ActionType eAct); + + int OnPowerReadback(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; }; + int OnCurrentReadback(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; }; + int OnOperatingMode(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; }; + +private: + bool initialized_; + + //// SPECIFIC TO LCX + float maxRelPower_; + float nominalPower_; + float maxCurrent_; + unsigned int waveLength; + string state_; + string alarm_; + string controlMode_; + string analogMod_; + string digitalMod_; + + bool link_AOM1;//->LCX linked to a AOM number 1 + bool link_AOM2;//->LCX linked to a AOM number 2 + bool pow_adj; //-> LCX with power adjustment + int mpa_number; //-> LCX linked to mpa number n +}; \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp new file mode 100644 index 000000000..cb4b5ab36 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp @@ -0,0 +1,170 @@ +#include "OxxiusMDual.h" + +#include +#include +#include +#include +#include "../../MMDevice/ModuleInterface.h" +using namespace std; + +/////////////////////////////////////////////////////////////////////////////// +// +// Oxxius M-Dual implementation +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +/////////////////////////////////////////////////////////////////////////////// + +OxxiusMDual::OxxiusMDual(const char* nameAndSlot) : initialized_(false) +{ + parentHub_ = 0; + core_ = GetCoreCallback(); + + std::string tSlot = string(nameAndSlot); + name_.assign(tSlot); // sets MDual name + + slot_ = tSlot.substr(tSlot.length() - 1, 1); + + // Set property list + // ----------------- + // Name (read only) + CreateProperty(MM::g_Keyword_Name, name_.c_str(), MM::String, true); + + CreateProperty(MM::g_Keyword_Description, "M-Dual module", MM::String, true); + + InitializeDefaultErrorMessages(); + SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); + + // parent ID display + CreateHubIDProperty(); +} + + +OxxiusMDual::~OxxiusMDual() +{ + Shutdown(); +} + + +int OxxiusMDual::Initialize() +{ + if (!initialized_) { + parentHub_ = static_cast(GetParentHub()); + if (!parentHub_) { + return DEVICE_COMM_HUB_MISSING; + } + char hubLabel[MM::MaxStrLength]; + parentHub_->GetLabel(hubLabel); + SetParentID(hubLabel); // for backward compatibility + + + // Set property list + // ----------------- + CPropertyAction* pAct = new CPropertyAction(this, &OxxiusMDual::OnSetRatio);//setting the possible positions + RETURN_ON_MM_ERROR(CreateProperty("Split ratio", "0", MM::Float, false, pAct)); + SetPropertyLimits("Split ratio", 0.0, 100.0); + + // Set property list + // ----------------- + // State + /*CPropertyAction* pAct = new CPropertyAction (this, &OxxiusMDual::OnState); + RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_State, "0", MM::Integer, false, pAct) ); + SetPropertyLimits("Set Position", 0, 100);*/ + + /*char pos[3]; + for (unsigned int i=0; iQueryCommand(this, GetCoreCallback(), NO_SLOT, command.str().c_str()) ); + parentHub_->ParseforInteger(currentPos); + + //SetPosition(currentPos); + pProp->Set((long)currentPos); + } + else if (eAct == MM::AfterSet) { + long newPosition = 0; + + //GetPosition(newPosition); + pProp->Get(newPosition); + + std::ostringstream newCommand; + newCommand << "IP" << slot_ << " " << newPosition; + + RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, newCommand.str().c_str()) ); + } + return DEVICE_OK; +}*/ + + +int OxxiusMDual::OnSetRatio(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + double currentRatio = 0.0; + std::ostringstream command; + command << "IP" << slot_; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, command.str().c_str(), true)); + parentHub_->ParseforPercent(currentRatio); + + pProp->Set(currentRatio); + } + + else if (eAct == MM::AfterSet) { + double newRatio = 0.0; + + pProp->Get(newRatio); + if ((newRatio >= 0.0) || (newRatio <= 100.0)) { + std::ostringstream newCommand; + newCommand << "IP" << slot_ << " " << newRatio; + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, newCommand.str().c_str(), true)); + } + } + return DEVICE_OK; +} \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusMDual.h b/DeviceAdapters/OxxiusCombiner/OxxiusMDual.h new file mode 100644 index 000000000..b610a44ad --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusMDual.h @@ -0,0 +1,37 @@ +#pragma once + +#include "OxxiusCombinerHub.h" + +#include "../../MMDevice/MMDevice.h" +#include "../../MMDevice/DeviceBase.h" +#include "../../MMDevice/ModuleInterface.h" +#include +#include +#include + +class OxxiusMDual : public CGenericBase +{ +public: + OxxiusMDual(const char* name); + ~OxxiusMDual(); + + // MMDevice API + // ------------ + int Initialize(); + int Shutdown(); + + void GetName(char* pszName) const; + bool Busy(); + + // Action Interface + // ---------------- + int OnSetRatio(MM::PropertyBase* pProp, MM::ActionType eAct); + +private: + bool initialized_; + std::string name_; + std::string slot_; + MM::Core* core_; + + OxxiusCombinerHub* parentHub_; +}; diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusShutter.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusShutter.cpp new file mode 100644 index 000000000..ffad899cc --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusShutter.cpp @@ -0,0 +1,148 @@ +#include "OxxiusShutter.h" + +#include +#include +#include +#include +#include "../../MMDevice/ModuleInterface.h" +using namespace std; + +/////////////////////////////////////////////////////////////////////////////// +// +// Oxxius shutter implementation +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +/////////////////////////////////////////////////////////////////////////////// + +OxxiusShutter::OxxiusShutter(const char* nameAndSlot) : initialized_(false) +{ + isOpen_ = false; + parentHub_ = 0; + + name_.assign(nameAndSlot); + + std::string strChnl = string(nameAndSlot); + strChnl = strChnl.substr(strChnl.length() - 1, 1); + channel_ = (unsigned int)atoi(strChnl.c_str()); + + // Set property list + // ----------------- + // Name (read only) + CreateProperty(MM::g_Keyword_Name, name_.c_str(), MM::String, true); + + std::ostringstream shutterDesc; + shutterDesc << "Electro-mechanical shutter on channel " << channel_ << "."; + CreateProperty(MM::g_Keyword_Description, shutterDesc.str().c_str(), MM::String, true); + + InitializeDefaultErrorMessages(); + SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); + + // parent ID display + CreateHubIDProperty(); +} + + +OxxiusShutter::~OxxiusShutter() +{ + Shutdown(); +} + + +int OxxiusShutter::Initialize() +{ + if (!initialized_) { + parentHub_ = static_cast(GetParentHub()); + if (!parentHub_) { + return DEVICE_COMM_HUB_MISSING; + } + char hubLabel[MM::MaxStrLength]; + parentHub_->GetLabel(hubLabel); + SetParentID(hubLabel); // for backward compatibility + + // Set property list + // ----------------- + + // Open/Close selector (write/read) + CPropertyAction* pAct = new CPropertyAction(this, &OxxiusShutter::OnState); + RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_State, "", MM::String, false, pAct)); + AddAllowedValue(MM::g_Keyword_State, "Open"); + AddAllowedValue(MM::g_Keyword_State, "Closed"); + + // Closing the shutter on Initialization + RETURN_ON_MM_ERROR(SetProperty(MM::g_Keyword_State, "Closed")); + + RETURN_ON_MM_ERROR(UpdateStatus()); + + initialized_ = true; + } + + return DEVICE_OK; +} + + +int OxxiusShutter::Shutdown() +{ + initialized_ = false; + return DEVICE_OK; +} + + +void OxxiusShutter::GetName(char* Name) const +{ + CDeviceUtils::CopyLimitedString(Name, name_.c_str()); +} + + +bool OxxiusShutter::Busy() +{ + return false; +} + + +int OxxiusShutter::SetOpen(bool openCommand) +{ + if (openCommand) + return SetProperty(MM::g_Keyword_State, "Open"); + else + return SetProperty(MM::g_Keyword_State, "Closed"); +} + + +int OxxiusShutter::GetOpen(bool& isOpen) +{ + isOpen = isOpen_; + return DEVICE_OK; +} + + +int OxxiusShutter::OnState(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) { + if (isOpen_) { + pProp->Set("Open"); + } + else { + pProp->Set("Closed"); + } + } + else if (eAct == MM::AfterSet) { + + std::string newState = ""; + pProp->Get(newState); + + std::ostringstream newCommand; + newCommand << "SH" << channel_ << " "; + + if (newState.compare("Open") == 0) { + newCommand << "1"; + isOpen_ = true; + } + else if (newState.compare("Closed") == 0) { + newCommand << "0"; + isOpen_ = false; + } + + RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, newCommand.str().c_str(), false)); + } + return DEVICE_OK; +} \ No newline at end of file diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusShutter.h b/DeviceAdapters/OxxiusCombiner/OxxiusShutter.h new file mode 100644 index 000000000..e637e98c9 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/OxxiusShutter.h @@ -0,0 +1,50 @@ +#pragma once + +#include "OxxiusCombinerHub.h" + +#include "../../MMDevice/MMDevice.h" +#include "../../MMDevice/DeviceBase.h" +#include "../../MMDevice/ModuleInterface.h" +#include +#include +#include + +////////////////////////////////////////////////////////////////////////////// +// +// Device adaptaters for "shutter" source in Combiner +// +////////////////////////////////////////////////////////////////////////////// + +class OxxiusShutter : public CShutterBase +{ +public: + OxxiusShutter(const char* nameAndChannel); + ~OxxiusShutter(); + + // MMDevice API + // ------------ + int Initialize(); + int Shutdown(); + + void GetName(char* pszName) const; + bool Busy(); + + // Shutter API + int SetOpen(bool openCommand = true); + int GetOpen(bool& isOpen); + int Fire(double /*deltaT*/) { return DEVICE_UNSUPPORTED_COMMAND; } + + // Action Interface + // ---------------- + int OnState(MM::PropertyBase* pProp, MM::ActionType eAct); + +private: + bool initialized_; + std::string name_; + + OxxiusCombinerHub* parentHub_; + bool isOpen_; + unsigned int channel_; + + MM::MMTime changedTime_; +}; diff --git a/DeviceAdapters/OxxiusCombiner/Oxxius_combiner.cpp b/DeviceAdapters/OxxiusCombiner/Oxxius_combiner.cpp deleted file mode 100644 index 49e042ddd..000000000 --- a/DeviceAdapters/OxxiusCombiner/Oxxius_combiner.cpp +++ /dev/null @@ -1,1920 +0,0 @@ - -/////////////////////////////////////////////////////////////////////////////// -// FILE: OxxiusCombiner.cpp -// PROJECT: Micro-Manager -// SUBSYSTEM: DeviceAdapters -//----------------------------------------------------------------------------- -// DESCRIPTION: Controls Oxxius lasers and combiners through a serial port -// COPYRIGHT: Oxxius SA, 2013-2019 -// LICENSE: LGPL -// AUTHORS: Tristan Martinez, Pierre Bretagne -// - - -#include "Oxxius_combiner.h" -#include -#include -#include -#include -#include "ModuleInterface.h" -using namespace std; - -// -#define MAX_NUMBER_OF_SLOTS 6 -#define RCV_BUF_LENGTH 256 -#define NO_SLOT 0 - -// Oxxius devices -const char* g_OxxiusCombinerDeviceName = "Combiner"; -const char* g_OxxiusLaserBoxxDeviceName = "LaserBoxx source"; -const char* g_OxxiusLaserBoxx1DeviceName = "LaserBoxx source 1"; -const char* g_OxxiusLaserBoxx2DeviceName = "LaserBoxx source 2"; -const char* g_OxxiusLaserBoxx3DeviceName = "LaserBoxx source 3"; -const char* g_OxxiusLaserBoxx4DeviceName = "LaserBoxx source 4"; -const char* g_OxxiusLaserBoxx5DeviceName = "LaserBoxx source 5"; -const char* g_OxxiusLaserBoxx6DeviceName = "LaserBoxx source 6"; - -const char* g_ObisLaserDeviceName = "Obis laser source"; -const char* g_ObisLaser1DeviceName = "Obis laser Source 1"; -const char* g_ObisLaser2DeviceName = "Obis laser Source 2"; -const char* g_ObisLaser3DeviceName = "Obis laser Source 3"; -const char* g_ObisLaser4DeviceName = "Obis laser Source 4"; -const char* g_ObisLaser5DeviceName = "Obis laser Source 5"; -const char* g_ObisLaser6DeviceName = "Obis laser Source 6"; - -const char* g_OxxiusShutterDeviceName = "Shutter"; -const char* g_OxxiusShutter1DeviceName = "Shutter 1"; -const char* g_OxxiusShutter2DeviceName = "Shutter 2"; -const char* g_OxxiusMDualDeviceName = "MDual"; -const char* g_OxxiusMDualADeviceName = "MDual A"; -const char* g_OxxiusMDualBDeviceName = "MDual B"; -const char* g_OxxiusMDualCDeviceName = "MDual C"; -const char* g_OxxiusFlipMirrorDeviceName = "Flip-Mirror"; -const char* g_OxxiusFlipMirror1DeviceName = "Flip-Mirror 1"; -const char* g_OxxiusFlipMirror2DeviceName = "Flip-Mirror 2"; - - - - -const char* g_slotPrefix[7] = {"","L1 ","L2 ","L3 ","L4 ","L5 ","L6 "}; - -const char* convertable[3] = { "A", "B", "C" }; - -/////////////////////////////////////////////////////////////////////////////// -// Exported MMDevice API -/////////////////////////////////////////////////////////////////////////////// -MODULE_API void InitializeModuleData() -{ - RegisterDevice(g_OxxiusCombinerDeviceName, MM::HubDevice, "Oxxius laser combiner controlled through serial interface"); - RegisterDevice(g_OxxiusLaserBoxx1DeviceName, MM::ShutterDevice, "LaserBoxx on slot 1"); - RegisterDevice(g_OxxiusLaserBoxx2DeviceName, MM::ShutterDevice, "LaserBoxx on slot 2"); - RegisterDevice(g_OxxiusLaserBoxx3DeviceName, MM::ShutterDevice, "LaserBoxx on slot 3"); - RegisterDevice(g_OxxiusLaserBoxx4DeviceName, MM::ShutterDevice, "LaserBoxx on slot 4"); - RegisterDevice(g_OxxiusLaserBoxx5DeviceName, MM::ShutterDevice, "LaserBoxx on slot 5"); - RegisterDevice(g_OxxiusLaserBoxx6DeviceName, MM::ShutterDevice, "LaserBoxx on slot 6"); - - RegisterDevice(g_ObisLaser1DeviceName, MM::ShutterDevice, "Obis Laser on slot 1"); - RegisterDevice(g_ObisLaser2DeviceName, MM::ShutterDevice, "Obis Laser on slot 2"); - RegisterDevice(g_ObisLaser3DeviceName, MM::ShutterDevice, "Obis Laser on slot 3"); - RegisterDevice(g_ObisLaser4DeviceName, MM::ShutterDevice, "Obis Laser on slot 4"); - RegisterDevice(g_ObisLaser5DeviceName, MM::ShutterDevice, "Obis Laser on slot 5"); - RegisterDevice(g_ObisLaser6DeviceName, MM::ShutterDevice, "Obis Laser on slot 6"); - - RegisterDevice(g_OxxiusShutter1DeviceName, MM::ShutterDevice, "E-m shutter on channel 1"); - RegisterDevice(g_OxxiusShutter2DeviceName, MM::ShutterDevice, "E-m shutter on channel 2"); - RegisterDevice(g_OxxiusMDualADeviceName, MM::GenericDevice, "M-Dual on channel A"); - RegisterDevice(g_OxxiusMDualBDeviceName, MM::GenericDevice, "M-Dual on channel B"); - RegisterDevice(g_OxxiusMDualCDeviceName, MM::GenericDevice, "M-Dual on channel C"); - RegisterDevice(g_OxxiusFlipMirror1DeviceName, MM::GenericDevice, "Flip-Mirror on slot 1"); - RegisterDevice(g_OxxiusFlipMirror2DeviceName, MM::GenericDevice, "Flip-Mirror on slot 2"); - -} - -MODULE_API MM::Device* CreateDevice(const char* deviceNameChar) -{ - if (deviceNameChar == 0) - return 0; - - std::string deviceNameAndSlot = string(deviceNameChar); - - if (strcmp(deviceNameChar,g_OxxiusCombinerDeviceName) == 0) { - return new OxxiusCombinerHub(); - } else if ( deviceNameAndSlot.compare(0, strlen(g_OxxiusLaserBoxxDeviceName), g_OxxiusLaserBoxxDeviceName) == 0 ) { - return new OxxiusLaserBoxx(deviceNameChar); - } else if ( deviceNameAndSlot.compare(0, strlen(g_OxxiusShutterDeviceName), g_OxxiusShutterDeviceName) == 0 ) { - return new OxxiusShutter(deviceNameChar); - } else if (deviceNameAndSlot.compare(0, strlen(g_OxxiusMDualDeviceName), g_OxxiusMDualDeviceName) == 0) { - return new OxxiusMDual(deviceNameChar); - } else if (deviceNameAndSlot.compare(0, strlen(g_OxxiusFlipMirrorDeviceName), g_OxxiusFlipMirrorDeviceName) == 0) { - return new OxxiusFlipMirror(deviceNameChar); - } else if (deviceNameAndSlot.compare(0, strlen(g_ObisLaserDeviceName), g_ObisLaserDeviceName) == 0) { -// return new OxxiusObisSupport(deviceNameChar); - } - return 0; -} - -MODULE_API void DeleteDevice(MM::Device* pDevice) -{ - delete pDevice; -} - -/////////////////////////////////////////////////////////////////////////////// -// -// Oxxius combiner implementation -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -/////////////////////////////////////////////////////////////////////////////// - - -OxxiusCombinerHub::OxxiusCombinerHub() : initialized_(false) -{ - // Initializing private variables - serialNumber_ = ""; - installedDevices_ = 0; - serialAnswer_ = ""; - interlockClosed_ = false; - keyActivated_ = false; - - InitializeDefaultErrorMessages(); - SetErrorText(ERR_COMBINER_NOT_FOUND, "Hub Device not found. The peer device is expected to be a Oxxius combiner"); - - - // Create pre-initialization properties - // ------------------------------------ - - // Communication port - CPropertyAction* pAct = new CPropertyAction(this, &OxxiusCombinerHub::OnPort); - CreateProperty(MM::g_Keyword_Port, "Undefined", MM::String, false, pAct, true); -} - -OxxiusCombinerHub::~OxxiusCombinerHub() -{ - Shutdown(); -} - -void OxxiusCombinerHub::GetName(char* name) const -{ - CDeviceUtils::CopyLimitedString(name, g_OxxiusCombinerDeviceName); -} - -bool OxxiusCombinerHub::Busy() -{ - return false; - -} - - -int OxxiusCombinerHub::Initialize() -{ - if(!initialized_) { - - // Set proprety list - // - - - - - - - - - - - // Name and description of the combiner: - RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_Name, g_OxxiusCombinerDeviceName, MM::String, true) ); - RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_Description, "Oxxius L6Cc/L4Cc combiner", MM::String, true) ); - - // Serial number of the combiner: - CPropertyAction* pAct = new CPropertyAction (this, &OxxiusCombinerHub::OnSerialNumber); - RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_HubID, serialNumber_.c_str(), MM::String, true, pAct) ); - - // Interlock circuit: - pAct = new CPropertyAction (this, &OxxiusCombinerHub::OnInterlock); - RETURN_ON_MM_ERROR( CreateProperty("Interlock circuit", "", MM::String, true, pAct) ); - - // Emission key: - pAct = new CPropertyAction (this, &OxxiusCombinerHub::OnEmissionKey); - RETURN_ON_MM_ERROR( CreateProperty("EmissionKey", "", MM::String, true, pAct) ); - - if (!IsCallbackRegistered()) - return DEVICE_NO_CALLBACK_REGISTERED; - - // Enumerates the installed AOMs and their position - bool AOM1en = false, AOM2en = false; - unsigned int ver = 0; - - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "AOM1 EN", false)); - ParseforBoolean(AOM1en); - - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "AOM2 EN", false)); - ParseforBoolean(AOM2en); - - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "SV?", false)); - ParseforVersion(ver); - - // A position equal to "0" stands for an absence of modulator - if (AOM1en) { - bool adcom = false; - string command = ""; - - if (ver < 1016) { //version check - adcom = true; - command = "AOM1 PO"; - } - else { - adcom = false; - command = "AOM1 POS"; - } - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, command.c_str(), adcom)); - ParseforInteger(AOM1pos_); - } - if (AOM2en) { - bool adcom = false; - string command = ""; - - if (ver < 1016) { //version check - adcom = true; - command = "AOM2 PO"; - } - else { - adcom = false; - command = "AOM2 POS"; - } - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, command.c_str(), adcom)); - ParseforInteger(AOM1pos_); - } - - - //Mpa position retreive - for (unsigned int i = 1; i <= MAX_NUMBER_OF_SLOTS; i++) { - string command = "IP"; - std::stringstream ss; - ss << i; - command += ss.str(); - - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, command.c_str(), true)); - if (serialAnswer_ != "????") { - mpa[i] = 1; - } - } - - - - RETURN_ON_MM_ERROR( UpdateStatus() ); - - initialized_ = true; - - // RETURN_ON_MM_ERROR( DetectInstalledDevices() ); - } - return DEVICE_OK; -} - - - -int OxxiusCombinerHub::DetectInstalledDevices() -{ - if (initialized_) { - - // Enumerates the lasers (or devices) present on the combiner - unsigned int masque = 1; - unsigned int repartition = 0; - - //sending command ?CL - RETURN_ON_MM_ERROR( QueryCommand(this, GetCoreCallback(), NO_SLOT, "?CL", false) ); - ParseforInteger(repartition); - - for(unsigned int querySlot=1; querySlot<=MAX_NUMBER_OF_SLOTS; querySlot++) { - if ((repartition & masque) != 0) { - string answer; - // A laser source is listed, now querying for detailed information (model, etc) - - std::string detailedInfo, serialNumber; - - //send command to get devices information - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), querySlot, "INF?", false)); - ParseforString(detailedInfo); - - if (detailedInfo != "timeout") { - std::ostringstream nameSlotModel; - nameSlotModel << g_OxxiusLaserBoxxDeviceName << " " << querySlot; - - MM::Device* pDev = ::CreateDevice(nameSlotModel.str().c_str()); - if (pDev) { - AddInstalledDevice(pDev); - installedDevices_++; - } - } - } - masque <<= 1; // Left-shift the bit mask and repeat - } - - // Creating Devices for the two electro-mechanical shutters: - for(unsigned int channel=1; channel<=2; channel++) { - std::ostringstream nameModelChannel; - nameModelChannel << g_OxxiusShutterDeviceName << " " << channel; - - MM::Device* pDev = ::CreateDevice(nameModelChannel.str().c_str()); - if (pDev) { - AddInstalledDevice(pDev); - installedDevices_++; - } - } - - // Creating Devices for the "Flip mirror" or MDUAL modules: - /*unsigned int FM1type = 0, FM2type = 0; - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "FM1C", false)); - ParseforInteger(FM1type); - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "FM2C", false)); - ParseforInteger(FM2type);*/ - - - //Mdual module creation - for (unsigned int j = 0; j <= 2; j++) { - std::string MDSlot; - std::ostringstream com; - com << "IP" << convertable[j]; - - std::ostringstream InfoMessage4; /////test in hub - InfoMessage4 << "test" << com.str(); - LogError(DEVICE_OK, this, GetCoreCallback(), InfoMessage4.str().c_str()); - - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, com.str().c_str(), true)); - ParseforString(MDSlot); - - com.str(""); - com.clear(); - com << g_OxxiusMDualDeviceName << " " << convertable[j]; - - if (MDSlot != "????") { - MM::Device* pDev = ::CreateDevice(com.str().c_str()); - if (pDev) { - AddInstalledDevice(pDev); - installedDevices_++; - } - - } - - } - - //Flip mirror module creation - - for (unsigned int j = 1; j <= 4; j++) { - unsigned int FMSlot; - std::ostringstream com; - com << "FM" << j << "C"; - - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, com.str().c_str(), false)); - ParseforInteger(FMSlot); - - com.str(""); - com.clear(); - com << g_OxxiusFlipMirrorDeviceName << " " << j; - - if (FMSlot == 1) { - - std::ostringstream InfoMessage4; /////test - InfoMessage4 << "test " << com.str().c_str(); - LogError(DEVICE_OK, this, GetCoreCallback(), InfoMessage4.str().c_str()); - - MM::Device* pDev = ::CreateDevice(com.str().c_str()); - if (pDev) { - AddInstalledDevice(pDev); - installedDevices_++; - } - - } - - } - - - //Laser OBIS creation - RETURN_ON_MM_ERROR(QueryCommand(this, GetCoreCallback(), NO_SLOT, "OB", true)); - ParseforInteger(obPos_); - - if (obPos_ != 0 && obPos_ != -1) { - std::ostringstream nameSlotModel; - nameSlotModel << g_ObisLaserDeviceName << " " << obPos_; - - MM::Device* pDev = ::CreateDevice(nameSlotModel.str().c_str()); - if (pDev) { - AddInstalledDevice(pDev); - installedDevices_++; - } - } - - /*switch (FM1type) { - case 4: { // MDual detected - MM::Device* pDev = ::CreateDevice(g_OxxiusMDualDeviceName); - if (pDev) { - AddInstalledDevice(pDev); - installedDevices_++; - } - break; - } - case 0: - default: - // nop - break; - }*/ - } - - return DEVICE_OK; -} - - -int OxxiusCombinerHub::Shutdown() -{ - initialized_ = false; - return DEVICE_OK; -} - - -/////////////////////////////////////////////////////////////////////////////// -// Action handlers -/////////////////////////////////////////////////////////////////////////////// - -int OxxiusCombinerHub::OnPort(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (eAct == MM::BeforeGet) - { - pProp->Set(port_.c_str()); - } - else if (eAct == MM::AfterSet) - { - pProp->Get(port_); - pProp->Set(port_.c_str()); - } - return DEVICE_OK; -} - - -int OxxiusCombinerHub::OnSerialNumber(MM::PropertyBase* pProp, MM::ActionType pAct) -{ - if (pAct == MM::BeforeGet) { - QueryCommand(this, GetCoreCallback(), NO_SLOT, "HID?", false); - ParseforString(serialNumber_); - pProp->Set(serialNumber_.c_str()); - } - - return DEVICE_OK; -} - - -int OxxiusCombinerHub::OnInterlock(MM::PropertyBase* pProp, MM::ActionType pAct) -{ - if (pAct == MM::BeforeGet) { - QueryCommand(this, GetCoreCallback(), NO_SLOT, "INT?", false); - ParseforBoolean(interlockClosed_); - - if( interlockClosed_) { - pProp->Set("Closed"); - } else { - pProp->Set("Open"); - } - } - - return DEVICE_OK; -} - - -int OxxiusCombinerHub::OnEmissionKey(MM::PropertyBase* pProp, MM::ActionType pAct) -{ - if (pAct == MM::BeforeGet) { - QueryCommand(this, GetCoreCallback(), NO_SLOT, "KEY?", false); - ParseforBoolean(keyActivated_); - - if( keyActivated_) { - pProp->Set("Armed"); - } else { - pProp->Set("Disarmed"); - } - } - - return DEVICE_OK; -} - - -/////////////////////////////////////////////////////////////////////////////// -// Generic methods -/////////////////////////////////////////////////////////////////////////////// - -void OxxiusCombinerHub::LogError(int id, MM::Device* device, MM::Core* core, const char* functionName) //prinnt log messages -{ - std::ostringstream os; - char deviceName[MM::MaxStrLength]; - device->GetName(deviceName); - os << "Error " << id << ", " << deviceName << ", " << functionName << endl; - core->LogMessage(device, os.str().c_str(), false); -} - - -/** - * Sends a serial command to a given slot, then stores the result in the receive buffer. - */ -int OxxiusCombinerHub::QueryCommand(MM::Device* device, MM::Core* core, const unsigned int destinationSlot, const char* command, bool adco) -{ - // First check: if the command string is empty, do nothing and return "DEVICE_OK" - if( strcmp(command, "") == 0) return DEVICE_OK; - - char rcvBuf_[RCV_BUF_LENGTH]; - // Compose the command to be sent to the combiner - std::string strCommand, strHZIn, strHZOut; - strCommand.assign(g_slotPrefix[destinationSlot]); - strCommand.append(command); - strHZIn.assign(g_slotPrefix[destinationSlot]); - strHZIn.append("HZ 9876"); - strHZOut.assign(g_slotPrefix[destinationSlot]); - strHZOut.append("HZ 0"); - -/* - std::ostringstream InfoMessage; - InfoMessage << "Now sending command :"; - InfoMessage << string(strCommand.c_str()); - LogError(DEVICE_OK, device, core, InfoMessage.str().c_str()); -*/ - std::ostringstream InfoMessage2; - InfoMessage2 << "Send: " << command << " Received: "; - - // Preambule for specific commands - if (adco) { - int ret = core->SetSerialCommand(device, port_.c_str(), strHZIn.c_str(), "\r\n"); - ret = core->GetSerialAnswer(device, port_.c_str(), RCV_BUF_LENGTH, rcvBuf_, "\r\n"); - if (ret != DEVICE_OK) { - LogError(ret, device, core, "QueryCommand-SetSerialCommand - preambule"); - return ret; - } - } - - // Send command through the serial interface - int ret = core->SetSerialCommand(device, port_.c_str(), strCommand.c_str(), "\r\n"); - if (ret != DEVICE_OK) { - LogError(ret, device, core, "QueryCommand-SetSerialCommand"); - return ret; - } - - // Get a response - ret = core->GetSerialAnswer(device, port_.c_str(), RCV_BUF_LENGTH, rcvBuf_, "\r\n"); - - InfoMessage2 << rcvBuf_ ; - /* DEBUG ONLY */ - // LogError(DEVICE_OK, device, core, InfoMessage2.str().c_str()); - - if (ret != DEVICE_OK) { - LogError(ret, device, core, "QueryCommand-GetSerialAnswer"); - - // Keep on trying until we either get our answer, or 3 seconds have passed - int maxTimeMs = 3000; - // Wait for a (increasing) delay between each try - int delayMs = 10; - // Keep track of how often we tried - int counter = 1; - bool done = false; - MM::MMTime startTime (core->GetCurrentMMTime()); // Let's keep in mind that MMTime is counted in microseconds - - while (!done) { - counter++; - ret = core->GetSerialAnswer(device, port_.c_str(), RCV_BUF_LENGTH, rcvBuf_, "\r\n"); - if ((ret == DEVICE_OK) || - ((core->GetCurrentMMTime() - startTime) > - MM::MMTime::fromMs(maxTimeMs))) { - done = true; - } else { - CDeviceUtils::SleepMs(delayMs); - delayMs *= 2; - } - } - ostringstream os; - if (ret == DEVICE_OK) - os << "QueryCommand-GetSerialAnswer: Succeeded reading from serial port after trying " << counter << "times."; - else - os << "QueryCommand-GetSerialAnswer: Failed reading from serial port after trying " << counter << "times."; - - core->LogMessage(device, os.str().c_str(), true); - - serialAnswer_.assign(rcvBuf_); - return ret; - } - serialAnswer_.assign(rcvBuf_); - ret = core->PurgeSerial(device, port_.c_str()); - -/* if( strcmp(serialAnswer_, "timeout") == 0) { - std::ostringstream syntaxErrorMessage; - syntaxErrorMessage << "Time out received against sent command '"; - syntaxErrorMessage << string(strCommand.c_str()); - syntaxErrorMessage << "'"; - - LogError(DEVICE_SERIAL_TIMEOUT, device, core, syntaxErrorMessage.str().c_str()); - return DEVICE_SERIAL_TIMEOUT; - } -*/ - // Epilogue for specific commands - if (adco) { - int ret = core->SetSerialCommand(device, port_.c_str(), strHZOut.c_str(), "\r\n"); - ret = core->GetSerialAnswer(device, port_.c_str(), RCV_BUF_LENGTH, rcvBuf_, "\r\n"); - if (ret != DEVICE_OK) { - LogError(ret, device, core, "QueryCommand-SetSerialCommand - Epilogue"); - return ret; - } - } - - return DEVICE_OK; -} - - -int OxxiusCombinerHub::ParseforBoolean(bool &Bval) -{ - unsigned int intAnswer = (unsigned int)atoi(serialAnswer_.c_str()); - Bval = (intAnswer == 1); - - serialAnswer_.clear(); - - return DEVICE_OK; -} - - -int OxxiusCombinerHub::ParseforFloat(float &Dval) -{ - Dval = (float) atof(serialAnswer_.c_str()); - - serialAnswer_.clear(); - - return DEVICE_OK; -} - - -int OxxiusCombinerHub::ParseforInteger(unsigned int &Ival) -{ - Ival = (unsigned int)atoi(serialAnswer_.c_str()); - - serialAnswer_.clear(); - - return DEVICE_OK; -} - - -int OxxiusCombinerHub::ParseforString(std::string &Sval) -{ - Sval.assign(serialAnswer_); - - serialAnswer_.clear(); - - return DEVICE_OK; -} - - -int OxxiusCombinerHub::ParseforVersion(unsigned int &Vval) //cast the string into a comparable int -{ - std::string temp1; - std::string temp2(serialAnswer_); - - for (unsigned int i = 0; i <= (temp2.length())-1; i++) { - if (temp2.at(i) != '.') { - temp1 += temp2.at(i); - } - } - - stringstream s(temp1); - s >> Vval; - serialAnswer_.clear(); - - return DEVICE_OK; -} - - -int OxxiusCombinerHub::ParseforPercent(double &Pval) //cast the string into a comparable int -{ - std::string percentage; - std::size_t found; - - percentage.assign(serialAnswer_); - - found = percentage.find("%"); - if (found != std::string::npos) { - Pval = atof(percentage.substr(0, found).c_str()); - } - - return DEVICE_OK; -} - - - -int OxxiusCombinerHub::ParseforChar(char* Nval) -{ - strcpy(Nval,serialAnswer_.c_str()); - serialAnswer_.clear(); - - return DEVICE_OK; -} - -bool OxxiusCombinerHub::GetAOMpos1(unsigned int slot) -{ - bool res = false; - - if (slot == AOM1pos_) { - res = true; - } - - return res; -} - -bool OxxiusCombinerHub::GetAOMpos2(unsigned int slot) -{ - bool res = false; - - if (slot == AOM2pos_) { - res = true; - } - - return res; -} - -bool OxxiusCombinerHub::GetMPA(unsigned int slot) { - bool res = false; - - if (mpa[slot] == 1) { - res = true; - } - - return res; -} - -int OxxiusCombinerHub::GetObPos() { - return obPos_; -} - - -/////////////////////////////////////////////////////////////////////////////// -// -// Oxxius generic LaserBoxx implementation -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -/////////////////////////////////////////////////////////////////////////////// - -/*std::ostringstream InfoMessageB; //////////// test out of hub - InfoMessageB << "testB" << "-" << slot_; - LogMessage(InfoMessageB.str().c_str(), false);*/ - -OxxiusLaserBoxx::OxxiusLaserBoxx(const char* nameAndSlot) : initialized_(false) -{ - std::string tSlot = string(nameAndSlot); - - name_.assign(tSlot);// set laser name - tSlot = tSlot.substr(tSlot.length()-1, 1); - slot_ = (unsigned int)atoi(tSlot.c_str());// set laser slot - - parentHub_ ; - busy_ = false; - laserOn_ = false; - alarm_ = ""; - state_ = ""; - digitalMod_ = ""; - analogMod_ = ""; - controlMode_ = ""; - - // powerSetPoint_ = 0.0; - maxRelPower_ = 0.0; - nominalPower_ = 0.0; - maxCurrent_ = 125.0; - - InitializeDefaultErrorMessages(); - SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); - - // parent ID display - CreateHubIDProperty(); -} - - -OxxiusLaserBoxx::~OxxiusLaserBoxx() -{ - Shutdown(); -} - - -void OxxiusLaserBoxx::GetName(char* Name) const -{ - CDeviceUtils::CopyLimitedString(Name, name_.c_str()); -} - - -int OxxiusLaserBoxx::Initialize() -{ - if (!initialized_) { - - std::size_t found; - std::string strSlot; - std::string spa; - - - parentHub_ = static_cast(GetParentHub()); - if (!parentHub_ ) { - return DEVICE_COMM_HUB_MISSING; - } - char hubLabel[MM::MaxStrLength]; - parentHub_->GetLabel(hubLabel); - SetParentID(hubLabel); // for backward compatibility - - // Set property list - // ----------------- - // Name (read only) - RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_Name, name_.c_str(), MM::String, true) ); - - // Description (read only) - std::ostringstream descriPt1; - char sourceSerialNumber[] = "LAS-XXXXXXXXXX"; - parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "HID?", false); - parentHub_->ParseforChar(sourceSerialNumber); - - parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "IP", true); - parentHub_->ParseforString(spa); - - parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "INF?", false); - parentHub_->ParseforString(strSlot); - - // Retrieves and define the laser's type - found = strSlot.find("-"); - if (found != std::string::npos) { - type = strSlot.substr(0, found); - } - strSlot.erase(0, found + 1); - - // Retrieves and define the laser's wavelength - found = strSlot.find("-"); - if (found != std::string::npos) { - waveLength = (unsigned int)atoi(strSlot.substr(0, found).c_str()); - - } - - // Retrieves and define the nominal power - strSlot.erase(0, found + 1); - nominalPower_ = (float) atof(strSlot.substr(0, found).c_str()); - - //set laser AOM if needed - // model[0] model[1] - // (major) (minor) - // 1 0 -> standard LBX - // 1 1n -> LBX linked to mpa number n - // 2 0 -> standard LCX - // 2 1 -> LCX linked to a AOM number 1 - // 2 2 -> LCX linked to a AOM number 2 - // 2 5 -> LCX with power adjustment - // 2 1n -> LCX linked to mpa number n - - - if (parentHub_->GetMPA(slot_)) { - model_[1] = 10 + slot_; - } else { - model_[1] = 0; - } - - if (type.compare("LBX") == 0) { // The source is a LBX - model_[0] = 1; - } - else if (type.compare("LCX") == 0) { // The source is a LCX - model_[0] = 2; - if (parentHub_->GetAOMpos1(slot_)) { //laser has AMO1 - model_[1] = 1; - } - if (parentHub_->GetAOMpos2(slot_)){ //laser has AMO2 - model_[1] = 2; - } - else if (spa != "????") { //self modulating lcx - model_[1] = 5; - } - } - else { // Should not happen: unkown type - model_[0] = 9; - model_[1] = 9; - } - - switch (model_[0]) { - case 1: // LBX model - descriPt1 << "LBX"; - break; - case 2: // LCX model - descriPt1 << "LCX"; - break; - default: // Should not happen - descriPt1 << "Unknown"; - break; - } - descriPt1 << " source on slot " << slot_; - descriPt1 << ", " << sourceSerialNumber; - - RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_Description, descriPt1.str().c_str(), MM::String, true) ); - - // Alarm (read only) - CPropertyAction* pAct = new CPropertyAction (this, &OxxiusLaserBoxx::OnAlarm); - RETURN_ON_MM_ERROR( CreateProperty("Alarm", "None", MM::String, true, pAct) ); - - // Status (read only) - pAct = new CPropertyAction (this, &OxxiusLaserBoxx::OnState); - RETURN_ON_MM_ERROR( CreateProperty("State", "", MM::String, true, pAct) ); - - // Emission selector (write/read) - pAct = new CPropertyAction (this, &OxxiusLaserBoxx::OnEmissionOnOff); - RETURN_ON_MM_ERROR( CreateProperty("Emission", "", MM::String, false, pAct) ); - AddAllowedValue("Emission", "ON"); - AddAllowedValue("Emission", "OFF"); - - // Digital modulation selector (write/read) - pAct = new CPropertyAction (this, &OxxiusLaserBoxx::OnDigitalMod); - RETURN_ON_MM_ERROR( CreateProperty("Digital Modulation", "", MM::String, false, pAct) ); - AddAllowedValue("Digital Modulation", "ON"); - AddAllowedValue("Digital Modulation", "OFF"); - - // Analog modulation selector (write/read) - pAct = new CPropertyAction (this, &OxxiusLaserBoxx::OnAnalogMod); - RETURN_ON_MM_ERROR( CreateProperty("Analog Modulation", "", MM::String, false, pAct) ); - AddAllowedValue("Analog Modulation", "ON"); - AddAllowedValue("Analog Modulation", "OFF"); - - // Control mode selector (= APC or ACC) (write/read) - pAct = new CPropertyAction (this, &OxxiusLaserBoxx::OnControlMode); - RETURN_ON_MM_ERROR( CreateProperty("Control mode", "", MM::String, false, pAct) ); - AddAllowedValue("Control mode", "ACC"); - AddAllowedValue("Control mode", "APC"); - - - - // Retrieves and define the laser's maximal power - std::string maxpowCmd = "DL SPM"; - switch (model_[0]) { - case 1: // LBX model - switch (model_[1]) { - case 0: // Standard LBX - parentHub_->QueryCommand(this, GetCoreCallback(), slot_, maxpowCmd.c_str(), true); - parentHub_->ParseforFloat(maxRelPower_); - maxRelPower_ = 100 * maxRelPower_ / nominalPower_; - break; - default: // LBX + MPA - maxRelPower_ = 100.0; - break; - } - break; - case 2: // LCX model - maxRelPower_ = 100.0; - break; - default: // Should not happen - maxRelPower_ = 0.0; - break; - } - - - // Retrieves and define the laser's maximal current - maxCurrent_ = 125.0; - - - // Power set point (write/read) - pAct = new CPropertyAction (this, &OxxiusLaserBoxx::OnPowerSetPoint); - RETURN_ON_MM_ERROR( CreateProperty("Power set point", "0", MM::Float, false, pAct) ); - SetPropertyLimits("Power set point", 0, maxRelPower_); - - // Power set point (write/read) - pAct = new CPropertyAction (this, &OxxiusLaserBoxx::OnCurrentSetPoint); - RETURN_ON_MM_ERROR( CreateProperty("Current set point", "0", MM::Float, false, pAct) ); - SetPropertyLimits("Current set point", 0, maxCurrent_); - - RETURN_ON_MM_ERROR( UpdateStatus() ); - - initialized_ = true; - } - - return DEVICE_OK; -} - - - -int OxxiusLaserBoxx::Shutdown() -{ - initialized_ = false; - return DEVICE_OK; -} - - -bool OxxiusLaserBoxx::Busy() -{ - return busy_; -} - - -int OxxiusLaserBoxx::SetOpen(bool openCommand) -{ - laserOn_ = openCommand; - return DEVICE_OK; -} - - -int OxxiusLaserBoxx::GetOpen(bool& isOpen) -{ - isOpen = laserOn_; - return DEVICE_OK; -} - - -/////////////////////////////////////////////////////////////////////////////// -// Action handlers -/////////////////////////////////////////////////////////////////////////////// - -int OxxiusLaserBoxx::OnAlarm(MM::PropertyBase* pProp, MM::ActionType) -{ - unsigned int alarmInt = 99; - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "?F", false) ); - - parentHub_->ParseforInteger(alarmInt); - - switch (alarmInt) { - case 0: - alarm_ = "No Alarm"; - break; - case 1: - alarm_ = "Out-of-bounds diode current"; - break; - case 2: - alarm_ = "Unexpected laser power value"; - break; - case 3: - alarm_ = "Out-of-bounds supply voltage"; - break; - case 4: - alarm_ = "Out-of-bounds internal temperature"; - break; - case 5: - alarm_ = "Out-of-bounds baseplate temperature"; - break; - case 7: - alarm_ = "Interlock circuit open"; - break; - case 8: - alarm_ = "Soft reset"; - break; - default: - alarm_ = "Other alarm"; - } - - pProp->Set(alarm_.c_str()); - - return DEVICE_OK; -} - - -int OxxiusLaserBoxx::OnState(MM::PropertyBase* pProp, MM::ActionType) -{ - unsigned int stateInt = 99; - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), slot_, "?STA", false) ); - - parentHub_->ParseforInteger(stateInt); - - switch (stateInt) { - case 1: - state_ = "Warm-up phase"; - break; - case 2: - state_ = "Stand-by state"; - break; - case 3: - state_ = "Emission on"; - break; - case 4: - state_ = "Internal error"; - break; - case 5: - state_ = "Alarm"; - break; - case 6: - state_ = "Sleep state"; - break; - default: - state_ = "Other state"; - } - - pProp->Set(alarm_.c_str()); - - return DEVICE_OK; -} - - -int OxxiusLaserBoxx::OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (eAct == MM::BeforeGet) { - unsigned int status = 0; - std::ostringstream query; - - query << "?CS " << slot_; - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, query.str().c_str(), false) ); - parentHub_->ParseforInteger(status); - - switch (status) { - case 0: // LBX model: Emission off - case 2: // LCX model: shutter closed - laserOn_ = false; - break; - case 1: // LBX model: Emission on - case 3: // LCX model: shutter open - laserOn_ = true; - break; - default: - laserOn_ = true; - } - - if (laserOn_) { - pProp->Set("ON"); - } else { - pProp->Set("OFF"); - } - } - else if (eAct == MM::AfterSet) { - std::string newEmissionStatus, newCommand = ""; - - pProp->Get(newEmissionStatus); - - if( newEmissionStatus.compare("ON") == 0 ) { - newCommand = "DL 1"; - laserOn_ = true; - } else if ( newEmissionStatus.compare("OFF") == 0 ) { - newCommand = "DL 0"; - laserOn_ = false; - } - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.c_str(), false) ); - } - return DEVICE_OK; -} - - -int OxxiusLaserBoxx::OnDigitalMod(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - unsigned int querySlot = NO_SLOT; - - if (eAct == MM::BeforeGet) { - std::string query; - - // The slot and command depend on the model - switch (model_[0]) { - case 1: // LBX model: retreiving the modulation status - querySlot = slot_; - query = "?TTL"; - break; - case 2: // LCX model: retreiving the modulation status - querySlot = NO_SLOT; - switch (model_[1]) { - case 1: - query = "AOM1 TTL"; - break; - case 2: - query = "AOM2 TTL"; - break; - default: - query = ""; - } - break; - default: // Should not happen - return DEVICE_OK; - } - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, query.c_str(), false) ); - - bool digiM; - parentHub_->ParseforBoolean(digiM); - - if (digiM) - digitalMod_.assign("ON"); - else - digitalMod_.assign("OFF"); - - pProp->Set(digitalMod_.c_str()); - - } else if (eAct == MM::AfterSet) { - std::string newModSet, newCommand; - - pProp->Get(newModSet); - digitalMod_.assign(newModSet); - - if (digitalMod_ == "ON") { - switch (model_[0]) { - case 1: // LBX model: setting the modulation on - querySlot = slot_; - newCommand.assign("TTL 1"); - break; - case 2: // LCX model: setting the modulation on - querySlot = NO_SLOT; - - switch (model_[1]) { - case 1: - newCommand.assign("AOM1 TTL 1"); - break; - case 2: - newCommand.assign("AOM2 TTL 1"); - break; - default: - newCommand.assign(""); - } - break; - default: // Should not happen - return DEVICE_OK; - } - - } else if (digitalMod_ == "OFF") { - switch (model_[0]) { - case 1: // LBX model: setting the modulation off - querySlot = slot_; - newCommand.assign("TTL 0"); - break; - case 2: // LCX model: setting the modulation off - querySlot = NO_SLOT; - switch (model_[1]) { - case 1: - newCommand.assign("AOM1 TTL 0"); - break; - case 2: - newCommand.assign("AOM2 TTL 0"); - break; - default: - newCommand.assign(""); - } - break; - default: // Should not happen - return DEVICE_OK; - } - } - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, newCommand.c_str(), false) ); - } - return DEVICE_OK; -} - - -int OxxiusLaserBoxx::OnAnalogMod(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - unsigned int querySlot = NO_SLOT; - - if (eAct == MM::BeforeGet) { - std::string query; - - // The slot and command depend on the model - switch (model_[0]) { - case 1: // LBX model: retreiving the modulation state - querySlot = slot_; - query = "?AM"; - break; - case 2: // LCX model: retreiving the modulation state - querySlot = NO_SLOT; - switch (model_[1]) { - case 1: - query = "AOM1 AM"; - break; - case 2: - query = "AOM2 AM"; - break; - default: - query = ""; - } - break; - default: // Should not happen - return DEVICE_OK; - } - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, query.c_str(), false) ); - bool digiM; - parentHub_->ParseforBoolean(digiM); - - if (digiM) { - digitalMod_.assign("ON"); - } else { - digitalMod_.assign("OFF"); - } - pProp->Set(digitalMod_.c_str()); - - } else if (eAct == MM::AfterSet) { - std::ostringstream newCommand; - - - - switch (model_[0]) { - case 1: // LBX model: setting the modulation on - querySlot = slot_; - newCommand << "AM "; - break; - case 2: // LCX model: setting the modulation on - querySlot = NO_SLOT; - switch (model_[1]) { - case 1: - case 2: - newCommand << "AOM" << model_[1] << " AM "; - break; - default: // No modulation without a modulator - return DEVICE_OK; - } - break; - default: // Should not happen - return DEVICE_OK; - } - - pProp->Get(digitalMod_); - - if (digitalMod_ == "OFF") - newCommand << "0"; - else - newCommand << "1"; - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), querySlot, newCommand.str().c_str(), false) ); - } - return DEVICE_OK; -} - - -int OxxiusLaserBoxx::OnControlMode(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (eAct == MM::BeforeGet) { - unsigned int ctrlM = 1; - std::string command; - - // The slot and command depend on the model - switch (model_[0]) { - case 1: // LBX model - command = "?APC"; - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), slot_, command.c_str(), false)); - parentHub_->ParseforInteger(ctrlM); - - break; - - case 2: // LCX model -> always in APC - ctrlM = 1; - break; - - default:; // Should not happen - } - - if (ctrlM == 1) { - controlMode_.assign("APC"); - } else if (ctrlM == 0) { - controlMode_.assign("ACC"); - } - - pProp->Set(controlMode_.c_str()); - } - else if (eAct == MM::AfterSet) { - std::string newCommand; - - pProp->Get(controlMode_); - - switch (model_[0]) { - case 1: // LBX model - if (controlMode_ == "ACC") { - newCommand.assign("APC 0"); - } else if (controlMode_ == "APC") { - newCommand.assign("APC 1"); - } - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), slot_, newCommand.c_str(), false) ); - - break; - - case 2: // LCX model -> always in APC - controlMode_ = "APC"; - pProp->Set(controlMode_.c_str()); - break; - - default:; // Should not happen - } - } - return DEVICE_OK; -} - - -int OxxiusLaserBoxx::OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (eAct == MM::BeforeGet) { - std::string command = "?SP"; - unsigned int thisSlot = slot_; - float absSetPoint_; - - if ((10 < model_[1]) && (model_[1] < 17)) { - thisSlot = NO_SLOT; - command = "?PL"; - stringstream s; - s << (model_[1] - 10); - command += s.str(); - } - else if (model_[0] == 2) { - switch (model_[1]) { - case 1: // LCX on AOM1 - thisSlot = NO_SLOT; - command = "?SP1"; - break; - - case 2: // LCX on AOM2 - thisSlot = NO_SLOT; - command = "?SP2"; - break; - default: // LCX without AOM: identical to LBX polling - break; - } - } - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), thisSlot, command.c_str(), false) ); - parentHub_->ParseforFloat(absSetPoint_); - - pProp->Set( (100 * absSetPoint_) / nominalPower_ ); - } - else if (eAct == MM::AfterSet) { - - double GUISetPoint = 0.0; - pProp->Get(GUISetPoint); - - if( (GUISetPoint >= 0.0)||(GUISetPoint <= maxRelPower_) ) { - std::string command = "P"; - unsigned int thisSlot = slot_; - - std::ostringstream newCommand; - char * powerSPString = new char[20]; - strcpy(powerSPString , CDeviceUtils::ConvertToString( (GUISetPoint * nominalPower_) / 100 )); - - if ((10 < model_[1]) && (model_[1] < 17)) { - thisSlot = NO_SLOT; - command = "IP"; - command += CDeviceUtils::ConvertToString((int)(model_[1] - 10)); - - strcpy(powerSPString , CDeviceUtils::ConvertToString(GUISetPoint) ); - } - else if (model_[0] == 2) { - switch (model_[1]) { - case 1: // LCX on AOM1 - thisSlot = NO_SLOT; - command = "P"; - break; - case 2: // LCX on AOM2 - thisSlot = NO_SLOT; - command = "P2"; - break; - case 5: // LCX with power adjustment - command = "IP"; - strcpy(powerSPString , CDeviceUtils::ConvertToString(GUISetPoint) ); - break; - default: - break; - } - } - newCommand << command << " " << powerSPString; - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), thisSlot, newCommand.str().c_str(), false) ); - } else { - // If the value entered through the GUI is not valid, read the machine value - OnPowerSetPoint(pProp,MM::BeforeGet); - } - } - return DEVICE_OK; -} - - - -int OxxiusLaserBoxx::OnCurrentSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (eAct == MM::BeforeGet) { - float machineSetPoint = 0.0; - std::string command = "?SC"; - unsigned int thisSlot = slot_; - - if (model_[0] == 1) { // Current modification only allowed on LBX models - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), thisSlot, command.c_str(), false) ); - parentHub_->ParseforFloat(machineSetPoint); - } - - pProp->Set( machineSetPoint ); - } - - else if (eAct == MM::AfterSet) { - - double GUISetPoint = 0.0; - pProp->Get(GUISetPoint); - - if( (GUISetPoint >= 0.0)||(GUISetPoint <= maxCurrent_) ) { - - std::ostringstream newCommand; - std::string command = "C"; - unsigned int thisSlot = slot_; - - char * currentSPString = new char[20]; - strcpy(currentSPString , CDeviceUtils::ConvertToString(GUISetPoint) ); - - if (model_[0] == 1) { // Current modification only allowed on LBX models - newCommand << command << " " << currentSPString; - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), thisSlot, newCommand.str().c_str(), false) ); - } - } - } - return DEVICE_OK; -} - - - - -/////////////////////////////////////////////////////////////////////////////// -// -// Oxxius shutter implementation -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -/////////////////////////////////////////////////////////////////////////////// - -OxxiusShutter::OxxiusShutter(const char* nameAndSlot) : initialized_(false) -{ - isOpen_ = false; - parentHub_ = 0; - - name_.assign(nameAndSlot); - - std::string strChnl = string(nameAndSlot); - strChnl = strChnl.substr(strChnl.length()-1, 1); - channel_ = (unsigned int) atoi(strChnl.c_str()); - - // Set property list - // ----------------- - // Name (read only) - CreateProperty(MM::g_Keyword_Name, name_.c_str(), MM::String, true); - - std::ostringstream shutterDesc; - shutterDesc << "Electro-mechanical shutter on channel " << channel_ << "."; - CreateProperty(MM::g_Keyword_Description, shutterDesc.str().c_str(), MM::String, true); - - InitializeDefaultErrorMessages(); - SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); - - // parent ID display - CreateHubIDProperty(); -} - - -OxxiusShutter::~OxxiusShutter() -{ - Shutdown(); -} - - -int OxxiusShutter::Initialize() -{ - if (!initialized_) { - parentHub_ = static_cast(GetParentHub()); - if (!parentHub_ ) { - return DEVICE_COMM_HUB_MISSING; - } - char hubLabel[MM::MaxStrLength]; - parentHub_->GetLabel(hubLabel); - SetParentID(hubLabel); // for backward compatibility - - // Set property list - // ----------------- - - // Open/Close selector (write/read) - CPropertyAction* pAct = new CPropertyAction (this, &OxxiusShutter::OnState); - RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_State, "", MM::String, false, pAct) ); - AddAllowedValue(MM::g_Keyword_State, "Open"); - AddAllowedValue(MM::g_Keyword_State, "Closed"); - - // Closing the shutter on Initialization - RETURN_ON_MM_ERROR( SetProperty(MM::g_Keyword_State, "Closed") ); - - RETURN_ON_MM_ERROR( UpdateStatus() ); - - initialized_ = true; - } - - return DEVICE_OK; -} - - -int OxxiusShutter::Shutdown() -{ - initialized_ = false; - return DEVICE_OK; -} - - -void OxxiusShutter::GetName(char* Name) const -{ - CDeviceUtils::CopyLimitedString(Name, name_.c_str()); -} - - -bool OxxiusShutter::Busy() -{ - return false; -} - - -int OxxiusShutter::SetOpen(bool openCommand) -{ - if (openCommand) - return SetProperty(MM::g_Keyword_State, "Open"); - else - return SetProperty(MM::g_Keyword_State, "Closed"); -} - - -int OxxiusShutter::GetOpen(bool& isOpen) -{ - isOpen = isOpen_; - return DEVICE_OK; -} - - -int OxxiusShutter::OnState(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (eAct == MM::BeforeGet) { - if (isOpen_) { - pProp->Set("Open"); - } else { - pProp->Set("Closed"); - } - } - else if (eAct == MM::AfterSet) { - - std::string newState = ""; - pProp->Get(newState); - - std::ostringstream newCommand; - newCommand << "SH" << channel_ << " "; - - if( newState.compare("Open") == 0 ) { - newCommand << "1"; - isOpen_ = true; - } else if ( newState.compare("Closed") == 0 ) { - newCommand << "0"; - isOpen_ = false; - } - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, newCommand.str().c_str(), false)); - } - return DEVICE_OK; -} - - - -/////////////////////////////////////////////////////////////////////////////// -// -// Oxxius M-Dual implementation -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -/////////////////////////////////////////////////////////////////////////////// - -OxxiusMDual::OxxiusMDual(const char* nameAndSlot) : initialized_(false) -{ - parentHub_ = 0; - core_ = GetCoreCallback(); - - std::string tSlot = string(nameAndSlot); - name_.assign(tSlot); // sets MDual name - - slot_ = tSlot.substr(tSlot.length() - 1, 1); - - // Set property list - // ----------------- - // Name (read only) - CreateProperty(MM::g_Keyword_Name, name_.c_str(), MM::String, true); - - CreateProperty(MM::g_Keyword_Description, "M-Dual module", MM::String, true); - - InitializeDefaultErrorMessages(); - SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); - - // parent ID display - CreateHubIDProperty(); -} - - -OxxiusMDual::~OxxiusMDual() -{ - Shutdown(); -} - - -int OxxiusMDual::Initialize() -{ - if (!initialized_) { - parentHub_ = static_cast(GetParentHub()); - if (!parentHub_ ) { - return DEVICE_COMM_HUB_MISSING; - } - char hubLabel[MM::MaxStrLength]; - parentHub_->GetLabel(hubLabel); - SetParentID(hubLabel); // for backward compatibility - - - // Set property list - // ----------------- - CPropertyAction* pAct = new CPropertyAction(this, &OxxiusMDual::OnSetRatio);//setting the possible positions - RETURN_ON_MM_ERROR(CreateProperty("Split ratio", "0", MM::Float, false, pAct)); - SetPropertyLimits("Split ratio", 0.0, 100.0); - - // Set property list - // ----------------- - // State - /*CPropertyAction* pAct = new CPropertyAction (this, &OxxiusMDual::OnState); - RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_State, "0", MM::Integer, false, pAct) ); - SetPropertyLimits("Set Position", 0, 100);*/ - - /*char pos[3]; - for (unsigned int i=0; iQueryCommand(this, GetCoreCallback(), NO_SLOT, command.str().c_str()) ); - parentHub_->ParseforInteger(currentPos); - - //SetPosition(currentPos); - pProp->Set((long)currentPos); - } - else if (eAct == MM::AfterSet) { - long newPosition = 0; - - //GetPosition(newPosition); - pProp->Get(newPosition); - - std::ostringstream newCommand; - newCommand << "IP" << slot_ << " " << newPosition; - - RETURN_ON_MM_ERROR( parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, newCommand.str().c_str()) ); - } - return DEVICE_OK; -}*/ - - -int OxxiusMDual::OnSetRatio(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (eAct == MM::BeforeGet) { - double currentRatio = 0.0; - std::ostringstream command; - command << "IP" << slot_; - - RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, command.str().c_str(), true)); - parentHub_->ParseforPercent(currentRatio); - - pProp->Set(currentRatio); - } - - else if (eAct == MM::AfterSet) { - double newRatio = 0.0; - - pProp->Get(newRatio); - if( (newRatio >= 0.0) || (newRatio <= 100.0) ) { - std::ostringstream newCommand; - newCommand << "IP" << slot_ << " " << newRatio; - - RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, newCommand.str().c_str(), true)); - } - } - return DEVICE_OK; -} - -/////////////////////////////////////////////////////////////////////////////// -// -// Oxxius Flip-Mirror implementation -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -/////////////////////////////////////////////////////////////////////////////// - -OxxiusFlipMirror::OxxiusFlipMirror(const char* nameAndSlot) : initialized_(false) -{ - parentHub_ = 0; - core_ = GetCoreCallback(); - - std::string fSlot = string(nameAndSlot); - nameF_.assign(fSlot);// set laser name - fSlot = fSlot.substr(fSlot.length() - 1, 1); - slot_ = (unsigned int)atoi(fSlot.c_str());// set laser slot - - // Set property list ///////////////////////////////////////////////////////////////////////////////////////////////////// NOT WORKING? (duplicate property name Name(4)) - // ----------------- - // Name (read only) - /*CreateProperty(MM::g_Keyword_Name, nameF_.c_str(), MM::String, true); - - CreateProperty(MM::g_Keyword_Description, "Flip-Mirror module", MM::String, true); - - InitializeDefaultErrorMessages(); - SetErrorText(ERR_NO_PORT_SET, "Hub Device not found. The Laser combiner is needed to create this device"); - - // parent ID display - CreateHubIDProperty();*/ -} - - -OxxiusFlipMirror::~OxxiusFlipMirror() -{ - Shutdown(); -} - - -int OxxiusFlipMirror::Initialize() -{ - if (!initialized_) { - parentHub_ = static_cast(GetParentHub()); - if (!parentHub_) { - return DEVICE_COMM_HUB_MISSING; - } - char hubLabel[MM::MaxStrLength]; - parentHub_->GetLabel(hubLabel); - SetParentID(hubLabel); // for backward compatibility - - CPropertyAction* pAct = new CPropertyAction(this, &OxxiusFlipMirror::OnSwitchPos);//setting the possible positions - RETURN_ON_MM_ERROR(CreateProperty("Switch Position", "0", MM::Integer, false, pAct)); - SetPropertyLimits("Switch Position", 0, 1); - - std::ostringstream descriPt2; - descriPt2 << ""; - RETURN_ON_MM_ERROR(CreateProperty(MM::g_Keyword_Description, descriPt2.str().c_str(), MM::String, true)); - - // Gate, or "closed" position -// RETURN_ON_MM_ERROR( CreateProperty(MM::g_Keyword_Closed_Position, "0", MM::String, false) ); - -// isOpen_ = false; // MDual closed posisiton is - - RETURN_ON_MM_ERROR(UpdateStatus()); - - initialized_ = true; - } - - return DEVICE_OK; -} - - -int OxxiusFlipMirror::Shutdown() -{ - initialized_ = false; - return DEVICE_OK; -} - - -void OxxiusFlipMirror::GetName(char* Name) const -{ - CDeviceUtils::CopyLimitedString(Name, nameF_.c_str()); -} - - -bool OxxiusFlipMirror::Busy() -{ - return false; -} - - -int OxxiusFlipMirror::OnSwitchPos(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (eAct == MM::BeforeGet) { - unsigned int currentPos = 0; - std::ostringstream command; - command << "FM" << slot_; - - RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, command.str().c_str(), false)); - parentHub_->ParseforInteger(currentPos); - - //SetPosition(currentPos); - pProp->Set((long)currentPos); - } - else if (eAct == MM::AfterSet) { - long newPosition = 0; - - //GetPosition(newPosition); - pProp->Get(newPosition); - - std::ostringstream newCommand; - newCommand << "FM" << slot_ << " " << newPosition; - - RETURN_ON_MM_ERROR(parentHub_->QueryCommand(this, GetCoreCallback(), NO_SLOT, newCommand.str().c_str(), false)); - } - return DEVICE_OK; -} diff --git a/DeviceAdapters/OxxiusCombiner/Oxxius_combiner.h b/DeviceAdapters/OxxiusCombiner/Oxxius_combiner.h deleted file mode 100644 index e31480a1a..000000000 --- a/DeviceAdapters/OxxiusCombiner/Oxxius_combiner.h +++ /dev/null @@ -1,349 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// FILE: OxxiusCombiner.h -// PROJECT: Micro-Manager -// SUBSYSTEM: DeviceAdapters -//----------------------------------------------------------------------------- -// DESCRIPTION: Controls Oxxius combiners through a serial interface -// COPYRIGHT: Oxxius SA, 2013-2018 -// LICENSE: LGPL -// AUTHOR: Tristan Martinez -// - -#ifndef _OXXIUS_COMBINER_H_ -#define _OXXIUS_COMBINER_H_ - -#include "MMDevice.h" -#include "DeviceBase.h" -#include "ModuleInterface.h" -#include -#include -#include - - -////////////////////////////////////////////////////////////////////////////// -// Error codes -// -#define ERR_PORT_CHANGE_FORBIDDEN 101 -#define ERR_NO_PORT_SET 102 -#define ERR_COMBINER_NOT_FOUND 201 -#define ERR_UNSUPPORTED_VERSION 202 - -////////////////////////////////////////////////////////////////////////////// -// Miscellaneous definitions -// -// Use the name 'return_value' that is unlikely to appear within 'result'. -#define RETURN_ON_MM_ERROR( result ) do { \ - int return_value = (result); \ - if (return_value != DEVICE_OK) { \ - return return_value; \ - } \ -} while (0) - - -////////////////////////////////////////////////////////////////////////////// -// Defining device adaptaters -// - -class OxxiusCombinerHub: public HubBase -{ -public: - OxxiusCombinerHub(); - ~OxxiusCombinerHub(); - - // MMDevice API - // ------------ - int Initialize(); - int Shutdown(); - - void GetName(char* pszName) const; - bool Busy(); - int DetectInstalledDevices(); - unsigned int GetNumberOfInstalledDevices() {return installedDevices_;}; -// MM::DeviceDetectionStatus DetectDevice(void); - bool SupportsDeviceDetection(void) {return true;}; - - - // Property handlers - int OnPort(MM::PropertyBase* pPropt, MM::ActionType eAct); - int OnSerialNumber(MM::PropertyBase* pPropt, MM::ActionType eAct); - int OnInterlock(MM::PropertyBase* pPropt, MM::ActionType eAct); - int OnEmissionKey(MM::PropertyBase* pPropt, MM::ActionType eAct); - - // Custom interface for child devices -// bool IsPortAvailable() {return portAvailable_;} - int QueryCommand(MM::Device* device, MM::Core* core, const unsigned int destinationSlot, const char* command, bool adco); - int ParseforBoolean(bool &destBoolean); - int ParseforFloat(float &destFloat); - int ParseforInteger(unsigned int &destInteger); - int ParseforString(std::string &destString); - int ParseforVersion(unsigned int &Vval); - int ParseforPercent(double &Pval); - int ParseforChar(char* Nval); - // int TempAdminInt(const char* com); - // int TempAdminString(int com, std::string &res); - - bool GetAOMpos1(unsigned int slot); - bool GetAOMpos2(unsigned int slot); - bool GetMPA(unsigned int slot); - int GetObPos(); - -private: - void LogError(int id, MM::Device* device, MM::Core* core, const char* functionName); - - std::string port_; - bool initialized_; - unsigned int installedDevices_; - - std::string serialAnswer_; - - std::string serialNumber_; - bool interlockClosed_; - bool keyActivated_; - - unsigned int AOM1pos_; - unsigned int AOM2pos_; - unsigned int mpa[7]; - unsigned int obPos_; -}; - -////////////////////////////////////////////////////////////////////////////// -// -// Device adaptaters for "LaserBoxx" source -// -////////////////////////////////////////////////////////////////////////////// - -class OxxiusLaserBoxx: public CShutterBase -{ -public: - OxxiusLaserBoxx(const char* nameAndSlot); - ~OxxiusLaserBoxx(); - - // MMDevice API - // ------------ - int Initialize(); - int Shutdown(); - - void GetName(char* pszName) const; - bool Busy(); - - int SetOpen(bool openCommand = true); - int GetOpen(bool& isOpen); - int Fire(double /*deltaT*/) { return DEVICE_UNSUPPORTED_COMMAND; } -// int LaserOnOff(int); - - // Action interface - // ---------------- -// int OnPower(MM::PropertyBase* pProp, MM::ActionType eAct); -// int OnCurrent(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnCurrentSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct); - - int OnControlMode(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnAnalogMod(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnDigitalMod(MM::PropertyBase* pProp, MM::ActionType eAct); - - int OnState(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnAlarm(MM::PropertyBase* pProp, MM::ActionType eAct); -// int OnHours(MM::PropertyBase* pProp, MM::ActionType eAct); - - unsigned int aomp1; - unsigned int aomp2; - -private: - std::string name_; - unsigned int slot_; - unsigned int model_[2]; - OxxiusCombinerHub* parentHub_; - bool initialized_; - bool busy_; - - // double powerSetPoint_; - // double currentSetPoint_; - float maxRelPower_; - float nominalPower_; - float maxCurrent_; - unsigned int waveLength; - std::string type; - - - bool laserOn_; - std::string state_; - std::string alarm_; -// std::string serialNumber_; -// std::string softVersion_; - std::string controlMode_; - std::string analogMod_; - std::string digitalMod_; -}; - -////////////////////////////////////////////////////////////////////////////// -// -// Device adaptaters for "Obis" source in Combiner -// -////////////////////////////////////////////////////////////////////////////// -/* -class OxxiusObisSupport : public CShutterBase -{ -public: - OxxiusObisSupport(const char* nameAndSlot); - ~OxxiusObisSupport(); - - // MMDevice API - // ------------ - int Initialize(); - int Shutdown(); - - void GetName(char* pszName) const; - bool Busy(); - - int SetOpen(bool openCommand = true); - int GetOpen(bool& isOpen); - int Fire(double ) { return DEVICE_UNSUPPORTED_COMMAND; } - // int LaserOnOff(int); - - // Action interface - // ---------------- - // int OnPower(MM::PropertyBase* pProp, MM::ActionType eAct); - // int OnCurrent(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnPowerSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnCurrentSetPoint(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnEmissionOnOff(MM::PropertyBase* pProp, MM::ActionType eAct); - - int OnControlMode(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnAnalogMod(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnDigitalMod(MM::PropertyBase* pProp, MM::ActionType eAct); - - int OnState(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnAlarm(MM::PropertyBase* pProp, MM::ActionType eAct); - // int OnHours(MM::PropertyBase* pProp, MM::ActionType eAct); - - unsigned int aomp1; - unsigned int aomp2; - -private: - std::string name_; - unsigned int slot_; - unsigned int model_[2]; - OxxiusCombinerHub* parentHub_; - bool initialized_; - bool busy_; - - double powerSetPoint_; - double currentSetPoint_; - unsigned int maxPower_; - unsigned int waveLength; - std::string type; - // double maxCurrent_; - - - bool laserOn_; - std::string state_; - std::string alarm_; - // std::string serialNumber_; - // std::string softVersion_; - std::string controlMode_; - std::string analogMod_; - std::string digitalMod_; -}; -*/ -////////////////////////////////////////////////////////////////////////////// -// -// Device adaptaters for "shutter" source in Combiner -// -////////////////////////////////////////////////////////////////////////////// - -class OxxiusShutter: public CShutterBase -{ -public: - OxxiusShutter(const char* nameAndChannel); - ~OxxiusShutter(); - - // MMDevice API - // ------------ - int Initialize(); - int Shutdown(); - - void GetName(char* pszName) const; - bool Busy(); - - // Shutter API - int SetOpen(bool openCommand = true); - int GetOpen(bool& isOpen); - int Fire(double /*deltaT*/) { return DEVICE_UNSUPPORTED_COMMAND; } - - // Action Interface - // ---------------- - int OnState(MM::PropertyBase* pProp, MM::ActionType eAct); - -private: - bool initialized_; - std::string name_; - - OxxiusCombinerHub* parentHub_; - bool isOpen_; - unsigned int channel_; - - MM::MMTime changedTime_; -}; - - -class OxxiusMDual: public CGenericBase -{ -public: - OxxiusMDual(const char* name); - ~OxxiusMDual(); - - // MMDevice API - // ------------ - int Initialize(); - int Shutdown(); - - void GetName(char* pszName) const; - bool Busy(); - - // Action Interface - // ---------------- - int OnSetRatio(MM::PropertyBase* pProp, MM::ActionType eAct); - -private: - bool initialized_; - std::string name_; - std::string slot_; - MM::Core* core_; - - OxxiusCombinerHub* parentHub_; -}; - - -class OxxiusFlipMirror : public CGenericBase -{ -public: - OxxiusFlipMirror(const char* name); - ~OxxiusFlipMirror(); - - // MMDevice API - // ------------ - int Initialize(); - int Shutdown(); - - void GetName(char* pszName) const; - bool Busy(); - //unsigned long GetNumberOfPositions()const { return numPos_; } - - // Action Interface - // ---------------- - int OnSwitchPos(MM::PropertyBase* pProp, MM::ActionType eAct); - - -private: - bool initialized_; - std::string nameF_; - unsigned int slot_; - MM::Core* core_; - - OxxiusCombinerHub* parentHub_; - - unsigned long numPos_; -}; -#endif // _OXXIUS_COMBINER_H_ \ No newline at end of file From c36577224edf2ae29511fb56aed6f0185af9cf70 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 25 Sep 2023 15:42:10 -0500 Subject: [PATCH 002/141] OxxiusCombiner: Clean up #includes Remove FixSnprintf.h, which is no longer available or needed. Include only the necessary headers from MMDevice, and let the build system set the path. --- DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp | 3 +++ DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.h | 7 ++----- DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.cpp | 1 - DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.h | 4 +--- DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp | 1 - DeviceAdapters/OxxiusCombiner/OxxiusMDual.h | 4 +--- DeviceAdapters/OxxiusCombiner/OxxiusShutter.cpp | 1 - DeviceAdapters/OxxiusCombiner/OxxiusShutter.h | 4 +--- 8 files changed, 8 insertions(+), 17 deletions(-) diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp index 73872ee42..9f6f3f9cf 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp +++ b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp @@ -7,6 +7,9 @@ #include "OxxiusLCX.h" #include "CoherentObis.h" #include "Cobolt08_01.h" + +#include "ModuleInterface.h" + using namespace std; diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.h b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.h index de99211c7..117001914 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.h +++ b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.h @@ -1,7 +1,5 @@ #pragma once -#include "../../MMDevice/MMDevice.h" -#include "../../MMDevice/DeviceBase.h" -#include "../../MMDevice/ModuleInterface.h" +#include "DeviceBase.h" #include #include #include @@ -18,8 +16,7 @@ using namespace std; #ifdef WIN32 #include #endif -#include "../../MMDevice/DeviceUtils.h" -#include "FixSnprintf.h" +#include "DeviceUtils.h" #include #include // diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.cpp index cddb613e8..1cd2a3345 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.cpp +++ b/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.cpp @@ -4,7 +4,6 @@ #include #include #include -#include "../../MMDevice/ModuleInterface.h" using namespace std; diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.h b/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.h index 250e5e3fb..80d781372 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.h +++ b/DeviceAdapters/OxxiusCombiner/OxxiusFlipMirror.h @@ -2,9 +2,7 @@ #include "OxxiusCombinerHub.h" -#include "../../MMDevice/MMDevice.h" -#include "../../MMDevice/DeviceBase.h" -#include "../../MMDevice/ModuleInterface.h" +#include "DeviceBase.h" #include #include #include diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp index cb4b5ab36..7360b3a11 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp +++ b/DeviceAdapters/OxxiusCombiner/OxxiusMDual.cpp @@ -4,7 +4,6 @@ #include #include #include -#include "../../MMDevice/ModuleInterface.h" using namespace std; /////////////////////////////////////////////////////////////////////////////// diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusMDual.h b/DeviceAdapters/OxxiusCombiner/OxxiusMDual.h index b610a44ad..e3a769de8 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusMDual.h +++ b/DeviceAdapters/OxxiusCombiner/OxxiusMDual.h @@ -2,9 +2,7 @@ #include "OxxiusCombinerHub.h" -#include "../../MMDevice/MMDevice.h" -#include "../../MMDevice/DeviceBase.h" -#include "../../MMDevice/ModuleInterface.h" +#include "DeviceBase.h" #include #include #include diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusShutter.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusShutter.cpp index ffad899cc..b985b1db7 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusShutter.cpp +++ b/DeviceAdapters/OxxiusCombiner/OxxiusShutter.cpp @@ -4,7 +4,6 @@ #include #include #include -#include "../../MMDevice/ModuleInterface.h" using namespace std; /////////////////////////////////////////////////////////////////////////////// diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusShutter.h b/DeviceAdapters/OxxiusCombiner/OxxiusShutter.h index e637e98c9..8ce63db4b 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusShutter.h +++ b/DeviceAdapters/OxxiusCombiner/OxxiusShutter.h @@ -2,9 +2,7 @@ #include "OxxiusCombinerHub.h" -#include "../../MMDevice/MMDevice.h" -#include "../../MMDevice/DeviceBase.h" -#include "../../MMDevice/ModuleInterface.h" +#include "DeviceBase.h" #include #include #include From 936fac9320c980bb2db6b671043d2011eb749930 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 25 Sep 2023 15:43:22 -0500 Subject: [PATCH 003/141] OxxiusCombiner: Update MMTime usage Required to compile with current MMDevice. --- DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp index 9f6f3f9cf..107c38b35 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp +++ b/DeviceAdapters/OxxiusCombiner/OxxiusCombinerHub.cpp @@ -585,12 +585,12 @@ int OxxiusCombinerHub::QueryCommand(MM::Device* device, MM::Core* core, const un // Keep track of how often we tried int counter = 1; bool done = false; - MM::MMTime startTime(core->GetCurrentMMTime()); // Let's keep in mind that MMTime is counted in microseconds + MM::MMTime startTime(GetCurrentMMTime()); while (!done) { counter++; ret = core->GetSerialAnswer(device, port_.c_str(), RCV_BUF_LENGTH, rcvBuf_, "\r\n"); - if ((ret == DEVICE_OK) || ((core->GetCurrentMMTime() - startTime) > (maxTimeMs * 1000.0))) + if ((ret == DEVICE_OK) || ((GetCurrentMMTime() - startTime) > MM::MMTime::fromMs(maxTimeMs))) done = true; else { CDeviceUtils::SleepMs(delayMs); From 43c1ac0e311c44592545e696e6e6e59ffac67469 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 25 Sep 2023 15:44:16 -0500 Subject: [PATCH 004/141] OxxiusCombiner: Add LGPL license --- DeviceAdapters/OxxiusCombiner/license.txt | 142 ++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 DeviceAdapters/OxxiusCombiner/license.txt diff --git a/DeviceAdapters/OxxiusCombiner/license.txt b/DeviceAdapters/OxxiusCombiner/license.txt new file mode 100644 index 000000000..56b20e458 --- /dev/null +++ b/DeviceAdapters/OxxiusCombiner/license.txt @@ -0,0 +1,142 @@ +GNU Lesser General Public License + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. + + (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + + In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. + + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS \ No newline at end of file From dda128915ba9b8614995860b2d38f28c7b0e9c50 Mon Sep 17 00:00:00 2001 From: Martin Zak Date: Thu, 28 Sep 2023 15:30:43 -0700 Subject: [PATCH 005/141] Fixing objective changer. --- DeviceAdapters/Zaber/ObjectiveChanger.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/DeviceAdapters/Zaber/ObjectiveChanger.cpp b/DeviceAdapters/Zaber/ObjectiveChanger.cpp index 96c266a18..92e13cb82 100644 --- a/DeviceAdapters/Zaber/ObjectiveChanger.cpp +++ b/DeviceAdapters/Zaber/ObjectiveChanger.cpp @@ -188,8 +188,8 @@ int ObjectiveChanger::GetPositionLabel(long pos, char* label) const { if (DEVICE_OK != CStateDeviceBase::GetPositionLabel(pos, label)) { - std::stringstream labelStr("Objective "); - labelStr << pos + 1; + std::stringstream labelStr; + labelStr << "Objective " << pos + 1; CDeviceUtils::CopyLimitedString(label, labelStr.str().c_str()); } @@ -312,17 +312,15 @@ int ObjectiveChanger::PositionGetSet(MM::PropertyBase* pProp, MM::ActionType eAc long indexToSet; pProp->Get(indexToSet); - if (initialized_) + if (initialized_ && (indexToSet + 1) != currentObjective_) { - if ((indexToSet >= 0) && (indexToSet < numPositions_)) - { - return setObjective(indexToSet + 1, false); - } - else + if (indexToSet < 0 || indexToSet >= numPositions_) { this->LogMessage("Requested position is outside the legal range.\n", true); return DEVICE_UNKNOWN_POSITION; } + + return setObjective(indexToSet + 1, false); } } @@ -335,12 +333,17 @@ int ObjectiveChanger::FocusOffsetGetSet(MM::PropertyBase* pProp, MM::ActionType if (eAct == MM::AfterSet) { - pProp->Get(focusOffset_); + double newOffset; + pProp->Get(newOffset); + auto update = focusOffset_ != newOffset; + focusOffset_ = newOffset; + if (update) { + return setObjective(currentObjective_, true); + } } else if (eAct == MM::BeforeGet) { pProp->Set(focusOffset_); - return setObjective(currentObjective_, true); } return DEVICE_OK; From ea98223089ca4b3b0050778bc10932b8c32feba2 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 3 Oct 2023 15:28:09 -0500 Subject: [PATCH 006/141] Ship latest Andor ALC DLL - Update AndorLaserCombiner's build.xml to ship DLLs from 3rdparty. - Remove the DLLs from this repository (as we generally want to avoid DLLs in our source repository). This points to a new version of AB_ALC_REV64.dll that should work for both AndorLaserCombiner and AndorILE (IntegratedLaserEngine). So keeping the build.xml in just AndorLaserCombiner is not logically correct, but let's leave it this way for now for simplicity. --- DeviceAdapters/AndorLaserCombiner/build.xml | 9 +++------ .../AndorLaserCombiner/lib/AB_ALC_REV.dll | Bin 215552 -> 0 bytes .../AndorLaserCombiner/lib/AB_ALC_REV64.dll | Bin 445440 -> 0 bytes .../AndorLaserCombiner/lib/usbi2cio64.dll | Bin 60944 -> 0 bytes 4 files changed, 3 insertions(+), 6 deletions(-) delete mode 100644 DeviceAdapters/AndorLaserCombiner/lib/AB_ALC_REV.dll delete mode 100644 DeviceAdapters/AndorLaserCombiner/lib/AB_ALC_REV64.dll delete mode 100644 DeviceAdapters/AndorLaserCombiner/lib/usbi2cio64.dll diff --git a/DeviceAdapters/AndorLaserCombiner/build.xml b/DeviceAdapters/AndorLaserCombiner/build.xml index 4c5d324cc..8e0fe08fe 100644 --- a/DeviceAdapters/AndorLaserCombiner/build.xml +++ b/DeviceAdapters/AndorLaserCombiner/build.xml @@ -1,13 +1,10 @@ - - - - - + - + diff --git a/DeviceAdapters/AndorLaserCombiner/lib/AB_ALC_REV.dll b/DeviceAdapters/AndorLaserCombiner/lib/AB_ALC_REV.dll deleted file mode 100644 index f48fc1f93def85642681aaca2eddf8dd670b3647..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215552 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 z 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 Date: Thu, 22 Jun 2023 12:45:12 +0200 Subject: [PATCH 007/141] First prototype implementation Changes: - Image snapping - Live stream from camera - Device adapter base - Camera listing - Property page (read only) - SDK wrapper and lib loader Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 537 ++++ .../AlliedVisionCamera/AlliedVisionCamera.h | 132 + .../AlliedVisionCamera.vcxproj | 183 ++ .../AlliedVisionCamera.vcxproj.filters | 54 + .../AlliedVisionCamera/SDK/Loader/Constants.h | 27 + .../SDK/Loader/LibLoader.cpp | 81 + .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 143 ++ .../AlliedVisionCamera/SDK/VmbC/VmbC.h | 2182 +++++++++++++++++ .../SDK/VmbC/VmbCTypeDefinitions.h | 610 +++++ .../SDK/VmbC/VmbCommonTypes.h | 444 ++++ .../SDK/VmbC/VmbConstants.h | 85 + .../SDK/VmbImageTransform/VmbTransform.h | 336 +++ .../SDK/VmbImageTransform/VmbTransformTypes.h | 1018 ++++++++ micromanager.sln | 6 + 14 files changed, 5838 insertions(+) create mode 100644 DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp create mode 100644 DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h create mode 100644 DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj create mode 100644 DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters create mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h create mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp create mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h create mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbC.h create mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCTypeDefinitions.h create mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCommonTypes.h create mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbConstants.h create mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransform.h create mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransformTypes.h diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp new file mode 100644 index 000000000..3df9abb8e --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -0,0 +1,537 @@ +/*============================================================================= + Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ +#include "AlliedVisionCamera.h" + +#include + +#include +#include +#include +#include + +#include "ModuleInterface.h" +#include "VmbC/VmbC.h" + +/////////////////////////////////////////////////////////////////////////////// +// Exported MMDevice API +/////////////////////////////////////////////////////////////////////////////// +MODULE_API void InitializeModuleData() { + g_api = std::make_unique(); + assert(g_api != nullptr); + auto err = g_api->VmbStartup_t(nullptr); + assert(err == VmbErrorSuccess); + + err = AlliedVisionCamera::getCamerasList(); + if (err != VmbErrorSuccess) { + // TODO Handle error + } + + g_api->VmbShutdown_t(); +} + +MODULE_API MM::Device* CreateDevice(const char* deviceName) { + if (deviceName == nullptr) { + return nullptr; + } + + return new AlliedVisionCamera(deviceName); +} + +MODULE_API void DeleteDevice(MM::Device* pDevice) { delete pDevice; } + +/////////////////////////////////////////////////////////////////////////////// +// AlliedVisionCamera +/////////////////////////////////////////////////////////////////////////////// + +AlliedVisionCamera::~AlliedVisionCamera() { + m_handle = nullptr; + + for (size_t i = 0; i < MAX_FRAMES; i++) { + delete[] m_buffer[i]; + } +} + +AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) + : CCameraBase(), + m_handle{nullptr}, + m_cameraName{deviceName}, + m_frames{}, + m_buffer{}, + m_bufferSize{0}, + m_imageWidth{}, + m_imageHeight{}, + m_isAcquisitionRunning{false} { + // [Rule] Create properties here (pre-init only) + InitializeDefaultErrorMessages(); + setApiErrorMessages(); +} + +int AlliedVisionCamera::Initialize() { + // [Rule] Implement communication here + LogMessage("Initializing Vimba X API..."); + VmbError_t err = g_api->VmbStartup_t(nullptr); + if (err != VmbErrorSuccess) { + return err; + } + + VmbVersionInfo_t ver; + err = g_api->VmbVersionQuery_t(&ver, sizeof(ver)); + if (err != VmbErrorSuccess) { + return err; + } + std::string v = std::to_string(ver.major) + "." + std::to_string(ver.minor) + + "." + std::to_string(ver.patch); + LogMessage("SDK version:" + v); + + LogMessage("Opening camera: " + m_cameraName); + err = g_api->VmbCameraOpen_t(m_cameraName.c_str(), + VmbAccessModeType::VmbAccessModeFull, &m_handle); + if (err != VmbErrorSuccess || m_handle == nullptr) { + return err; + } + + // Init properties and buffer + setupProperties(); + resizeImageBuffer(); + + return DEVICE_OK; +} + +int AlliedVisionCamera::Shutdown() { + // [Rule] Implement disconnection here + LogMessage("Shutting down camera: " + m_cameraName); + if (m_handle != nullptr) { + VmbError_t err = g_api->VmbCameraClose_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + } + + g_api->VmbShutdown_t(); + return DEVICE_OK; +} + +const unsigned char* AlliedVisionCamera::GetImageBuffer() { + return reinterpret_cast(m_buffer[0]); +} + +unsigned AlliedVisionCamera::GetImageWidth() const { return m_imageWidth; } + +unsigned AlliedVisionCamera::GetImageHeight() const { return m_imageHeight; } + +unsigned AlliedVisionCamera::GetImageBytesPerPixel() const { + // TODO implement + return 1; +} + +int AlliedVisionCamera::SnapImage() { + if (m_isAcquisitionRunning) { + return DEVICE_CAMERA_BUSY_ACQUIRING; + } + resizeImageBuffer(); + + VmbFrame_t frame; + frame.buffer = m_buffer[0]; + frame.bufferSize = m_bufferSize; + + VmbError_t err = + g_api->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbCaptureStart_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbCaptureFrameWait_t(m_handle, &frame, 3000); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbCaptureEnd_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbCaptureQueueFlush_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbFrameRevokeAll_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + return err; +} + +long AlliedVisionCamera::GetImageBufferSize() const { return m_bufferSize; } +unsigned AlliedVisionCamera::GetBitDepth() const { + // TODO implement + return 8; +} +int AlliedVisionCamera::GetBinning() const { + // TODO implement + return 1; +} +int AlliedVisionCamera::SetBinning(int binSize) { + // TODO implement + return VmbErrorSuccess; +} +void AlliedVisionCamera::SetExposure(double exp_ms) { + // TODO implement +} +double AlliedVisionCamera::GetExposure() const { + // TODO implement + return 8058.96; +} +int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, + unsigned ySize) { + // TODO implement + return VmbErrorSuccess; +} +int AlliedVisionCamera::GetROI(unsigned& x, unsigned& y, unsigned& xSize, + unsigned& ySize) { + // TODO implement + return VmbErrorSuccess; +} +int AlliedVisionCamera::ClearROI() { + // TODO implement + return 0; +} +int AlliedVisionCamera::IsExposureSequenceable(bool& isSequenceable) const { + // TODO implement + return VmbErrorSuccess; +} +void AlliedVisionCamera::GetName(char* name) const { + CDeviceUtils::CopyLimitedString(name, m_cameraName.c_str()); +} +bool AlliedVisionCamera::IsCapturing() { return m_isAcquisitionRunning; } + +void AlliedVisionCamera::setApiErrorMessages() { + SetErrorText(VmbErrorApiNotStarted, "Vimba X API not started"); + SetErrorText(VmbErrorNotFound, "Device cannot be found"); + SetErrorText(VmbErrorDeviceNotOpen, "Device cannot be opened"); + SetErrorText(VmbErrorBadParameter, + "Invalid parameter passed to the function"); + SetErrorText(VmbErrorNotImplemented, "Feature not implemented"); + SetErrorText(VmbErrorNotSupported, "Feature not supported"); + SetErrorText(VmbErrorUnknown, "Unknown error"); +} + +VmbError_t AlliedVisionCamera::getCamerasList() { + VmbUint32_t camNum; + + // Get the number of connected cameras first + VmbError_t err = g_api->VmbCamerasList_t(nullptr, 0, &camNum, 0); + if (VmbErrorSuccess == err) { + VmbCameraInfo_t* camInfo = new VmbCameraInfo_t[camNum]; + + // Get the cameras + err = g_api->VmbCamerasList_t(camInfo, camNum, &camNum, sizeof *camInfo); + + if (err == VmbErrorSuccess) { + for (VmbUint32_t i = 0; i < camNum; ++i) { + RegisterDevice(camInfo[i].cameraIdString, MM::CameraDevice, + camInfo[i].cameraName); + } + } + + delete[] camInfo; + } + + return err; +} + +VmbError_t AlliedVisionCamera::resizeImageBuffer() { + auto err = g_api->VmbFeatureIntGet_t(m_handle, "Width", &m_imageWidth); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbFeatureIntGet_t(m_handle, "Height", &m_imageHeight); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbPayloadSizeGet_t(m_handle, &m_bufferSize); + if (err != VmbErrorSuccess) { + return err; + } + + for (size_t i = 0; i < MAX_FRAMES; i++) { + delete[] m_buffer[i]; + m_buffer[i] = new VmbUint8_t[m_bufferSize]; + } + + return VmbErrorSuccess; +} + +int AlliedVisionCamera::OnPixelTypeChanged(MM::PropertyBase* pProp, + MM::ActionType eAct) { + // TODO implement + resizeImageBuffer(); + return 0; +} + +int AlliedVisionCamera::OnBinningChanged(MM::PropertyBase* pProp, + MM::ActionType eAct) { + // TODO implement + resizeImageBuffer(); + return 0; +} + +VmbError_t AlliedVisionCamera::createPropertyFromFeature( + const VmbFeatureInfo_t* feature) { + // TODO + // Implemnet onProperyChanged for some properties and buffer resize + // Implement readOnly/WriteOnly reading + if (feature == nullptr) { + return VmbErrorInvalidValue; + } + + VmbError_t err = VmbErrorSuccess; + switch (feature->featureDataType) { + case VmbFeatureDataBool: { + VmbBool_t value; + err = g_api->VmbFeatureBoolGet_t(m_handle, feature->name, &value); + if (VmbErrorSuccess == err) { + CreateIntegerProperty(feature->name, value, true, nullptr); + } + break; + } + case VmbFeatureDataEnum: { + char const* value = nullptr; + err = g_api->VmbFeatureEnumGet_t(m_handle, feature->name, &value); + if (VmbErrorSuccess == err) { + CreateStringProperty(feature->name, value, true, nullptr); + } + break; + } + case VmbFeatureDataFloat: { + double value; + err = g_api->VmbFeatureFloatGet_t(m_handle, feature->name, &value); + if (err == VmbErrorSuccess) { + CreateFloatProperty(feature->name, value, true, nullptr); + } + break; + } + case VmbFeatureDataInt: { + VmbInt64_t value; + err = g_api->VmbFeatureIntGet_t(m_handle, feature->name, &value); + if (err == VmbErrorSuccess) { + CreateIntegerProperty(feature->name, value, true, nullptr); + } + break; + } + case VmbFeatureDataString: { + VmbUint32_t size = 0; + err = g_api->VmbFeatureStringGet_t(m_handle, feature->name, nullptr, 0, + &size); + if (VmbErrorSuccess == err && size > 0) { + std::shared_ptr buff = std::shared_ptr(new char[size]); + err = g_api->VmbFeatureStringGet_t(m_handle, feature->name, buff.get(), + size, &size); + if (VmbErrorSuccess == err) { + CreateStringProperty(feature->name, buff.get(), true, nullptr); + } + } + break; + } + case VmbFeatureDataCommand: + case VmbFeatureDataUnknown: + case VmbFeatureDataRaw: + case VmbFeatureDataNone: + default: + err = VmbErrorFeaturesUnavailable; + break; + } + + return err; +} + +VmbError_t AlliedVisionCamera::setupProperties() { + VmbUint32_t featureCount = 0; + VmbError_t err = g_api->VmbFeaturesList_t(m_handle, NULL, 0, &featureCount, + sizeof(VmbFeatureInfo_t)); + if (err != VmbErrorSuccess || !featureCount) { + return err; + } + + std::shared_ptr features = + std::shared_ptr(new VmbFeatureInfo_t[featureCount]); + + err = g_api->VmbFeaturesList_t(m_handle, features.get(), featureCount, + &featureCount, sizeof(VmbFeatureInfo_t)); + + if (err != VmbErrorSuccess) { + return err; + } + + const VmbFeatureInfo_t* end = features.get() + featureCount; + for (VmbFeatureInfo_t* feature = features.get(); feature != end; ++feature) { + std::stringstream ss; + ss << "/// Feature Name: " << feature->name << "\n"; + ss << "/// Display Name: " << feature->displayName << "\n"; + ss << "/// Tooltip: " << feature->tooltip << "\n"; + ss << "/// Description: " << feature->description << "\n"; + ss << "/// SNFC Namespace: " << feature->sfncNamespace << "\n"; + LogMessage(ss.str().c_str()); + createPropertyFromFeature(feature); + } +} + +int AlliedVisionCamera::StartSequenceAcquisition(long numImages, + double interval_ms, + bool stopOnOverflow) { + if (m_isAcquisitionRunning) { + return DEVICE_CAMERA_BUSY_ACQUIRING; + } + + int err = GetCoreCallback()->PrepareForAcq(this); + if (err != DEVICE_OK) { + return err; + } + + err = resizeImageBuffer(); + if (err != VmbErrorSuccess) { + return err; + } + + for (size_t i = 0; i < MAX_FRAMES; i++) { + // Setup frame with buffer + m_frames[i].buffer = new uint8_t[m_bufferSize]; + m_frames[i].bufferSize = m_bufferSize; + m_frames[i].context[0] = this; //(i); //VmbFrameAnnounce_t(m_handle, &(m_frames[i]), sizeof(VmbFrame_t)); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbCaptureFrameQueue_t( + m_handle, &(m_frames[i]), + [](const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, + VmbFrame_t* frame) { + reinterpret_cast(frame->context[0]) + ->insertFrame(frame); + }); + if (err != VmbErrorSuccess) { + return err; + } + } + + err = g_api->VmbCaptureStart_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); + if (err != VmbErrorSuccess) { + return err; + } + + m_isAcquisitionRunning = true; + return err; +} + +int AlliedVisionCamera::StartSequenceAcquisition(double interval_ms) { + return StartSequenceAcquisition(LONG_MAX, interval_ms, true); +} +int AlliedVisionCamera::StopSequenceAcquisition() { + if (m_isAcquisitionRunning) { + auto err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); + if (err != VmbErrorSuccess) { + return err; + } + + m_isAcquisitionRunning = false; + } + + auto err = g_api->VmbCaptureEnd_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbCaptureQueueFlush_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbFrameRevokeAll_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + return err; +} + +void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { + if (frame != nullptr && frame->receiveStatus == VmbFrameStatusComplete) { + VmbUint8_t* buffer = reinterpret_cast(frame->buffer); + + // TODO implement metadata + Metadata md; + md.put("Camera", m_cameraName); + + // TODO implement parameters + auto err = GetCoreCallback()->InsertImage(this, buffer, m_imageWidth, + m_imageHeight, 1, 1, + md.Serialize().c_str()); + + if (err == DEVICE_BUFFER_OVERFLOW) { + GetCoreCallback()->ClearImageBuffer(this); + // TODO implement parameters + err = GetCoreCallback()->InsertImage(this, buffer, frame->width, + frame->height, 1, 1, + md.Serialize().c_str(), false); + } + + if (m_isAcquisitionRunning) { + g_api->VmbCaptureFrameQueue_t( + m_handle, frame, + [](const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, + VmbFrame_t* frame) { + reinterpret_cast(frame->context[0]) + ->insertFrame(frame); + }); + } + } +} \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h new file mode 100644 index 000000000..a6b208606 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -0,0 +1,132 @@ +/*============================================================================= + Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ +#ifndef ALLIEDVISIONCAMERA_H +#define ALLIEDVISIONCAMERA_H + +#include + +#include "DeviceBase.h" +#include "Loader/LibLoader.h" + +/** + * @brief Pointer to the Vimba API +*/ +static std::unique_ptr g_api; + +/** + * @brief Main Allied Vision Camera class +*/ +class AlliedVisionCamera : public CCameraBase { + // PUBLIC + public: + /** + * @brief Contructor of Allied Vision Camera + * @param[in] deviceName Device name + */ + AlliedVisionCamera(const char* deviceName); + /** + * @brief Allied Vision Camera destructor + */ + ~AlliedVisionCamera(); + + /** + * @brief Get connected camera list + * @return VmbError_t + */ + static VmbError_t getCamerasList(); + + // API Methods + int Initialize() override; + int Shutdown() override; + const unsigned char* GetImageBuffer() override; + unsigned GetImageWidth() const override; + unsigned GetImageHeight() const override; + unsigned GetImageBytesPerPixel() const override; + int SnapImage() override; + long GetImageBufferSize() const override; + unsigned GetBitDepth() const override; + int GetBinning() const override; + int SetBinning(int binSize) override; + void SetExposure(double exp_ms) override; + double GetExposure() const override; + int SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) override; + int GetROI(unsigned& x, unsigned& y, unsigned& xSize, + unsigned& ySize) override; + int ClearROI() override; + int IsExposureSequenceable(bool& isSequenceable) const override; + void GetName(char* name) const override; + int StartSequenceAcquisition(double interval_ms) override; + int StartSequenceAcquisition(long numImages, double interval_ms, + bool stopOnOverflow) override; + int StopSequenceAcquisition() override; + bool IsCapturing() override; + + // Callbacks + int OnPixelTypeChanged(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnBinningChanged(MM::PropertyBase* pProp, MM::ActionType eAct); + + // Static variables + static constexpr const VmbUint8_t MAX_FRAMES = 7; + + // PRIVATE + private: + /** + * @brief Setup error messages for Vimba API + */ + void setApiErrorMessages(); + + /** + * @brief Resize all buffers for image frames + * @return VmbError_t + */ + VmbError_t resizeImageBuffer(); + + /** + * @brief Setup uManager properties from Vimba features + * @return VmbError_t + */ + VmbError_t setupProperties(); + + /** + * @brief Helper method to create single uManager property from Vimba feature + * @param[in] feature Pointer to the Vimba feature + * @return VmbError_t + */ + VmbError_t createPropertyFromFeature(const VmbFeatureInfo_t* feature); + + /** + * @brief Insert ready frame to the uManager + * @param[in] frame Pointer to the frame + */ + void insertFrame(VmbFrame_t* frame); + + // MEMBERS + VmbHandle_t m_handle; // m_frames; // m_buffer; // + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + {b8c95f39-54bf-40a9-807b-598df2821d55} + + + + 16.0 + Win32Proj + {12e75cb0-4b48-4d7c-bb26-d928f18488c2} + AlliedVisionCamera + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + C:\Program Files\Micro-Manager-2.0 + + + false + + + + Level3 + true + WIN32;_DEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + true + _DEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + $(ProjectDir)SDK;%(AdditionalIncludeDirectories) + + + Windows + true + false + + + + + true + true + true + NDEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + $(ProjectDir)SDK;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + false + + + + + + \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters new file mode 100644 index 000000000..03328d086 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters @@ -0,0 +1,54 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h new file mode 100644 index 000000000..929d01061 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h @@ -0,0 +1,27 @@ +/*============================================================================= + Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ +#ifndef CONSTANTS_H +#define CONSTANTS_H + +static constexpr const char* VIMBA_X_ENV_VAR = "VIMBA_X_HOME"; // + +#include + +#include "VmbC/VmbC.h" + +/** + * @brief Wrapper for single Windows process + */ +class ProcWrapper { + // PUBLIC + public: + /** + * @brief Constructor of wrapper + * @param[in] funPtr Pointer to the process + */ + explicit ProcWrapper(FARPROC funPtr) : m_funPtr(funPtr) {} + + /** + * @brief Operator () overload to call function + */ + template >> + operator T*() const { + return reinterpret_cast(m_funPtr); + } + // PRIVATE + private: + FARPROC m_funPtr; // +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief Timeout parameter signaling a blocking call. + */ +#define VMBINFINITE 0xFFFFFFFF + +/** + * \brief Define for the handle to use for accessing the Vmb system features cast to a given type + * + * Note: The primary purpose of this macro is the use in the VmbC sources. + * API users should use ::gVmbHandle instead. + */ +#define VMB_API_HANDLE(typeName) ((typeName)((((VmbUint64_t)1) << (VmbUint64_t)(sizeof(VmbHandle_t) * 8 - 4)) | ((VmbUint64_t) 1))) + +/** + * \brief Constant for the Vmb handle to be able to access Vmb system features. + */ +static const VmbHandle_t gVmbHandle = VMB_API_HANDLE(VmbHandle_t); + +//===== FUNCTION PROTOTYPES =================================================== + +/** + * \defgroup Functions Vmb C API Functions + * \{ + */ + +/** + * \name API Version + * \defgroup Version API Version + * \{ + */ + +/** + * \brief Retrieve the version number of VmbC. + * + * This function can be called at anytime, even before the API is + * initialized. All other version numbers may be queried via feature access. + * + * \param[out] versionInfo Pointer to the struct where version information resides + * \param[in] sizeofVersionInfo Size of structure in bytes + * + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback. + * + * \retval ::VmbErrorStructSize The given struct size is not valid for this version of the API + * + * \retval ::VmbErrorBadParameter \p versionInfo is null. + * + */ +IMEXPORTC VmbError_t VMB_CALL VmbVersionQuery ( VmbVersionInfo_t* versionInfo, + VmbUint32_t sizeofVersionInfo ); +/** + * \} + */ + +/** + * \name API Initialization + * \{ + * \defgroup Init API Initialization + * \{ + */ + +/** + * \brief Initializes the VmbC API. + * + * Note: This function must be called before any VmbC function other than ::VmbVersionQuery() is run. + * + * \param[in] pathConfiguration A string containing a semicolon (Windows) or colon (other os) separated list of paths. The paths contain directories to search for .cti files, + * paths to .cti files and optionally the path to a configuration xml file. If null is passed the parameter is the cti files found in the paths + * the GENICAM_GENTL{32|64}_PATH environment variable are considered + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorAlready This function was called before and call to ::VmbShutdown has been executed on a non-callback thread + * + * \retval ::VmbErrorInvalidCall If called from a callback or ::VmbShutdown is currently running + * + * \retval ::VmbErrorXml If parsing the settings xml is unsuccessful; a missing default xml file does not result in this error. + * + * \retval ::VmbErrorTLNotFound A transport layer that was marked as required was not found. + * + * \retval ::VmbErrorNoTL No transport layer was found on the system; note that some of the transport layers may have been filtered out via the settings file. + * + * \retval ::VmbErrorIO A log file should be written according to the settings xml file, but this log file could not be opened. + * + * \retval ::VmbErrorBadParameter \p pathConfiguration contains only separator and whitespace chars. + * + */ +IMEXPORTC VmbError_t VMB_CALL VmbStartup (const VmbFilePathChar_t* pathConfiguration); + +/** + * \brief Perform a shutdown of the API. + * + * This frees some resources and deallocates all physical resources if applicable. + * + * The call is silently ignored, if executed from a callback. + * + */ +IMEXPORTC void VMB_CALL VmbShutdown ( void ); + +/** + * \} \} + */ + +/** + * \name Camera Enumeration & Information + * \{ + * \defgroup CameraInfo Camera Enumeration & Information + * \{ + */ + +/** + * List all the cameras that are currently visible to the API. + * + * Note: This function is usually called twice: once with an empty array to query the length + * of the list, and then again with an array of the correct length. + * If camera lists change between the calls, numFound may deviate from the query return. + * + * \param[in,out] cameraInfo Array of VmbCameraInfo_t, allocated by the caller. + * The camera list is copied here. May be null. + * + * \param[in] listLength Number of entries in the callers cameraInfo array. + * + * \param[in,out] numFound Number of cameras found. Can be more than listLength. + * + * \param[in] sizeofCameraInfo Size of one VmbCameraInfo_t entry (if \p cameraInfo is null, this parameter is ignored). + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p numFound is null + * + * \retval ::VmbErrorStructSize The given struct size is not valid for this API version and \p cameraInfo is not null + * + * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries + */ +IMEXPORTC VmbError_t VMB_CALL VmbCamerasList ( VmbCameraInfo_t* cameraInfo, + VmbUint32_t listLength, + VmbUint32_t* numFound, + VmbUint32_t sizeofCameraInfo ); + +/** + * \brief Retrieve information about a single camera given its handle. + * + * Note: Some information is only filled for opened cameras. + * + * \param[in] cameraHandle The handle of the camera; both remote and local device handles are permitted + * + * \param[in,out] info Structure where information will be copied + * + * \param[in] sizeofCameraInfo Size of the structure + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorStructSize The given struct size is not valid for this API version + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorBadParameter \p info is null + * + * \retval ::VmbErrorBadHandle The handle does not correspond to a camera + */ +IMEXPORTC VmbError_t VMB_CALL VmbCameraInfoQueryByHandle( VmbHandle_t cameraHandle, + VmbCameraInfo_t* info, + VmbUint32_t sizeofCameraInfo); + +/** + * \brief Retrieve information about a single camera given the ID of the camera. + * + * Note: Some information is only filled for opened cameras. + * + * \param[in] idString ID of the camera + * + * \param[in,out] info Structure where information will be copied + * + * \param[in] sizeofCameraInfo Size of the structure + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p idString or \p info are null or \p idString is the empty string + * + * \retval ::VmbErrorNotFound No camera with the given id is found + * + * \retval ::VmbErrorStructSize The given struct size is not valid for this API version + */ +IMEXPORTC VmbError_t VMB_CALL VmbCameraInfoQuery ( const char* idString, + VmbCameraInfo_t* info, + VmbUint32_t sizeofCameraInfo ); + +/** + * \brief Open the specified camera. + * + * \param[in] idString ID of the camera. + * \param[in] accessMode The desired access mode. + * \param[out] cameraHandle The remote device handle of the camera, if opened successfully. + * + * A camera may be opened in a specific access mode, which determines + * the level of control you have on a camera. + * Examples for idString: + * + * "DEV_81237473991" for an ID given by a transport layer, + * "169.254.12.13" for an IP address, + * "000F314C4BE5" for a MAC address or + * "DEV_1234567890" for an ID as reported by Vmb + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInUse The camera with the given ID is already opened + * + * \retval ::VmbErrorInvalidCall If called from frame callback or chunk access callback + * + * \retval ::VmbErrorBadParameter If \p idString or \p cameraHandle are null + * + * \retval ::VmbErrorInvalidAccess A camera with the given id was found, but could not be opened + * + * \retval ::VmbErrorNotFound The designated camera cannot be found + */ +IMEXPORTC VmbError_t VMB_CALL VmbCameraOpen ( const char* idString, + VmbAccessMode_t accessMode, + VmbHandle_t* cameraHandle ); + +/** + * \brief Close the specified camera. + * + * Depending on the access mode this camera was opened with, events are killed, + * callbacks are unregistered, and camera control is released. + * + * \param[in] cameraHandle A valid camera handle + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInUse The camera is currently in use with ::VmbChunkDataAccess + * + * \retval ::VmbErrorBadHandle The handle does not correspond to an open camera + * + * \retval ::VmbErrorInvalidCall If called from frame callback or chunk access callback + */ +IMEXPORTC VmbError_t VMB_CALL VmbCameraClose ( const VmbHandle_t cameraHandle ); + +/** + * \} \} + */ + +//----- Features ---------------------------------------------------------- + +/** + * \name General Feature Functions + * \{ + * \defgroup GeneralFeatures General Feature Functions + * \{ + */ + +/** + * \brief List all the features for this entity. + * + * This function lists all implemented features, whether they are currently available or not. + * The list of features does not change as long as the entity is connected. + * + * This function is usually called twice: once with an empty list to query the length + * of the list, and then again with a list of the correct length. + * + * If ::VmbErrorMoreData is returned and \p numFound is non-null, the total number of features has been written to \p numFound. + * + * If there are more elements in \p featureInfoList than features available, the remaining elements + * are filled with zero-initialized ::VmbFeatureInfo_t structs. + * + * \param[in] handle Handle for an entity that exposes features + * \param[out] featureInfoList An array of ::VmbFeatureInfo_t to be filled by the API. May be null if \p numFund is used for size query. + * \param[in] listLength Number of ::VmbFeatureInfo_t elements provided + * \param[out] numFound Number of ::VmbFeatureInfo_t elements found. May be null if \p featureInfoList is not null. + * \param[in] sizeofFeatureInfo Size of a ::VmbFeatureInfo_t entry + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorStructSize The given struct size of ::VmbFeatureInfo_t is not valid for this version of the API + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter Both \p featureInfoList and \p numFound are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeaturesList ( VmbHandle_t handle, + VmbFeatureInfo_t* featureInfoList, + VmbUint32_t listLength, + VmbUint32_t* numFound, + VmbUint32_t sizeofFeatureInfo ); + +/** + * \brief Query information about the constant properties of a feature. + * + * Users provide a pointer to ::VmbFeatureInfo_t, which is then set to the internal representation. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] featureInfo The feature info to query + * \param[in] sizeofFeatureInfo Size of the structure + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorStructSize The given struct size of ::VmbFeatureInfo_t is not valid for this version of the API + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name or \p featureInfo are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound A feature with the given name does not exist. + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureInfoQuery ( const VmbHandle_t handle, + const char* name, + VmbFeatureInfo_t* featureInfo, + VmbUint32_t sizeofFeatureInfo ); + +/** + * \brief List all the features selected by a given feature for this module. + * + * This function lists all selected features, whether they are currently available or not. + * Features with selected features ("selectors") have no direct impact on the camera, + * but only influence the register address that selected features point to. + * The list of features does not change while the camera/interface is connected. + * This function is usually called twice: once with an empty array to query the length + * of the list, and then again with an array of the correct length. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] featureInfoList An array of ::VmbFeatureInfo_t to be filled by the API. May be null if \p numFound is used for size query. + * \param[in] listLength Number of ::VmbFeatureInfo_t elements provided + * \param[out] numFound Number of ::VmbFeatureInfo_t elements found. May be null if \p featureInfoList is not null. + * \param[in] sizeofFeatureInfo Size of a ::VmbFeatureInfo_t entry + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorBadParameter \p name is null or both \p featureInfoList and \p numFound are null + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorStructSize The given struct size of ::VmbFeatureInfo_t is not valid for this version of the API + * + * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureListSelected ( const VmbHandle_t handle, + const char* name, + VmbFeatureInfo_t* featureInfoList, + VmbUint32_t listLength, + VmbUint32_t* numFound, + VmbUint32_t sizeofFeatureInfo ); + +/** + * \brief Return the dynamic read and write capabilities of this feature. + * + * The access mode of a feature may change. For example, if "PacketSize" + * is locked while image data is streamed, it is only readable. + * + * \param[in] handle Handle for an entity that exposes features. + * \param[in] name Name of the feature. + * \param[out] isReadable Indicates if this feature is readable. May be null. + * \param[out] isWriteable Indicates if this feature is writable. May be null. + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name is null or both \p isReadable and \p isWriteable are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureAccessQuery ( const VmbHandle_t handle, + const char* name, + VmbBool_t * isReadable, + VmbBool_t * isWriteable ); + +/** + * \} \} + */ + +/** + * \name Integer Feature Access + * \{ + * \defgroup IntAccess Integer Feature Access + * \{ + */ + +/** + * \brief Get the value of an integer feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] value Value to get + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name or \p value are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Integer + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntGet ( const VmbHandle_t handle, + const char* name, + VmbInt64_t* value ); + +/** + * \brief Set the value of an integer feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[in] value Value to set + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorInvalidCall If called from feature callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter If \p name is null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Integer + * + * \retval ::VmbErrorInvalidAccess The feature is unavailable or not writable + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + * + * \retval ::VmbErrorInvalidValue If value is either out of bounds or not an increment of the minimum + * + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntSet ( const VmbHandle_t handle, + const char* name, + VmbInt64_t value ); + +/** + * \brief Query the range of an integer feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] min Minimum value to be returned. May be null. + * \param[out] max Maximum value to be returned. May be null. + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter If \p name is null or both \p min and \p max are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature name is not Integer + * + * \retval ::VmbErrorInvalidAccess The range information is unavailable or not writable + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntRangeQuery ( const VmbHandle_t handle, + const char* name, + VmbInt64_t* min, + VmbInt64_t* max ); + +/** + * \brief Query the increment of an integer feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] value Value of the increment to get. + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter If \p name or \p value are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Integer + * + * \retval ::VmbErrorInvalidAccess The information is unavailable or cannot be read + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntIncrementQuery ( const VmbHandle_t handle, + const char* name, + VmbInt64_t* value ); + +/** + * \brief Retrieves info about the valid value set of an integer feature. + * + * Retrieves information about the set of valid values of an integer feature. If null is passed as buffer, + * only the size of the set is determined and written to bufferFilledCount; Otherwise the largest possible + * number of elements of the valid value set is copied to buffer. + * + * \param[in] handle The handle for the entity the feature information is retrieved from + * \param[in] name The name of the feature to retrieve the info for; if null is passed ::VmbErrorBadParameter is returned + * \param[in] buffer The array to copy the valid values to or null if only the size of the set is requested + * \param[in] bufferSize The size of buffer; if buffer is null, the value is ignored + * \param[out] setSize The total number of elements in the set; the value is set, if ::VmbErrorMoreData is returned + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name is null or both \p buffer and \p bufferFilledCount are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of the feature is not Integer + * + * \retval ::VmbErrorValidValueSetNotPresent The feature does not provide a valid value set + * + * \retval ::VmbErrorMoreData Some of data was retrieved successfully, but the size of buffer is insufficient to store all elements + * + * \retval ::VmbErrorIncomplete The module the handle refers to is in a state where it cannot complete the request + * + * \retval ::VmbErrorOther Some other issue occurred + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntValidValueSetQuery(const VmbHandle_t handle, + const char* name, + VmbInt64_t* buffer, + VmbUint32_t bufferSize, + VmbUint32_t* setSize); + +/** + * \} \} + */ + +/** + * \name Float Feature Access + * \{ + * \defgroup FloatAccess Float Feature Access + * \{ + */ + +/** + * \brief Get the value of a float feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] value Value to get + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name or \p value are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Float + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureFloatGet ( const VmbHandle_t handle, + const char* name, + double* value ); + +/** + * \brief Set the value of a float feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[in] value Value to set + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from feature callback + * + * \retval ::VmbErrorBadParameter \p name is null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Float + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + * + * \retval ::VmbErrorInvalidValue If value is not within valid bounds + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureFloatSet ( const VmbHandle_t handle, + const char* name, + double value ); + +/** + * \brief Query the range of a float feature. + * + * Only one of the values may be queried if the other parameter is set to null, + * but if both parameters are null, an error is returned. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] min Minimum value to be returned. May be null. + * \param[out] max Maximum value to be returned. May be null. + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorBadParameter \p name is null or both \p min and \p max are null + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Float + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureFloatRangeQuery ( const VmbHandle_t handle, + const char* name, + double* min, + double* max ); + +/** + * \brief Query the increment of a float feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] hasIncrement `true` if this float feature has an increment. + * \param[out] value Value of the increment to get. + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorBadParameter \p name is null or both \p value and \p hasIncrement are null + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Float + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureFloatIncrementQuery ( const VmbHandle_t handle, + const char* name, + VmbBool_t* hasIncrement, + double* value ); + +/** + * \} \} +*/ + +/** + * \name Enum Feature Access + * \{ + * \defgroup EnumAccess Enum Feature Access + * \{ + */ + +/** + * \brief Get the value of an enumeration feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] value The current enumeration value. The returned value is a + * reference to the API value + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorBadParameter \p name or \p value are null + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature featureName is not Enumeration + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature is not available + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumGet ( const VmbHandle_t handle, + const char* name, + const char** value ); + +/** + * \brief Set the value of an enumeration feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[in] value Value to set + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from feature callback + * + * \retval ::VmbErrorBadParameter If \p name or \p value are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration + * + * \retval ::VmbErrorNotAvailable The feature is not available + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorInvalidValue \p value is not a enum entry for the feature or the existing enum entry is currently not available + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumSet ( const VmbHandle_t handle, + const char* name, + const char* value ); + +/** + * \brief Query the value range of an enumeration feature. + * + * All elements not filled with the names of enum entries by the function are set to null. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[out] nameArray An array of enumeration value names; may be null if \p numFound is used for size query + * \param[in] arrayLength Number of elements in the array + * \param[out] numFound Number of elements found; may be null if \p nameArray is not null + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name is null or both \p nameArray and \p numFound are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorNotImplemented The feature \p name is not implemented + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration + * + * \retval ::VmbErrorMoreData The given array length was insufficient to hold all available entries + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumRangeQuery ( const VmbHandle_t handle, + const char* name, + const char** nameArray, + VmbUint32_t arrayLength, + VmbUint32_t* numFound ); + +/** + * \brief Check if a certain value of an enumeration is available. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[in] value Value to check + * \param[out] isAvailable Indicates if the given enumeration value is available + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name, \p value or \p isAvailable are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration + * + * \retval ::VmbErrorNotImplemented The feature \p name is not implemented + * + * \retval ::VmbErrorInvalidValue There is no enum entry with string representation of \p value for the given enum feature + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumIsAvailable ( const VmbHandle_t handle, + const char* name, + const char* value, + VmbBool_t * isAvailable ); + +/** + * \brief Get the integer value for a given enumeration string value. + * + * Converts a name of an enum member into an int value ("Mono12Packed" to 0x10C0006) + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[in] value The enumeration value to get the integer value for + * \param[out] intVal The integer value for this enumeration entry + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter If \p name, \p value or \p intVal are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound No feature with the given name was found + * + * \retval ::VmbErrorNotImplemented The feature \p name is not implemented + * + * \retval ::VmbErrorInvalidValue \p value is not the name of a enum entry for the feature + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumAsInt ( const VmbHandle_t handle, + const char* name, + const char* value, + VmbInt64_t* intVal ); + +/** + * \brief Get the enumeration string value for a given integer value. + * + * Converts an int value to a name of an enum member (e.g. 0x10C0006 to "Mono12Packed") + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[in] intValue The numeric value + * \param[out] stringValue The string value for the numeric value + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name or \p stringValue are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound No feature with the given name was found + * + * \retval ::VmbErrorNotImplemented No feature \p name is not implemented + * + * \retval ::VmbErrorInvalidValue \p intValue is not the int value of an enum entry + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumAsString ( VmbHandle_t handle, + const char* name, + VmbInt64_t intValue, + const char** stringValue ); + +/** + * \brief Get infos about an entry of an enumeration feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] featureName Name of the feature + * \param[in] entryName Name of the enum entry of that feature + * \param[out] featureEnumEntry Infos about that entry returned by the API + * \param[in] sizeofFeatureEnumEntry Size of the structure + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorStructSize Size of ::VmbFeatureEnumEntry_t is not compatible with the API version + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p featureName, \p entryName or \p featureEnumEntry are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorNotImplemented The feature \p name is not implemented + * + * \retval ::VmbErrorInvalidValue There is no enum entry with a string representation of \p entryName + * + * \retval ::VmbErrorWrongType The type of feature featureName is not Enumeration + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumEntryGet ( const VmbHandle_t handle, + const char* featureName, + const char* entryName, + VmbFeatureEnumEntry_t* featureEnumEntry, + VmbUint32_t sizeofFeatureEnumEntry ); + +/** + * \} \} + */ + +/** + * \name String Feature Access + * \{ + * \defgroup StringAccess String Feature Access + * \{ + */ + +/** + * \brief Get the value of a string feature. + * + * This function is usually called twice: once with an empty buffer to query the length + * of the string, and then again with a buffer of the correct length. + * + * The value written to \p sizeFilled includes the terminating 0 character of the string. + * + * If a \p buffer is provided and there its insufficient to hold all the data, the longest + * possible prefix fitting the buffer is copied to \p buffer; the last element of \p buffer is + * set to 0 case. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the string feature + * \param[out] buffer String buffer to fill. May be null if \p sizeFilled is used for size query. + * \param[in] bufferSize Size of the input buffer + * \param[out] sizeFilled Size actually filled. May be null if \p buffer is not null. + * + * + * \return An error code indicating the type of error, if any. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name is null, both \p buffer and \p sizeFilled are null or \p buffer is non-null and bufferSize is 0 + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not String + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + * + * \retval ::VmbErrorMoreData The given buffer size was too small + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureStringGet ( const VmbHandle_t handle, + const char* name, + char* buffer, + VmbUint32_t bufferSize, + VmbUint32_t* sizeFilled ); + +/** + * \brief Set the value of a string feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the string feature + * \param[in] value Value to set + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from feature callback + * + * \retval ::VmbErrorBadParameter \p name or \p value are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not String + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorInvalidValue If length of value exceeded the maximum length + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureStringSet ( const VmbHandle_t handle, + const char* name, + const char* value ); + +/** + * \brief Get the maximum length of a string feature. + * + * The length reported does not include the terminating 0 char. + * + * Note: For some features the maximum size is not fixed and may change. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the string feature + * \param[out] maxLength Maximum length of this string feature + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name or \p maxLength are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorWrongType The type of feature \p name is not String + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureStringMaxlengthQuery ( const VmbHandle_t handle, + const char* name, + VmbUint32_t* maxLength ); + +/** + * \} \} + */ + +/** + * \name Boolean Feature Access + * \{ + * \defgroup BoolAccess Boolean Feature Access + * \{ + */ + +/** + * \brief Get the value of a boolean feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the boolean feature + * \param[out] value Value to be read + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name or \p value are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound If feature is not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Boolean + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureBoolGet ( const VmbHandle_t handle, + const char* name, + VmbBool_t * value ); + +/** + * \brief Set the value of a boolean feature. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the boolean feature + * \param[in] value Value to write + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name is null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound If the feature is not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Boolean + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + * + * \retval ::VmbErrorInvalidValue If value is not within valid bounds + * + * \retval ::VmbErrorInvalidCall If called from feature callback + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureBoolSet ( const VmbHandle_t handle, + const char* name, + VmbBool_t value ); + +/** + * \} \} + */ + +/** + * \name Command Feature Access + * \{ + * \defgroup CmdAccess Command Feature Access + * \{ + */ + +/** + * \brief Run a feature command. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the command feature + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorInvalidCall If called from a feature callback or chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name is null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound Feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Command + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureCommandRun ( const VmbHandle_t handle, + const char* name ); + +/** + * \brief Check if a feature command is done. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the command feature + * \param[out] isDone State of the command. + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorBadParameter If \p name or \p isDone are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound Feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Command + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureCommandIsDone ( const VmbHandle_t handle, + const char* name, + VmbBool_t * isDone ); + +/** + * \} \} + */ + +/** + * \name Raw Feature Access + * \{ + * \defgroup RawAccess Raw Feature Access + * \{ + */ + +/** + * \brief Read the memory contents of an area given by a feature name. + * + * This feature type corresponds to a top-level "Register" feature in GenICam. + * Data transfer is split up by the transport layer if the feature length is too large. + * You can get the size of the memory area addressed by the feature name by ::VmbFeatureRawLengthQuery(). + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the raw feature + * \param[out] buffer Buffer to fill + * \param[in] bufferSize Size of the buffer to be filled + * \param[out] sizeFilled Number of bytes actually filled + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p name, \p buffer or \p sizeFilled are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound Feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Register + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureRawGet ( const VmbHandle_t handle, + const char* name, + char* buffer, + VmbUint32_t bufferSize, + VmbUint32_t* sizeFilled ); + +/** + * \brief Write to a memory area given by a feature name. + * + * This feature type corresponds to a first-level "Register" node in the XML file. + * Data transfer is split up by the transport layer if the feature length is too large. + * You can get the size of the memory area addressed by the feature name by ::VmbFeatureRawLengthQuery(). + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the raw feature + * \param[in] buffer Data buffer to use + * \param[in] bufferSize Size of the buffer + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from feature callback or a chunk access callback + * + * \retval ::VmbErrorBadParameter \p name or \p buffer are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound Feature was not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Register + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureRawSet ( const VmbHandle_t handle, + const char* name, + const char* buffer, + VmbUint32_t bufferSize ); + +/** + * \brief Get the length of a raw feature for memory transfers. + * + * This feature type corresponds to a first-level "Register" node in the XML file. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the raw feature + * \param[out] length Length of the raw feature area (in bytes) + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter If \p name or \p length are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound Feature not found + * + * \retval ::VmbErrorWrongType The type of feature \p name is not Register + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorNotImplemented The feature isn't implemented + * + * \retval ::VmbErrorNotAvailable The feature isn't available currently + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureRawLengthQuery ( const VmbHandle_t handle, + const char* name, + VmbUint32_t* length ); + +/** + * \} \} + */ + +/** + * \name Feature Invalidation + * \{ + * \defgroup FeatureInvalidation Feature Invalidation + * \{ + */ + +/** + * \brief Register a VmbInvalidationCallback callback for feature invalidation signaling. + * + * Any feature change, either of its value or of its access state, may be tracked + * by registering an invalidation callback. + * Registering multiple callbacks for one feature invalidation event is possible because + * only the combination of handle, name, and callback is used as key. If the same + * combination of handle, name, and callback is registered a second time, the callback remains + * registered and the context is overwritten with \p userContext. + * + * \param[in] handle Handle for an entity that emits events + * \param[in] name Name of the event + * \param[in] callback Callback to be run when invalidation occurs + * \param[in] userContext User context passed to function + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorBadParameter If \p name or \p callback are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound No feature with \p name was found for the module associated with \p handle + * + * \retval ::VmbErrorNotImplemented The feature \p name is not implemented + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureInvalidationRegister ( VmbHandle_t handle, + const char* name, + VmbInvalidationCallback callback, + void* userContext ); + +/** + * \brief Unregister a previously registered feature invalidation callback. + * + * Since multiple callbacks may be registered for a feature invalidation event, + * a combination of handle, name, and callback is needed for unregistering, too. + * + * \param[in] handle Handle for an entity that emits events + * \param[in] name Name of the event + * \param[in] callback Callback to be removed + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorBadParameter If \p name or \p callback are null + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound No feature with \p name was found for the module associated with \p handle or there was no listener to unregister + * + * \retval ::VmbErrorNotImplemented The feature \p name is not implemented + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + */ +IMEXPORTC VmbError_t VMB_CALL VmbFeatureInvalidationUnregister ( VmbHandle_t handle, + const char* name, + VmbInvalidationCallback callback ); + +/** + * \} \} + */ + +/** + * \name Image preparation and acquisition + * \{ + * \defgroup Capture Image preparation and acquisition + * \{ + */ + +/** +* \brief Get the necessary payload size for buffer allocation. +* +* Returns the payload size necessary for buffer allocation as queried from the Camera. +* If the stream module provides a PayloadSize feature, this value will be returned instead. +* If a camera handle is passed, the payload size refers to the stream with index 0. +* +* \param[in] handle Camera or stream handle +* \param[out] payloadSize Payload Size +* +* +* \return An error code indicating success or the type of error that occurred. +* +* \retval ::VmbErrorSuccess If no error +* +* \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command +* +* \retval ::VmbErrorBadHandle The given handle is not valid +* +* \retval ::VmbErrorBadParameter \p payloadSize is null +*/ +IMEXPORTC VmbError_t VMB_CALL VmbPayloadSizeGet(VmbHandle_t handle, + VmbUint32_t* payloadSize); + +/** + * \brief Announce frames to the API that may be queued for frame capturing later. + * + * Allows some preparation for frames like DMA preparation depending on the transport layer. + * The order in which the frames are announced is not taken into consideration by the API. + * If frame.buffer is null, the allocation is done by the transport layer. + * + * \param[in] handle Camera or stream handle + * \param[in] frame Frame buffer to announce + * \param[in] sizeofFrame Size of the frame structure + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorStructSize The given struct size is not valid for this version of the API + * + * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given camera handle is not valid + * + * \retval ::VmbErrorBadParameter \p frame is null + * + * \retval ::VmbErrorAlready The frame has already been announced + * + * \retval ::VmbErrorBusy The underlying transport layer does not support announcing frames during acquisition + * + * \retval ::VmbErrorMoreData The given buffer size is invalid (usually 0) + */ +IMEXPORTC VmbError_t VMB_CALL VmbFrameAnnounce ( VmbHandle_t handle, + const VmbFrame_t* frame, + VmbUint32_t sizeofFrame ); + + +/** + * \brief Revoke a frame from the API. + * + * The referenced frame is removed from the pool of frames for capturing images. + * + * \param[in] handle Handle for a camera or stream + * \param[in] frame Frame buffer to be removed from the list of announced frames + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorBadParameter The given frame pointer is not valid + * + * \retval ::VmbErrorBusy The underlying transport layer does not support revoking frames during acquisition + * + * \retval ::VmbErrorNotFound The given frame could not be found for the stream + * + * \retval ::VmbErrorInUse The frame is currently still in use (e.g. in a running frame callback) + */ +IMEXPORTC VmbError_t VMB_CALL VmbFrameRevoke ( VmbHandle_t handle, + const VmbFrame_t* frame ); + + +/** + * \brief Revoke all frames assigned to a certain stream or camera. + * + * In case of an failure some of the frames may have been revoked. To prevent this it is recommended to call + * ::VmbCaptureQueueFlush for the same handle before invoking this function. + * + * \param[in] handle Handle for a stream or camera + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle \p handle is not valid + * + * \retval ::VmbErrorInUse One of the frames of the stream is still in use + */ +IMEXPORTC VmbError_t VMB_CALL VmbFrameRevokeAll ( VmbHandle_t handle ); + + +/** + * \brief Prepare the API for incoming frames. + * + * \param[in] handle Handle for a camera or a stream + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given handle is not valid; this includes the camera no longer being open + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorMoreData The buffer size of the announced frames is insufficient + * + * \retval ::VmbErrorInsufficientBufferCount The operation requires more buffers to be announced; see the StreamAnnounceBufferMinimum stream feature + * + * \retval ::VmbErrorAlready Capturing was already started + */ +IMEXPORTC VmbError_t VMB_CALL VmbCaptureStart ( VmbHandle_t handle ); + + +/** + * \brief Stop the API from being able to receive frames. + * + * Consequences of VmbCaptureEnd(): + * The frame callback will not be called anymore + * + * \note This function waits for the completion of the last callback for the current capture. + * If the callback does not return in finite time, this function may not return in finite time either. + * + * \param[in] handle Handle for a stream or camera + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle \p handle is not valid + */ +IMEXPORTC VmbError_t VMB_CALL VmbCaptureEnd ( VmbHandle_t handle ); + + +/** + * \brief Queue frames that may be filled during frame capturing. + * + * The given frame is put into a queue that will be filled sequentially. + * The order in which the frames are filled is determined by the order in which they are queued. + * If the frame was announced with ::VmbFrameAnnounce() before, the application + * has to ensure that the frame is also revoked by calling ::VmbFrameRevoke() or + * ::VmbFrameRevokeAll() when cleaning up. + * + * \warning \p callback should to return in finite time. Otherwise ::VmbCaptureEnd and + * operations resulting in the stream being closed may not return. + * + * \param[in] handle Handle of a camera or stream + * \param[in] frame Pointer to an already announced frame + * \param[in] callback Callback to be run when the frame is complete. Null is OK. + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorBadParameter If \p frame is null + * + * \retval ::VmbErrorBadHandle No stream related to \p handle could be found + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInternalFault The buffer or bufferSize members of \p frame have been set to null or zero respectively + * + * \retval ::VmbErrorNotFound The frame is not a frame announced for the given stream + * + * \retval ::VmbErrorAlready The frame is currently queued + */ +IMEXPORTC VmbError_t VMB_CALL VmbCaptureFrameQueue ( VmbHandle_t handle, + const VmbFrame_t* frame, + VmbFrameCallback callback ); + +/** + * \brief Wait for a queued frame to be filled (or dequeued). + * + * The frame needs to be queued and not filled for the function to complete successfully. + * + * If a camera handle is passed, the first stream of the camera is used. + * + * \param[in] handle Handle of a camera or stream + * \param[in] frame Pointer to an already announced and queued frame + * \param[in] timeout Timeout (in milliseconds) + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorBadParameter If \p frame or the buffer of \p frame are null or the the buffer size of \p frame is 0 + * + * \retval ::VmbErrorBadHandle No stream related to \p handle could be found + * + * \retval ::VmbErrorNotFound The frame is not one currently queued for the stream + * + * \retval ::VmbErrorAlready The frame has already been dequeued or VmbCaptureFrameWait has been called already for this frame + * + * \retval ::VmbErrorInUse If the frame was queued with a frame callback + * + * \retval ::VmbErrorTimeout Call timed out + * + * \retval ::VmbErrorIncomplete Capture is not active when the function is called + */ +IMEXPORTC VmbError_t VMB_CALL VmbCaptureFrameWait ( const VmbHandle_t handle, + const VmbFrame_t* frame, + VmbUint32_t timeout); + + +/** + * \brief Flush the capture queue. + * + * Control of all the currently queued frames will be returned to the user, + * leaving no frames in the capture queue. + * After this call, no frame notification will occur until frames are queued again + * + * Frames need to be revoked separately, if desired. + * + * This function can only succeeds, if no capture is currently active. + * If ::VmbCaptureStart has been called for the stream, but no successful call to ::VmbCaptureEnd + * happened, the function fails with error code ::VmbErrorInUse. + * + * \param[in] handle The handle of the camera or stream to flush. + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorBadHandle No stream related to \p handle could be found. + * + * \retval ::VmbErrorInUse There is currently an active capture + */ +IMEXPORTC VmbError_t VMB_CALL VmbCaptureQueueFlush(VmbHandle_t handle); + +/** + * \} \} + */ + +/** + * \name Transport Layer Enumeration & Information + * \{ + * \defgroup TransportLayer Transport Layer Enumeration & Information + * \{ + */ + +/** + * \brief List all the transport layers that are used by the API. + * + * Note: This function is usually called twice: once with an empty array to query the length + * of the list, and then again with an array of the correct length. + * + * \param[in,out] transportLayerInfo Array of VmbTransportLayerInfo_t, allocated by the caller. + * The transport layer list is copied here. May be null. + * \param[in] listLength Number of entries in the caller's transportLayerInfo array. + * \param[in,out] numFound Number of transport layers found. May be more than listLength. + * \param[in] sizeofTransportLayerInfo Size of one ::VmbTransportLayerInfo_t entry (ignored if \p transportLayerInfo is null). + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInternalFault An internal fault occurred + * + * \retval ::VmbErrorNotImplemented One of the transport layers does not provide the required information + * + * \retval ::VmbErrorBadParameter \p numFound is null + * + * \retval ::VmbErrorStructSize The given struct size is not valid for this API version + * + * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries + */ +IMEXPORTC VmbError_t VMB_CALL VmbTransportLayersList ( VmbTransportLayerInfo_t* transportLayerInfo, + VmbUint32_t listLength, + VmbUint32_t* numFound, + VmbUint32_t sizeofTransportLayerInfo); + +/** + * \} \} +*/ + +/** + * \name Interface Enumeration & Information + * \{ + * \defgroup Interface Interface Enumeration & Information + * \{ + */ + +/** + * \brief List all the interfaces that are currently visible to the API. + * + * Note: All the interfaces known via GenICam transport layers are listed by this + * command and filled into the provided array. Interfaces may correspond to + * adapter cards or frame grabber cards. + * This function is usually called twice: once with an empty array to query the length + * of the list, and then again with an array of the correct length. + * + * \param[in,out] interfaceInfo Array of ::VmbInterfaceInfo_t, allocated by the caller. + * The interface list is copied here. May be null. + * + * \param[in] listLength Number of entries in the callers interfaceInfo array + * + * \param[in,out] numFound Number of interfaces found. Can be more than listLength + * + * \param[in] sizeofInterfaceInfo Size of one ::VmbInterfaceInfo_t entry + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p numFound is null + * + * \retval ::VmbErrorStructSize The given struct size is not valid for this API version + * + * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries + */ +IMEXPORTC VmbError_t VMB_CALL VmbInterfacesList ( VmbInterfaceInfo_t* interfaceInfo, + VmbUint32_t listLength, + VmbUint32_t* numFound, + VmbUint32_t sizeofInterfaceInfo ); + +/** + * \} \} + */ + +/** + * \name Direct Access + * \{ + * \defgroup DirectAccess Direct Access + * \{ + */ + +//----- Memory/Register access -------------------------------------------- + +/** + * \brief Read an array of bytes. + * + * \param[in] handle Handle for an entity that allows memory access + * \param[in] address Address to be used for this read operation + * \param[in] bufferSize Size of the data buffer to read + * \param[out] dataBuffer Buffer to be filled + * \param[out] sizeComplete Size of the data actually read + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + */ +IMEXPORTC VmbError_t VMB_CALL VmbMemoryRead ( const VmbHandle_t handle, + VmbUint64_t address, + VmbUint32_t bufferSize, + char* dataBuffer, + VmbUint32_t* sizeComplete ); + +/** + * \brief Write an array of bytes. + * + * \param[in] handle Handle for an entity that allows memory access + * \param[in] address Address to be used for this read operation + * \param[in] bufferSize Size of the data buffer to write + * \param[in] dataBuffer Data to write + * \param[out] sizeComplete Number of bytes successfully written; if an + * error occurs this is less than bufferSize + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorMoreData Not all data were written; see sizeComplete value for the number of bytes written + */ +IMEXPORTC VmbError_t VMB_CALL VmbMemoryWrite ( const VmbHandle_t handle, + VmbUint64_t address, + VmbUint32_t bufferSize, + const char* dataBuffer, + VmbUint32_t* sizeComplete ); + +/** + * \} \} + */ + +/** + * \name Load & Save Settings + * \{ + * \defgroup LoadSaveSettings Load & Save Settings + * \{ + */ + +/** + * \brief Write the current features related to a module to a xml file + * + * Camera must be opened beforehand and function needs corresponding handle. + * With given filename parameter path and name of XML file can be determined. + * Additionally behaviour of function can be set with providing 'persistent struct'. + * + * \param[in] handle Handle for an entity that allows register access + * \param[in] filePath The path to the file to save the settings to; relative paths are relative to the current working directory + * \param[in] settings Settings struct; if null the default settings are used + * (persist features except LUT for the remote device, maximum 5 iterations, logging only errors) + * \param[in] sizeofSettings Size of settings struct + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorBadParameter If \p filePath is or the settings struct is invalid + * + * \retval ::VmbErrorStructSize If sizeofSettings the struct size does not match the size of the struct expected by the API + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorNotFound The provided handle is insufficient to identify all the modules that should be saved + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorIO There was an issue writing the file. + */ +IMEXPORTC VmbError_t VMB_CALL VmbSettingsSave(VmbHandle_t handle, + const VmbFilePathChar_t* filePath, + const VmbFeaturePersistSettings_t* settings, + VmbUint32_t sizeofSettings); + +/** + * \brief Load all feature values from xml file to device-related modules. + * + * The modules must be opened beforehand. If the handle is non-null it must be a valid handle other than the Vmb API handle. + * Additionally behaviour of function can be set with providing \p settings . Note that even in case of an failure some or all of the features + * may have been set for some of the modules. + * + * The error code ::VmbErrorRetriesExceeded only indicates that the number of retries was insufficient + * to restore the features. Even if the features could not be restored for one of the modules, restoring the features is not aborted but the process + * continues for other modules, if present. + * + * \param[in] handle Handle related to the modules to write the values to; + * may be null to indicate that modules should be identified based on the information provided in the input file + * + * \param[in] filePath The path to the file to load the settings from; relative paths are relative to the current working directory + * \param[in] settings Settings struct; pass null to use the default settings. If the \p maxIterations field is 0, the number of + * iterations is determined by the value loaded from the xml file + * \param[in] sizeofSettings Size of the settings struct + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess If no error + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback + * + * \retval ::VmbErrorStructSize If sizeofSettings the struct size does not match the size of the struct expected by the API + * + * \retval ::VmbErrorWrongType \p handle is neither null nor a transport layer, interface, local device, remote device or stream handle + * + * \retval ::VmbErrorBadHandle The given handle is not valid + * + * \retval ::VmbErrorAmbiguous The modules to restore the settings for cannot be uniquely identified based on the information available + * + * \retval ::VmbErrorNotFound The provided handle is insufficient to identify all the modules that should be restored + * + * \retval ::VmbErrorRetriesExceeded Some or all of the features could not be restored with the max iterations specified + * + * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode + * + * \retval ::VmbErrorBadParameter If \p filePath is null or the settings struct is invalid + * + * \retval ::VmbErrorIO There was an issue with reading the file. + */ +IMEXPORTC VmbError_t VMB_CALL VmbSettingsLoad(VmbHandle_t handle, + const VmbFilePathChar_t* filePath, + const VmbFeaturePersistSettings_t* settings, + VmbUint32_t sizeofSettings); + +/** + * \} \} + */ + +/** + * \name Chunk Data + * \{ + * \defgroup ChunkData Chunk Data + * \{ + */ + +/** + * \brief Access chunk data for a frame. + * + * This function can only succeed if the given frame has been filled by the API. + * + * \param[in] frame A pointer to a filled frame that is announced + * \param[in] chunkAccessCallback A callback to access the chunk data from + * \param[in] userContext A pointer to pass to the callback + * + * + * \return An error code indicating success or the type of error that occurred. + * + * \retval ::VmbErrorSuccess The call was successful + * + * \retval ::VmbErrorInvalidCall If called from a chunk access callback or a feature callback + * + * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command + * + * \retval ::VmbErrorBadParameter \p frame or \p chunkAccessCallback are null + * + * \retval ::VmbErrorInUse The frame state does not allow for retrieval of chunk data + * (e.g. the frame could have been reenqueued before the chunk access could happen). + * + * \retval ::VmbErrorNotFound The frame is currently not announced for a stream + * + * \retval ::VmbErrorDeviceNotOpen If the device the frame was received from is no longer open + * + * \retval ::VmbErrorNoChunkData \p frame does not contain chunk data + * + * \retval ::VmbErrorParsingChunkData The chunk data does not adhere to the expected format + * + * \retval ::VmbErrorUserCallbackException The callback threw an exception + * + * \retval ::VmbErrorFeaturesUnavailable The feature description for the remote device is unavailable + * + * \retval ::VmbErrorCustom The minimum a user defined error code returned by the callback + */ +IMEXPORTC VmbError_t VMB_CALL VmbChunkDataAccess(const VmbFrame_t* frame, + VmbChunkAccessCallback chunkAccessCallback, + void* userContext); + +/** + * \} \} \} + */ +#ifdef __cplusplus +} +#endif + +#endif // VMBC_H_INCLUDE_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCTypeDefinitions.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCTypeDefinitions.h new file mode 100644 index 000000000..b010d5be6 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCTypeDefinitions.h @@ -0,0 +1,610 @@ +/*============================================================================= + Copyright (C) 2012 - 2021 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + +------------------------------------------------------------------------------- + + File: VmbCTypeDefinitions.h + +------------------------------------------------------------------------------- + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ + +/** + * \file + * \brief Struct definitions for the VmbC API. + */ + +#ifndef VMBC_TYPE_DEFINITIONS_H_INCLUDE_ +#define VMBC_TYPE_DEFINITIONS_H_INCLUDE_ + +#include +#include + +#include + +#if defined (_WIN32) +#if defined AVT_VMBAPI_C_EXPORTS // DLL exports +#define IMEXPORTC // We export via the .def file +#elif defined AVT_VMBAPI_C_LIB // static LIB +#define IMEXPORTC +#else // import +#define IMEXPORTC __declspec(dllimport) +#endif + +#ifndef _WIN64 + // Calling convention +#define VMB_CALL __stdcall +#else + // Calling convention +#define VMB_CALL +#endif +#elif defined (__GNUC__) && (__GNUC__ >= 4) && defined (__ELF__) + // SO exports (requires compiler option -fvisibility=hidden) +#ifdef AVT_VMBAPI_C_EXPORTS +#define IMEXPORTC __attribute__((visibility("default"))) +#else +#define IMEXPORTC +#endif + +#ifdef __i386__ + // Calling convention +#define VMB_CALL __attribute__((stdcall)) +#else + // Calling convention +#define VMB_CALL +#endif +#elif defined (__APPLE__) +#define IMEXPORTC __attribute__((visibility("default"))) + // Calling convention +#define VMB_CALL +#else +#error Unknown platform, file needs adaption +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \name Transport layer + * \{ + */ + + /** +* \brief Camera or transport layer type (for instance U3V or GEV). +*/ +typedef enum VmbTransportLayerType +{ + VmbTransportLayerTypeUnknown = 0, //!< Interface is not known to this version of the API + VmbTransportLayerTypeGEV = 1, //!< GigE Vision + VmbTransportLayerTypeCL = 2, //!< Camera Link + VmbTransportLayerTypeIIDC = 3, //!< IIDC 1394 + VmbTransportLayerTypeUVC = 4, //!< USB video class + VmbTransportLayerTypeCXP = 5, //!< CoaXPress + VmbTransportLayerTypeCLHS = 6, //!< Camera Link HS + VmbTransportLayerTypeU3V = 7, //!< USB3 Vision Standard + VmbTransportLayerTypeEthernet = 8, //!< Generic Ethernet + VmbTransportLayerTypePCI = 9, //!< PCI / PCIe + VmbTransportLayerTypeCustom = 10, //!< Non standard + VmbTransportLayerTypeMixed = 11, //!< Mixed (transport layer only) +} VmbTransportLayerType; + +/** + * \brief Type for an Interface; for values see ::VmbTransportLayerType. + */ +typedef VmbUint32_t VmbTransportLayerType_t; + +/** + * \brief Transport layer information. + * + * Holds read-only information about a transport layer. + */ +typedef struct VmbTransportLayerInfo +{ + /** + * \name Out + * \{ + */ + + const char* transportLayerIdString; //!< Unique id of the transport layer + const char* transportLayerName; //!< Name of the transport layer + const char* transportLayerModelName; //!< Model name of the transport layer + const char* transportLayerVendor; //!< Vendor of the transport layer + const char* transportLayerVersion; //!< Version of the transport layer + const char* transportLayerPath; //!< Full path of the transport layer + VmbHandle_t transportLayerHandle; //!< Handle of the transport layer for feature access + VmbTransportLayerType_t transportLayerType; //!< The type of the transport layer + + /** + * \} + */ +} VmbTransportLayerInfo_t; + +/** + * \} + */ + +/** + * \name Interface + * \{ + */ + +/** + * \brief Interface information. + * + * Holds read-only information about an interface. + */ +typedef struct VmbInterfaceInfo +{ + /** + * \name Out + * \{ + */ + + const char* interfaceIdString; //!< Identifier of the interface + const char* interfaceName; //!< Interface name, given by the transport layer + VmbHandle_t interfaceHandle; //!< Handle of the interface for feature access + VmbHandle_t transportLayerHandle; //!< Handle of the related transport layer for feature access + VmbTransportLayerType_t interfaceType; //!< The technology of the interface + + /** + * \} + */ +} VmbInterfaceInfo_t; + +/** + * \} + */ + +/** + * \name Camera + * \{ + */ + + /** + * \brief Access mode for cameras. + * + * Used in ::VmbCameraInfo_t as flags, so multiple modes can be + * announced, while in ::VmbCameraOpen(), no combination must be used. + */ +typedef enum VmbAccessModeType +{ + VmbAccessModeNone = 0, //!< No access + VmbAccessModeFull = 1, //!< Read and write access + VmbAccessModeRead = 2, //!< Read-only access + VmbAccessModeUnknown = 4, //!< Access type unknown + VmbAccessModeExclusive = 8, //!< Read and write access without permitting access for other consumers +} VmbAccessModeType; + +/** + * \brief Type for an AccessMode; for values see ::VmbAccessModeType. + */ +typedef VmbUint32_t VmbAccessMode_t; + +/** + * \brief Camera information. + * + * Holds read-only information about a camera. + */ +typedef struct VmbCameraInfo +{ + /** + * \name Out + * \{ + */ + + const char* cameraIdString; //!< Identifier of the camera + const char* cameraIdExtended; //!< globally unique identifier for the camera + const char* cameraName; //!< The display name of the camera + const char* modelName; //!< Model name + const char* serialString; //!< Serial number + VmbHandle_t transportLayerHandle; //!< Handle of the related transport layer for feature access + VmbHandle_t interfaceHandle; //!< Handle of the related interface for feature access + VmbHandle_t localDeviceHandle; //!< Handle of the related GenTL local device. NULL if the camera is not opened + VmbHandle_t const* streamHandles; //!< Handles of the streams provided by the camera. NULL if the camera is not opened + VmbUint32_t streamCount; //!< Number of stream handles in the streamHandles array + VmbAccessMode_t permittedAccess; //!< Permitted access modes, see ::VmbAccessModeType + + /** + * \} + */ +} VmbCameraInfo_t; + +/** + * \} + */ + +/** + * \name Feature + * \{ + */ + +/** + * \brief Supported feature data types. + */ +typedef enum VmbFeatureDataType +{ + VmbFeatureDataUnknown = 0, //!< Unknown feature type + VmbFeatureDataInt = 1, //!< 64-bit integer feature + VmbFeatureDataFloat = 2, //!< 64-bit floating point feature + VmbFeatureDataEnum = 3, //!< Enumeration feature + VmbFeatureDataString = 4, //!< String feature + VmbFeatureDataBool = 5, //!< Boolean feature + VmbFeatureDataCommand = 6, //!< Command feature + VmbFeatureDataRaw = 7, //!< Raw (direct register access) feature + VmbFeatureDataNone = 8, //!< Feature with no data +} VmbFeatureDataType; + +/** + * \brief Data type for a Feature; for values see ::VmbFeatureDataType. + */ +typedef VmbUint32_t VmbFeatureData_t; + +/** + * \brief Feature visibility. + */ +typedef enum VmbFeatureVisibilityType +{ + VmbFeatureVisibilityUnknown = 0, //!< Feature visibility is not known + VmbFeatureVisibilityBeginner = 1, //!< Feature is visible in feature list (beginner level) + VmbFeatureVisibilityExpert = 2, //!< Feature is visible in feature list (expert level) + VmbFeatureVisibilityGuru = 3, //!< Feature is visible in feature list (guru level) + VmbFeatureVisibilityInvisible = 4, //!< Feature is visible in the feature list, but should be hidden in GUI applications +} VmbFeatureVisibilityType; + +/** + * \brief Type for Feature visibility; for values see ::VmbFeatureVisibilityType. + */ +typedef VmbUint32_t VmbFeatureVisibility_t; + +/** + * \brief Feature flags. + */ +typedef enum VmbFeatureFlagsType +{ + VmbFeatureFlagsNone = 0, //!< No additional information is provided + VmbFeatureFlagsRead = 1, //!< Static info about read access. Current status depends on access mode, check with ::VmbFeatureAccessQuery() + VmbFeatureFlagsWrite = 2, //!< Static info about write access. Current status depends on access mode, check with ::VmbFeatureAccessQuery() + VmbFeatureFlagsVolatile = 8, //!< Value may change at any time + VmbFeatureFlagsModifyWrite = 16, //!< Value may change after a write +} VmbFeatureFlagsType; + +/** + * \brief Type for Feature flags; for values see ::VmbFeatureFlagsType. + */ +typedef VmbUint32_t VmbFeatureFlags_t; + +/** + * \brief Feature information. + * + * Holds read-only information about a feature. + */ +typedef struct VmbFeatureInfo +{ + /** + * \name Out + * \{ + */ + + const char* name; //!< Name used in the API + const char* category; //!< Category this feature can be found in + const char* displayName; //!< Feature name to be used in GUIs + const char* tooltip; //!< Short description, e.g. for a tooltip + const char* description; //!< Longer description + const char* sfncNamespace; //!< Namespace this feature resides in + const char* unit; //!< Measuring unit as given in the XML file + const char* representation; //!< Representation of a numeric feature + VmbFeatureData_t featureDataType; //!< Data type of this feature + VmbFeatureFlags_t featureFlags; //!< Access flags for this feature + VmbUint32_t pollingTime; //!< Predefined polling time for volatile features + VmbFeatureVisibility_t visibility; //!< GUI visibility + VmbBool_t isStreamable; //!< Indicates if a feature can be stored to / loaded from a file + VmbBool_t hasSelectedFeatures; //!< Indicates if the feature selects other features + + /** + * \} + */ +} VmbFeatureInfo_t; + +/** + * \brief Info about possible entries of an enumeration feature. + */ +typedef struct VmbFeatureEnumEntry +{ + /** + * \name Out + * \{ + */ + + const char* name; //!< Name used in the API + const char* displayName; //!< Enumeration entry name to be used in GUIs + const char* tooltip; //!< Short description, e.g. for a tooltip + const char* description; //!< Longer description + VmbInt64_t intValue; //!< Integer value of this enumeration entry + const char* sfncNamespace; //!< Namespace this feature resides in + VmbFeatureVisibility_t visibility; //!< GUI visibility + + /** + * \} + */ +} VmbFeatureEnumEntry_t; + +/** + * \} + */ + +/** + * \name Frame + * \{ + */ + +/** + * \brief Status of a frame transfer. + */ +typedef enum VmbFrameStatusType +{ + VmbFrameStatusComplete = 0, //!< Frame has been completed without errors + VmbFrameStatusIncomplete = -1, //!< Frame could not be filled to the end + VmbFrameStatusTooSmall = -2, //!< Frame buffer was too small + VmbFrameStatusInvalid = -3, //!< Frame buffer was invalid +} VmbFrameStatusType; + +/** + * \brief Type for the frame status; for values see ::VmbFrameStatusType. + */ +typedef VmbInt32_t VmbFrameStatus_t; + +/** + * \brief Frame flags. + */ +typedef enum VmbFrameFlagsType +{ + VmbFrameFlagsNone = 0, //!< No additional information is provided + VmbFrameFlagsDimension = 1, //!< VmbFrame_t::width and VmbFrame_t::height are provided + VmbFrameFlagsOffset = 2, //!< VmbFrame_t::offsetX and VmbFrame_t::offsetY are provided (ROI) + VmbFrameFlagsFrameID = 4, //!< VmbFrame_t::frameID is provided + VmbFrameFlagsTimestamp = 8, //!< VmbFrame_t::timestamp is provided + VmbFrameFlagsImageData = 16, //!< VmbFrame_t::imageData is provided + VmbFrameFlagsPayloadType = 32, //!< VmbFrame_t::payloadType is provided + VmbFrameFlagsChunkDataPresent = 64, //!< VmbFrame_t::chunkDataPresent is set based on info provided by the transport layer +} VmbFrameFlagsType; + +/** + * \brief Type for Frame flags; for values see ::VmbFrameFlagsType. + */ +typedef VmbUint32_t VmbFrameFlags_t; + +/** + * \brief Frame payload type. + */ +typedef enum VmbPayloadType +{ + VmbPayloadTypeUnknown = 0, //!< Unknown payload type + VmbPayloadTypeImage = 1, //!< image data + VmbPayloadTypeRaw = 2, //!< raw data + VmbPayloadTypeFile = 3, //!< file data + VmbPayloadTypeJPEG = 5, //!< JPEG data as described in the GigEVision 2.0 specification + VmbPayloadTypJPEG2000 = 6, //!< JPEG 2000 data as described in the GigEVision 2.0 specification + VmbPayloadTypeH264 = 7, //!< H.264 data as described in the GigEVision 2.0 specification + VmbPayloadTypeChunkOnly = 8, //!< Chunk data exclusively + VmbPayloadTypeDeviceSpecific = 9, //!< Device specific data format + VmbPayloadTypeGenDC = 11, //!< GenDC data +} VmbPayloadType; + +/** + * \brief Type representing the payload type of a frame. For values see ::VmbPayloadType. + */ +typedef VmbUint32_t VmbPayloadType_t; + +/** + * \brief Type used to represent a dimension value, e.g. the image height. + */ +typedef VmbUint32_t VmbImageDimension_t; + +/** + * \brief Frame delivered by the camera. + */ +typedef struct VmbFrame +{ + /** + * \name In + * \{ + */ + + void* buffer; //!< Comprises image and potentially chunk data + VmbUint32_t bufferSize; //!< The size of the data buffer + void* context[4]; //!< 4 void pointers that can be employed by the user (e.g. for storing handles) + + /** + * \} + */ + + /** + * \name Out + * \{ + */ + + VmbFrameStatus_t receiveStatus; //!< The resulting status of the receive operation + VmbUint64_t frameID; //!< Unique ID of this frame in this stream + VmbUint64_t timestamp; //!< The timestamp set by the camera + VmbUint8_t* imageData; //!< The start of the image data, if present, or null + VmbFrameFlags_t receiveFlags; //!< Flags indicating which additional frame information is available + VmbPixelFormat_t pixelFormat; //!< Pixel format of the image + VmbImageDimension_t width; //!< Width of an image + VmbImageDimension_t height; //!< Height of an image + VmbImageDimension_t offsetX; //!< Horizontal offset of an image + VmbImageDimension_t offsetY; //!< Vertical offset of an image + VmbPayloadType_t payloadType; //!< The type of payload + VmbBool_t chunkDataPresent; //!< True if the transport layer reported chunk data to be present in the buffer + + /** + * \} + */ +} VmbFrame_t; + +/** + * \} + */ + +/** + * \name Save/LoadSettings + * \{ + */ + +/** + * \brief Type of features that are to be saved (persisted) to the XML file when using ::VmbSettingsSave + */ +typedef enum VmbFeaturePersistType +{ + VmbFeaturePersistAll = 0, //!< Save all features to XML, including look-up tables (if possible) + VmbFeaturePersistStreamable = 1, //!< Save only features marked as streamable, excluding look-up tables + VmbFeaturePersistNoLUT = 2 //!< Save all features except look-up tables (default) +} VmbFeaturePersistType; + +/** + * \brief Type for feature persistence; for values see ::VmbFeaturePersistType. + */ +typedef VmbUint32_t VmbFeaturePersist_t; + +/** + * \brief Parameters determining the operation mode of ::VmbSettingsSave and ::VmbSettingsLoad. + */ +typedef enum VmbModulePersistFlagsType +{ + VmbModulePersistFlagsNone = 0x00, //!< Persist/Load features for no module. + VmbModulePersistFlagsTransportLayer = 0x01, //!< Persist/Load the transport layer features. + VmbModulePersistFlagsInterface = 0x02, //!< Persist/Load the interface features. + VmbModulePersistFlagsRemoteDevice = 0x04, //!< Persist/Load the remote device features. + VmbModulePersistFlagsLocalDevice = 0x08, //!< Persist/Load the local device features. + VmbModulePersistFlagsStreams = 0x10, //!< Persist/Load the features of stream modules. + VmbModulePersistFlagsAll = 0xff //!< Persist/Load features for all modules. +} VmbModulePersistFlagsType; + +/** + * \brief Type for module persist flags; for values see VmbModulePersistFlagsType + * + * Use a combination of ::VmbModulePersistFlagsType constants + */ +typedef VmbUint32_t VmbModulePersistFlags_t; + +/** + * \brief A level to use for logging + */ +typedef enum VmbLogLevel +{ + VmbLogLevelNone = 0, //!< Nothing is logged regardless of the severity of the issue + VmbLogLevelError, //!< Only errors are logged + VmbLogLevelDebug, //!< Only error and debug messages are logged + VmbLogLevelWarn, //!< Only error, debug and warn messages are logged + VmbLogLevelTrace, //!< all messages are logged + VmbLogLevelAll = VmbLogLevelTrace, //!< all messages are logged +} VmbLogLevel; + +/** + * \brief The type used for storing the log level + * + * Use a constant from ::VmbLogLevel + */ +typedef VmbUint32_t VmbLogLevel_t; + +/** + * \brief Parameters determining the operation mode of ::VmbSettingsSave and ::VmbSettingsLoad + */ +typedef struct VmbFeaturePersistSettings +{ + /** + * \name In + * \{ + */ + + VmbFeaturePersist_t persistType; //!< Type of features that are to be saved + VmbModulePersistFlags_t modulePersistFlags; //!< Flags specifying the modules to persist/load + VmbUint32_t maxIterations; //!< Number of iterations when loading settings + VmbLogLevel_t loggingLevel; //!< Determines level of detail for load/save settings logging + + /** + * \} + */ +} VmbFeaturePersistSettings_t; + +/** + * \} + */ + +/** + * \name Callbacks + * \{ + */ + +/** + * \brief Invalidation callback type for a function that gets called in a separate thread + * and has been registered with ::VmbFeatureInvalidationRegister(). + * + * While the callback is run, all feature data is atomic. After the callback finishes, + * the feature data may be updated with new values. + * + * Do not spend too much time in this thread; it prevents the feature values + * from being updated from any other thread or the lower-level drivers. + * + * \param[in] handle Handle for an entity that exposes features + * \param[in] name Name of the feature + * \param[in] userContext Pointer to the user context, see ::VmbFeatureInvalidationRegister + */ +typedef void (VMB_CALL* VmbInvalidationCallback)(const VmbHandle_t handle, const char* name, void* userContext); + +/** + * \brief Frame Callback type for a function that gets called in a separate thread + * if a frame has been queued with ::VmbCaptureFrameQueue. + * + * \warning Any operations closing the stream including ::VmbShutdown and ::VmbCameraClose in addition to + * ::VmbCaptureEnd block until any currently active callbacks return. If the callback does not + * return in finite time, the program may not return. + * + * \param[in] cameraHandle Handle of the camera the frame belongs to + * \param[in] streamHandle Handle of the stream the frame belongs to + * \param[in] frame The received frame + */ +typedef void (VMB_CALL* VmbFrameCallback)(const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, VmbFrame_t* frame); + +/** + * \brief Function pointer type to access chunk data + * + * This function should complete as quickly as possible, since it blocks other updates on the + * remote device. + * + * This function should not throw exceptions, even if VmbC is used from C++. Any exception + * thrown will only result in an error code indicating that an exception was thrown. + * + * \param[in] featureAccessHandle A special handle that can be used for accessing features; + * the handle is only valid during the call of the function. + * \param[in] userContext The value the user passed to ::VmbChunkDataAccess. + * + * \return An error to be returned from ::VmbChunkDataAccess in the absence of other errors; + * A custom exit code >= ::VmbErrorCustom can be returned to indicate a failure via + * ::VmbChunkDataAccess return code + */ +typedef VmbError_t(VMB_CALL* VmbChunkAccessCallback)(VmbHandle_t featureAccessHandle, void* userContext); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif // VMBC_TYPE_DEFINITIONS_H_INCLUDE_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCommonTypes.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCommonTypes.h new file mode 100644 index 000000000..7e3615472 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCommonTypes.h @@ -0,0 +1,444 @@ +/*============================================================================= + Copyright (C) 2012 - 2021 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + +------------------------------------------------------------------------------- + + File: VmbCommonTypes.h + +------------------------------------------------------------------------------- + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ + +/** + * \file + * \brief Main header file for the common types of the APIs. + * + * This file describes all necessary definitions for types used within + * the Vmb APIs. These type definitions are designed to be + * portable from other languages and other operating systems. + */ + +#ifndef VMBCOMMONTYPES_H_INCLUDE_ +#define VMBCOMMONTYPES_H_INCLUDE_ + +#ifdef _WIN32 +# include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \name Basic Types + * \{ + */ + +#if defined (_MSC_VER) + + /** + * \brief 8-bit signed integer. + */ + typedef __int8 VmbInt8_t; + + /** + * \brief 8-bit unsigned integer. + */ + typedef unsigned __int8 VmbUint8_t; + + /** + * \brief 16-bit signed integer. + */ + typedef __int16 VmbInt16_t; + + /** + * \brief 16-bit unsigned integer. + */ + typedef unsigned __int16 VmbUint16_t; + + /** + * \brief 32-bit signed integer. + */ + typedef __int32 VmbInt32_t; + + /** + * \brief 32-bit unsigned integer. + */ + typedef unsigned __int32 VmbUint32_t; + + /** + * \brief 64-bit signed integer. + */ + typedef __int64 VmbInt64_t; + + /** + * \brief 64-bit unsigned integer. + */ + typedef unsigned __int64 VmbUint64_t; + +#else + + /** + * \brief 8-bit signed integer. + */ + typedef signed char VmbInt8_t; + + /** + * \brief 8-bit unsigned integer. + */ + typedef unsigned char VmbUint8_t; + + /** + * \brief 16-bit signed integer. + */ + typedef short VmbInt16_t; + + /** + * \brief 16-bit unsigned integer. + */ + typedef unsigned short VmbUint16_t; + + /** + * \brief 32-bit signed integer. + */ + typedef int VmbInt32_t; + + /** + * \brief 32-bit unsigned integer. + */ + typedef unsigned int VmbUint32_t; + + /** + * \brief 64-bit signed integer. + */ + typedef long long VmbInt64_t; + + /** + * \brief 64-bit unsigned integer. + */ + typedef unsigned long long VmbUint64_t; + +#endif + + /** + * \brief Handle, e.g. for a camera. + */ + typedef void* VmbHandle_t; + +#if defined(__cplusplus) || defined(__bool_true_false_are_defined) + + /** + * \brief Standard type for boolean values. + */ + typedef bool VmbBool_t; + +#else + + /** + * \brief Boolean type (equivalent to char). + * + * For values see ::VmbBoolVal + */ + typedef char VmbBool_t; + +#endif + + /** + * \brief enum for bool values. + */ + typedef enum VmbBoolVal + { + VmbBoolTrue = 1, + VmbBoolFalse = 0, + } VmbBoolVal; + + /** + * \brief char type. + */ + typedef unsigned char VmbUchar_t; + +#ifdef _WIN32 + + /** + * \brief Character type used for file paths (Windows uses wchar_t not char). + */ + typedef wchar_t VmbFilePathChar_t; + + /** + * \brief macro for converting a c string literal into a system dependent string literal + * + * Adds L as prefix on Windows and is replaced by the unmodified value on other operating systems. + * + * \code{.c} + * const VmbFilePathChar_t* path = VMB_FILE_PATH_LITERAL("./some/path/tl.cti"); + * \endcode + */ +# define VMB_FILE_PATH_LITERAL(value) L##value + +#else + + /** + * Character type used for file paths + */ + typedef char VmbFilePathChar_t; + + /** + * \brief macro for converting a c string literal into a system dependent string literal + * + * Adds L as prefix on Windows and is replaced by the unmodified value on other operating systems. + * + * \code{.c} + * const VmbFilePathChar_t* path = VMB_FILE_PATH_LITERAL("./some/path/tl.cti"); + * \endcode + */ +# define VMB_FILE_PATH_LITERAL(value) value +#endif + +/** + * \} + */ + +/** + * \name Error Codes + * \{ + */ + + /** + * \brief Error codes, returned by most functions. + */ + typedef enum VmbErrorType + { + VmbErrorSuccess = 0, //!< No error + VmbErrorInternalFault = -1, //!< Unexpected fault in VmbC or driver + VmbErrorApiNotStarted = -2, //!< ::VmbStartup() was not called before the current command + VmbErrorNotFound = -3, //!< The designated instance (camera, feature etc.) cannot be found + VmbErrorBadHandle = -4, //!< The given handle is not valid + VmbErrorDeviceNotOpen = -5, //!< Device was not opened for usage + VmbErrorInvalidAccess = -6, //!< Operation is invalid with the current access mode + VmbErrorBadParameter = -7, //!< One of the parameters is invalid (usually an illegal pointer) + VmbErrorStructSize = -8, //!< The given struct size is not valid for this version of the API + VmbErrorMoreData = -9, //!< More data available in a string/list than space is provided + VmbErrorWrongType = -10, //!< Wrong feature type for this access function + VmbErrorInvalidValue = -11, //!< The value is not valid; either out of bounds or not an increment of the minimum + VmbErrorTimeout = -12, //!< Timeout during wait + VmbErrorOther = -13, //!< Other error + VmbErrorResources = -14, //!< Resources not available (e.g. memory) + VmbErrorInvalidCall = -15, //!< Call is invalid in the current context (e.g. callback) + VmbErrorNoTL = -16, //!< No transport layers are found + VmbErrorNotImplemented = -17, //!< API feature is not implemented + VmbErrorNotSupported = -18, //!< API feature is not supported + VmbErrorIncomplete = -19, //!< The current operation was not completed (e.g. a multiple registers read or write) + VmbErrorIO = -20, //!< Low level IO error in transport layer + VmbErrorValidValueSetNotPresent = -21, //!< The valid value set could not be retrieved, since the feature does not provide this property + VmbErrorGenTLUnspecified = -22, //!< Unspecified GenTL runtime error + VmbErrorUnspecified = -23, //!< Unspecified runtime error + VmbErrorBusy = -24, //!< The responsible module/entity is busy executing actions + VmbErrorNoData = -25, //!< The function has no data to work on + VmbErrorParsingChunkData = -26, //!< An error occurred parsing a buffer containing chunk data + VmbErrorInUse = -27, //!< Something is already in use + VmbErrorUnknown = -28, //!< Error condition unknown + VmbErrorXml = -29, //!< Error parsing XML + VmbErrorNotAvailable = -30, //!< Something is not available + VmbErrorNotInitialized = -31, //!< Something is not initialized + VmbErrorInvalidAddress = -32, //!< The given address is out of range or invalid for internal reasons + VmbErrorAlready = -33, //!< Something has already been done + VmbErrorNoChunkData = -34, //!< A frame expected to contain chunk data does not contain chunk data + VmbErrorUserCallbackException = -35, //!< A callback provided by the user threw an exception + VmbErrorFeaturesUnavailable = -36, //!< The XML for the module is currently not loaded; the module could be in the wrong state or the XML could not be retrieved or could not be parsed properly + VmbErrorTLNotFound = -37, //!< A required transport layer could not be found or loaded + VmbErrorAmbiguous = -39, //!< An entity cannot be uniquely identified based on the information provided + VmbErrorRetriesExceeded = -40, //!< Something could not be accomplished with a given number of retries + VmbErrorInsufficientBufferCount = -41, //!< The operation requires more buffers + VmbErrorCustom = 1, //!< The minimum error code to use for user defined error codes to avoid conflict with existing error codes + } VmbErrorType; + + /** + * \brief Type for an error returned by API methods; for values see ::VmbErrorType. + */ + typedef VmbInt32_t VmbError_t; + +/** + * \} + */ + +/** + * \name Version + * \{ + */ + + /** + * \brief Version information. + */ + typedef struct VmbVersionInfo + { + /** + * \name Out + * \{ + */ + + VmbUint32_t major; //!< Major version number + VmbUint32_t minor; //!< Minor version number + VmbUint32_t patch; //!< Patch version number + + /** + * \} + */ + } VmbVersionInfo_t; + +/** + * \} + */ + + /** + * \name Pixel information + * \{ + */ + + /** + * \brief Indicates if pixel is monochrome or RGB. + */ + typedef enum VmbPixelType + { + VmbPixelMono = 0x01000000, //!< Monochrome pixel + VmbPixelColor = 0x02000000 //!< Pixel bearing color information + } VmbPixelType; + + /** + * \brief Indicates number of bits for a pixel. Needed for building values of ::VmbPixelFormatType. + */ + typedef enum VmbPixelOccupyType + { + VmbPixelOccupy8Bit = 0x00080000, //!< Pixel effectively occupies 8 bits + VmbPixelOccupy10Bit = 0x000A0000, //!< Pixel effectively occupies 10 bits + VmbPixelOccupy12Bit = 0x000C0000, //!< Pixel effectively occupies 12 bits + VmbPixelOccupy14Bit = 0x000E0000, //!< Pixel effectively occupies 14 bits + VmbPixelOccupy16Bit = 0x00100000, //!< Pixel effectively occupies 16 bits + VmbPixelOccupy24Bit = 0x00180000, //!< Pixel effectively occupies 24 bits + VmbPixelOccupy32Bit = 0x00200000, //!< Pixel effectively occupies 32 bits + VmbPixelOccupy48Bit = 0x00300000, //!< Pixel effectively occupies 48 bits + VmbPixelOccupy64Bit = 0x00400000, //!< Pixel effectively occupies 64 bits + } VmbPixelOccupyType; + + /** + * \brief Pixel format types. + * As far as possible, the Pixel Format Naming Convention (PFNC) has been followed, allowing a few deviations. + * If data spans more than one byte, it is always LSB aligned, except if stated differently. + */ + typedef enum VmbPixelFormatType + { + // mono formats + VmbPixelFormatMono8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x0001, //!< Monochrome, 8 bits (PFNC: Mono8) + VmbPixelFormatMono10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0003, //!< Monochrome, 10 bits in 16 bits (PFNC: Mono10) + VmbPixelFormatMono10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0046, //!< Monochrome, 10 bits in 16 bits (PFNC: Mono10p) + VmbPixelFormatMono12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0005, //!< Monochrome, 12 bits in 16 bits (PFNC: Mono12) + VmbPixelFormatMono12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x0006, //!< Monochrome, 2x12 bits in 24 bits (GEV:Mono12Packed) + VmbPixelFormatMono12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0047, //!< Monochrome, 2x12 bits in 24 bits (PFNC: MonoPacked) + VmbPixelFormatMono14 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0025, //!< Monochrome, 14 bits in 16 bits (PFNC: Mono14) + VmbPixelFormatMono16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0007, //!< Monochrome, 16 bits (PFNC: Mono16) + + // bayer formats + VmbPixelFormatBayerGR8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x0008, //!< Bayer-color, 8 bits, starting with GR line (PFNC: BayerGR8) + VmbPixelFormatBayerRG8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x0009, //!< Bayer-color, 8 bits, starting with RG line (PFNC: BayerRG8) + VmbPixelFormatBayerGB8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x000A, //!< Bayer-color, 8 bits, starting with GB line (PFNC: BayerGB8) + VmbPixelFormatBayerBG8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x000B, //!< Bayer-color, 8 bits, starting with BG line (PFNC: BayerBG8) + VmbPixelFormatBayerGR10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x000C, //!< Bayer-color, 10 bits in 16 bits, starting with GR line (PFNC: BayerGR10) + VmbPixelFormatBayerRG10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x000D, //!< Bayer-color, 10 bits in 16 bits, starting with RG line (PFNC: BayerRG10) + VmbPixelFormatBayerGB10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x000E, //!< Bayer-color, 10 bits in 16 bits, starting with GB line (PFNC: BayerGB10) + VmbPixelFormatBayerBG10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x000F, //!< Bayer-color, 10 bits in 16 bits, starting with BG line (PFNC: BayerBG10) + VmbPixelFormatBayerGR12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0010, //!< Bayer-color, 12 bits in 16 bits, starting with GR line (PFNC: BayerGR12) + VmbPixelFormatBayerRG12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0011, //!< Bayer-color, 12 bits in 16 bits, starting with RG line (PFNC: BayerRG12) + VmbPixelFormatBayerGB12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0012, //!< Bayer-color, 12 bits in 16 bits, starting with GB line (PFNC: BayerGB12) + VmbPixelFormatBayerBG12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0013, //!< Bayer-color, 12 bits in 16 bits, starting with BG line (PFNC: BayerBG12) + VmbPixelFormatBayerGR12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x002A, //!< Bayer-color, 2x12 bits in 24 bits, starting with GR line (GEV:BayerGR12Packed) + VmbPixelFormatBayerRG12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x002B, //!< Bayer-color, 2x12 bits in 24 bits, starting with RG line (GEV:BayerRG12Packed) + VmbPixelFormatBayerGB12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x002C, //!< Bayer-color, 2x12 bits in 24 bits, starting with GB line (GEV:BayerGB12Packed) + VmbPixelFormatBayerBG12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x002D, //!< Bayer-color, 2x12 bits in 24 bits, starting with BG line (GEV:BayerBG12Packed) + VmbPixelFormatBayerGR10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0056, //!< Bayer-color, 10 bits continuous packed, starting with GR line (PFNC: BayerGR10p) + VmbPixelFormatBayerRG10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0058, //!< Bayer-color, 10 bits continuous packed, starting with RG line (PFNC: BayerRG10p) + VmbPixelFormatBayerGB10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0054, //!< Bayer-color, 10 bits continuous packed, starting with GB line (PFNC: BayerGB10p) + VmbPixelFormatBayerBG10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0052, //!< Bayer-color, 10 bits continuous packed, starting with BG line (PFNC: BayerBG10p) + VmbPixelFormatBayerGR12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0057, //!< Bayer-color, 12 bits continuous packed, starting with GR line (PFNC: BayerGR12p) + VmbPixelFormatBayerRG12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0059, //!< Bayer-color, 12 bits continuous packed, starting with RG line (PFNC: BayerRG12p) + VmbPixelFormatBayerGB12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0055, //!< Bayer-color, 12 bits continuous packed, starting with GB line (PFNC: BayerGB12p) + VmbPixelFormatBayerBG12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0053, //!< Bayer-color, 12 bits continuous packed, starting with BG line (PFNC: BayerBG12p) + VmbPixelFormatBayerGR16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x002E, //!< Bayer-color, 16 bits, starting with GR line (PFNC: BayerGR16) + VmbPixelFormatBayerRG16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x002F, //!< Bayer-color, 16 bits, starting with RG line (PFNC: BayerRG16) + VmbPixelFormatBayerGB16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0030, //!< Bayer-color, 16 bits, starting with GB line (PFNC: BayerGB16) + VmbPixelFormatBayerBG16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0031, //!< Bayer-color, 16 bits, starting with BG line (PFNC: BayerBG16) + + // rgb formats + VmbPixelFormatRgb8 = VmbPixelColor | VmbPixelOccupy24Bit | 0x0014, //!< RGB, 8 bits x 3 (PFNC: RGB8) + VmbPixelFormatBgr8 = VmbPixelColor | VmbPixelOccupy24Bit | 0x0015, //!< BGR, 8 bits x 3 (PFNC: BGR8) + VmbPixelFormatRgb10 = VmbPixelColor | VmbPixelOccupy48Bit | 0x0018, //!< RGB, 12 bits in 16 bits x 3 (PFNC: RGB12) + VmbPixelFormatBgr10 = VmbPixelColor | VmbPixelOccupy48Bit | 0x0019, //!< RGB, 12 bits in 16 bits x 3 (PFNC: RGB12) + VmbPixelFormatRgb12 = VmbPixelColor | VmbPixelOccupy48Bit | 0x001A, //!< RGB, 12 bits in 16 bits x 3 (PFNC: RGB12) + VmbPixelFormatBgr12 = VmbPixelColor | VmbPixelOccupy48Bit | 0x001B, //!< RGB, 12 bits in 16 bits x 3 (PFNC: RGB12) + VmbPixelFormatRgb14 = VmbPixelColor | VmbPixelOccupy48Bit | 0x005E, //!< RGB, 14 bits in 16 bits x 3 (PFNC: RGB12) + VmbPixelFormatBgr14 = VmbPixelColor | VmbPixelOccupy48Bit | 0x004A, //!< RGB, 14 bits in 16 bits x 3 (PFNC: RGB12) + VmbPixelFormatRgb16 = VmbPixelColor | VmbPixelOccupy48Bit | 0x0033, //!< RGB, 16 bits x 3 (PFNC: RGB16) + VmbPixelFormatBgr16 = VmbPixelColor | VmbPixelOccupy48Bit | 0x004B, //!< RGB, 16 bits x 3 (PFNC: RGB16) + + // rgba formats + VmbPixelFormatArgb8 = VmbPixelColor | VmbPixelOccupy32Bit | 0x0016, //!< ARGB, 8 bits x 4 (PFNC: RGBa8) + VmbPixelFormatRgba8 = VmbPixelFormatArgb8, //!< RGBA, 8 bits x 4, legacy name + VmbPixelFormatBgra8 = VmbPixelColor | VmbPixelOccupy32Bit | 0x0017, //!< BGRA, 8 bits x 4 (PFNC: BGRa8) + VmbPixelFormatRgba10 = VmbPixelColor | VmbPixelOccupy64Bit | 0x005F, //!< RGBA, 8 bits x 4, legacy name + VmbPixelFormatBgra10 = VmbPixelColor | VmbPixelOccupy64Bit | 0x004C, //!< RGBA, 8 bits x 4, legacy name + VmbPixelFormatRgba12 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0061, //!< RGBA, 8 bits x 4, legacy name + VmbPixelFormatBgra12 = VmbPixelColor | VmbPixelOccupy64Bit | 0x004E, //!< RGBA, 8 bits x 4, legacy name + VmbPixelFormatRgba14 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0063, //!< RGBA, 8 bits x 4, legacy name + VmbPixelFormatBgra14 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0050, //!< RGBA, 8 bits x 4, legacy name + VmbPixelFormatRgba16 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0064, //!< RGBA, 8 bits x 4, legacy name + VmbPixelFormatBgra16 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0051, //!< RGBA, 8 bits x 4, legacy name + + // yuv/ycbcr formats + VmbPixelFormatYuv411 = VmbPixelColor | VmbPixelOccupy12Bit | 0x001E, //!< YUV 4:1:1 with 8 bits (PFNC: YUV411_8_UYYVYY, GEV:YUV411Packed) + VmbPixelFormatYuv422 = VmbPixelColor | VmbPixelOccupy16Bit | 0x001F, //!< YUV 4:2:2 with 8 bits (PFNC: YUV422_8_UYVY, GEV:YUV422Packed) + VmbPixelFormatYuv444 = VmbPixelColor | VmbPixelOccupy24Bit | 0x0020, //!< YUV 4:4:4 with 8 bits (PFNC: YUV8_UYV, GEV:YUV444Packed) + VmbPixelFormatYuv422_8 = VmbPixelColor | VmbPixelOccupy16Bit | 0x0032, //!< YUV 4:2:2 with 8 bits Channel order YUYV (PFNC: YUV422_8) + VmbPixelFormatYCbCr8_CbYCr = VmbPixelColor | VmbPixelOccupy24Bit | 0x003A, //!< YCbCr 4:4:4 with 8 bits (PFNC: YCbCr8_CbYCr) - identical to VmbPixelFormatYuv444 + VmbPixelFormatYCbCr422_8 = VmbPixelColor | VmbPixelOccupy16Bit | 0x003B, //!< YCbCr 4:2:2 8-bit YCbYCr (PFNC: YCbCr422_8) + VmbPixelFormatYCbCr411_8_CbYYCrYY = VmbPixelColor | VmbPixelOccupy12Bit | 0x003C, //!< YCbCr 4:1:1 with 8 bits (PFNC: YCbCr411_8_CbYYCrYY) - identical to VmbPixelFormatYuv411 + VmbPixelFormatYCbCr601_8_CbYCr = VmbPixelColor | VmbPixelOccupy24Bit | 0x003D, //!< YCbCr601 4:4:4 8-bit CbYCrt (PFNC: YCbCr601_8_CbYCr) + VmbPixelFormatYCbCr601_422_8 = VmbPixelColor | VmbPixelOccupy16Bit | 0x003E, //!< YCbCr601 4:2:2 8-bit YCbYCr (PFNC: YCbCr601_422_8) + VmbPixelFormatYCbCr601_411_8_CbYYCrYY = VmbPixelColor | VmbPixelOccupy12Bit | 0x003F, //!< YCbCr601 4:1:1 8-bit CbYYCrYY (PFNC: YCbCr601_411_8_CbYYCrYY) + VmbPixelFormatYCbCr709_8_CbYCr = VmbPixelColor | VmbPixelOccupy24Bit | 0x0040, //!< YCbCr709 4:4:4 8-bit CbYCr (PFNC: YCbCr709_8_CbYCr) + VmbPixelFormatYCbCr709_422_8 = VmbPixelColor | VmbPixelOccupy16Bit | 0x0041, //!< YCbCr709 4:2:2 8-bit YCbYCr (PFNC: YCbCr709_422_8) + VmbPixelFormatYCbCr709_411_8_CbYYCrYY = VmbPixelColor | VmbPixelOccupy12Bit | 0x0042, //!< YCbCr709 4:1:1 8-bit CbYYCrYY (PFNC: YCbCr709_411_8_CbYYCrYY) + VmbPixelFormatYCbCr422_8_CbYCrY = VmbPixelColor | VmbPixelOccupy16Bit | 0x0043, //!< YCbCr 4:2:2 with 8 bits (PFNC: YCbCr422_8_CbYCrY) - identical to VmbPixelFormatYuv422 + VmbPixelFormatYCbCr601_422_8_CbYCrY = VmbPixelColor | VmbPixelOccupy16Bit | 0x0044, //!< YCbCr601 4:2:2 8-bit CbYCrY (PFNC: YCbCr601_422_8_CbYCrY) + VmbPixelFormatYCbCr709_422_8_CbYCrY = VmbPixelColor | VmbPixelOccupy16Bit | 0x0045, //!< YCbCr709 4:2:2 8-bit CbYCrY (PFNC: YCbCr709_422_8_CbYCrY) + VmbPixelFormatYCbCr411_8 = VmbPixelColor | VmbPixelOccupy12Bit | 0x005A, //!< YCbCr 4:1:1 8-bit YYCbYYCr (PFNC: YCbCr411_8) + VmbPixelFormatYCbCr8 = VmbPixelColor | VmbPixelOccupy24Bit | 0x005B, //!< YCbCr 4:4:4 8-bit YCbCr (PFNC: YCbCr8) + + VmbPixelFormatLast, + } VmbPixelFormatType; + + /** + * \brief Type for the pixel format; for values see ::VmbPixelFormatType. + */ + typedef VmbUint32_t VmbPixelFormat_t; + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif // VMBCOMMONTYPES_H_INCLUDE_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbConstants.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbConstants.h new file mode 100644 index 000000000..95ebeb690 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbConstants.h @@ -0,0 +1,85 @@ +/*============================================================================= + Copyright (C) 2021 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + +------------------------------------------------------------------------------- + + File: VmbConstants.h + +------------------------------------------------------------------------------- + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ + +/** + * \file + * \brief File containing constants used in the Vmb C API. + */ + +#ifndef VMBCONSTANTS_H_INCLUDE_ +#define VMBCONSTANTS_H_INCLUDE_ + +#ifdef _WIN32 +/** + * \brief the character used to separate file paths in the parameter of ::VmbStartup + */ +#define VMB_PATH_SEPARATOR_CHAR L';' + +/** + * \brief the string used to separate file paths in the parameter of ::VmbStartup + */ +#define VMB_PATH_SEPARATOR_STRING L";" +#else +/** + * \brief the character used to separate file paths in the parameter of ::VmbStartup + */ +#define VMB_PATH_SEPARATOR_CHAR ':' + +/** + * \brief the string used to separate file paths in the parameter of ::VmbStartup + */ +#define VMB_PATH_SEPARATOR_STRING ":" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup SfncNamespaces Sfnc Namespace Constants + * \{ + */ + +/** + * \brief The C string identifying the namespace of features not defined in the SFNC + * standard. + */ +#define VMB_SFNC_NAMESPACE_CUSTOM "Custom" + +/** + * \brief The C string identifying the namespace of features defined in the SFNC + * standard. + */ +#define VMB_SFNC_NAMESPACE_STANDARD "Standard" + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif // VMBCONSTANTS_H_INCLUDE_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransform.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransform.h new file mode 100644 index 000000000..77d497eca --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransform.h @@ -0,0 +1,336 @@ +/*============================================================================= + Copyright (C) 2012 - 2021 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + +------------------------------------------------------------------------------- + + File: VmbTransform.h + + Description: Definition of image transform functions for the Vmb APIs. + +------------------------------------------------------------------------------- + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ + +/** + * \file + */ +#ifndef VMB_TRANSFORM_H_ +#define VMB_TRANSFORM_H_ +#ifndef VMB_TRANSFORM +#define VMB_TRANSFORM +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef VMBIMAGETRANSFORM_API +# ifndef VMB_NO_EXPORT +# ifdef VMB_EXPORTS +# if defined(__ELF__) && (defined(__clang__) || defined(__GNUC__)) +# define VMBIMAGETRANSFORM_API __attribute__((visibility("default"))) +# elif defined( __APPLE__ ) || defined(__MACH__) +# define VMBIMAGETRANSFORM_API __attribute__((visibility("default"))) +# else +# ifndef _WIN64 +# define VMBIMAGETRANSFORM_API __declspec(dllexport) __stdcall +# else +# define VMBIMAGETRANSFORM_API __stdcall +# endif +# endif +# else +# if defined (__ELF__) && (defined(__clang__) || defined(__GNUC__)) +# define VMBIMAGETRANSFORM_API +# elif defined( __APPLE__ ) || defined(__MACH__) +# define VMBIMAGETRANSFORM_API +# else +# define VMBIMAGETRANSFORM_API __declspec(dllimport) __stdcall +# endif +# endif +# else +# define VMBIMAGETRANSFORM_API +# endif +#endif + +/** + * \brief Inquire the library version. + * + * \param[out] value Contains the library version (Major,Minor,Sub,Build). + * + * This function can be called at anytime, even before the library is initialized. + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter \p value is null. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbGetImageTransformVersion ( VmbUint32_t* value ); + +/** + * \brief Get information about processor supported features. + * + * This should be called before using any SIMD (MMX,SSE) optimized functions. + * + * \param[out] technoInfo Returns the supported SIMD technologies. + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter If \p technoInfo is null. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbGetTechnoInfo( VmbTechInfo_t* technoInfo ); + +/** + * \brief Translate an Vmb error code to a human-readable string. + * + * \param[in] errorCode The error code to get a readable string for. + * \param[out] info Pointer to a zero terminated string the error description is written to. + * \param[in] maxInfoLength The size of the \p info buffer. + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter \p info is null, or if maxInfoLength is 0. + * + * \retval ::VmbErrorMoreData If \p maxInfoLength is too small to hold the complete information. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbGetErrorInfo(VmbError_t errorCode, + VmbANSIChar_t* info, + VmbUint32_t maxInfoLength ); + +/** + * \brief Get information about the currently loaded Vmb ImageTransform API. + * + * + * \p infoType may be one of the following values: + * - ::VmbAPIInfoAll: Returns all information about the API + * - ::VmbAPIInfoPlatform: Returns information about the platform the API was built for (x86 or x64) + * - ::VmbAPIInfoBuild: Returns info about the API built (debug or release) + * - ::VmbAPIInfoTechnology: Returns info about the supported technologies the API was built for (OpenMP or OpenCL) + * + * \param[in] infoType Type of information to return + * \param[out] info Pointer to a zero terminated string that + * \param[in] maxInfoLength The length of the \p info buffer + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter \p info is null. + * + * \retval ::VmbErrorMoreData If chars are insufficient \p maxInfoLength to hold the complete information. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbGetApiInfoString(VmbAPIInfo_t infoType, + VmbANSIChar_t* info, + VmbUint32_t maxInfoLength ); + +/** + * \brief Set transformation options to a predefined debayering mode. + * + * The default mode is 2x2 debayering. Debayering modes only work for image widths and heights + * divisible by two. + * + * \param[in] debayerMode The mode used for debayering the raw source image. + * + * \param[in,out] transformInfo Parameter that contains information about special + * transform functionality + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter \p transformInfo is null. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbSetDebayerMode(VmbDebayerMode_t debayerMode, + VmbTransformInfo* transformInfo ); + +/** + * \brief Set transformation options to a 3x3 color matrix transformation. + * + * \param[in] matrix Color correction matrix. + * + * \param[in,out] transformInfo Parameter that is filled with information + * about special transform functionality. + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter If \p matrix or \p transformInfo are null. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbSetColorCorrectionMatrix3x3(const VmbFloat_t* matrix, + VmbTransformInfo* transformInfo ); + +/** + * \brief Initialize the give VmbTransformInfo with gamma correction information. + * + * \param[in] gamma Float gamma correction to set + * \param[in,out] transformInfo Transform info to set gamma correction to + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter \p transformInfo is null. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbSetGammaCorrection(VmbFloat_t gamma, + VmbTransformInfo* transformInfo ); + +/** + * \brief Set the pixel related info of a VmbImage to the values appropriate for the given pixel format. + * + * A VmbPixelFormat_t can be obtained from Vmb C/C++ APIs frame. + * For displaying images, it is suggested to use ::VmbSetImageInfoFromString() or to look up + * a matching VmbPixelFormat_t. + * + * \param[in] pixelFormat The pixel format describes the pixel format to be used. + * \param[in] width The width of the image in pixels. + * \param[in] height The height of the image in pixels. + * \param[in,out] image A pointer to the image struct to write the info to. + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter If \p image is null or one of the members of \p image is invalid. + * + * \retval ::VmbErrorStructSize If the Size member of the image is incorrect. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbSetImageInfoFromPixelFormat(VmbPixelFormat_t pixelFormat, + VmbUint32_t width, + VmbUint32_t height, + VmbImage* image); + +/** + * \brief Set image info member values in VmbImage from string. + * + * This function does not read or write to VmbImage::Data member. + * + * \param[in] imageFormat The string containing the image format. This parameter is case insensitive. + * \param[in] width The width of the image in pixels. + * \param[in] height The height of the image in pixels. + * \param[in,out] image A pointer to the image struct to write the info to. + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter \p imageFormat or \p image are null. + * + * \retval ::VmbErrorStructSize The Size member of \p image contains an invalid value. + * + * \retval ::VmbErrorResources The function ran out of memory while processing. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbSetImageInfoFromString(const VmbANSIChar_t* imageFormat, + VmbUint32_t width, + VmbUint32_t height, + VmbImage* image); + +/** + * \brief Set output image dependent on the input image, user specifies pixel layout and bit depth of the out format. + * + * \param[in] inputPixelFormat Input Vmb pixel format + * \param[in] width width of the output image + * \param[in] height height of the output image + * \param[in] outputPixelLayout pixel component layout for output image + * \param[in] bitsPerPixel bit depth of output 8 and 16 supported + * \param[out] outputImage The output image to write the compatible format to. + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter \p outputImage is null. + * + * \retval ::VmbErrorStructSize The Size member of \p outputImage contains an invalid size. + * + * \retval ::VmbErrorNotImplemented No suitable transformation is implemented. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbSetImageInfoFromInputParameters(VmbPixelFormat_t inputPixelFormat, + VmbUint32_t width, + VmbUint32_t height, + VmbPixelLayout_t outputPixelLayout, + VmbUint32_t bitsPerPixel, + VmbImage* outputImage); + +/** + * \brief Set output image compatible to input image with given layout and bit depth. + * The output image will have same dimensions as the input image. + * + * \param[in] inputImage The input image with fully initialized image info elements. + * \param[in] outputPixelLayout The desired layout for the output image. + * \param[in] bitsPerPixel The desided bit depth for output image. 8 bit and 16 bit are supported. + * \param[out] outputImage The output image to write the compatible format to. + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter \p inputImage or \p outputImage are null, + * or the PixelInfo member of the ImageInfo member of the \p inputImage does not correspond to a supported pixel format. + * + * \retval ::VmbErrorStructSize The Size member of \p outputImage contains an invalid size. + * + * \retval ::VmbErrorNotImplemented No suitable transformation is implemented. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbSetImageInfoFromInputImage(const VmbImage* inputImage, + VmbPixelLayout_t outputPixelLayout, + VmbUint32_t bitsPerPixel, + VmbImage* outputImage); + +/** + * \brief Transform an image from one pixel format to another providing additional transformation options, if necessary. + * + * The transformation is defined by the provided images and the \p parameter. + * + * Create the source and destination image info structure with VmbSetImageInfoFromPixelFormat + * or VmbSetimageInfoFromString and keep those structures as template. + * For calls to transform, simply attach the image to the Data member. + * The optional parameters, when set, are constraints on the transform. + * + * \param[in] source The pointer to source image. + * \param[in,out] destination The pointer to destination image. + * \param[in] parameter An array of transform parameters; may be null. + * \param[in] parameterCount The number of transform parameters. + * + * \return An error code indicating success or the type of error. + * + * \retval ::VmbErrorSuccess The call was successful. + * + * \retval ::VmbErrorBadParameter if any image pointer or their "Data" members is NULL, or + * if "Width" or "Height" don't match between source and destination, or + * if one of the parameters for the conversion does not fit + * + * \retval ::VmbErrorStructSize The Size member of \p source or \p destination contain an invalid value. + * + * \retval ::VmbErrorNotImplemented The transformation from the format of \p source to the format of \p destination is not implemented. + */ +VmbError_t VMBIMAGETRANSFORM_API VmbImageTransform(const VmbImage* source, + VmbImage* destination, + const VmbTransformInfo* parameter, + VmbUint32_t parameterCount); + +#ifdef __cplusplus +} +#endif + +#endif // VMB_TRANSFORM_H_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransformTypes.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransformTypes.h new file mode 100644 index 000000000..c2438470c --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransformTypes.h @@ -0,0 +1,1018 @@ +/*============================================================================= + Copyright (C) 2012 - 2021 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + +------------------------------------------------------------------------------- + + File: VmbTransformTypes.h + + Description: Definition of types used in the Vmb Image Transform library. + +------------------------------------------------------------------------------- + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ + +/** + * \file + */ +#ifndef VMB_TRANSFORM_TYPES_H_ +#define VMB_TRANSFORM_TYPES_H_ + +#include + +/** + * \brief the type of character to use for strings. + */ +typedef char VmbANSIChar_t; + +/** + * \brief The floating point type to use for matrices. + */ +typedef float VmbFloat_t; + +/** + * \brief Enumeration for the Bayer pattern. + */ +typedef enum VmbBayerPattern +{ + VmbBayerPatternRGGB=0, //!< RGGB pattern, red pixel comes first + VmbBayerPatternGBRG, //!< RGGB pattern, green pixel of blue row comes first + VmbBayerPatternGRBG, //!< RGGB pattern, green pixel of red row comes first + VmbBayerPatternBGGR, //!< RGGB pattern, blue pixel comes first + VmbBayerPatternCYGM=128, //!< CYGM pattern, cyan pixel comes first in the first row, green in the second row (of the sensor) + VmbBayerPatternGMCY, //!< CYGM pattern, green pixel comes first in the first row, cyan in the second row (of the sensor) + VmbBayerPatternCYMG, //!< CYGM pattern, cyan pixel comes first in the first row, magenta in the second row (of the sensor) + VmbBayerPatternMGCY, //!< CYGM pattern, magenta pixel comes first in the first row, cyan in the second row (of the sensor) + VmbBayerPatternLAST=255 +} VmbBayerPattern; + +/** + * \brief Type for an error returned by API methods; for values see ::VmbBayerPattern. + */ +typedef VmbUint32_t VmbBayerPattern_t; + +/** + * \brief Enumeration for the endianness. + */ +typedef enum VmbEndianness +{ + VmbEndiannessLittle=0, //!< Little endian data format + VmbEndiannessBig, //!< Big endian data format + VmbEndiannessLast=255 +} VmbEndianness; + +/** + * \brief Type for the endianness; for values see ::VmbEndianness. + */ +typedef VmbUint32_t VmbEndianness_t; + +/** + * \brief Enumeration for the image alignment. + */ +typedef enum VmbAlignment +{ + VmbAlignmentMSB=0, //!< Data is MSB aligned (pppp pppp pppp ....) + VmbAlignmentLSB, //!< Data is LSB aligned (.... pppp pppp pppp) + VmbAlignmentLAST=255 +} VmbAlignment; + +/** + * \brief Enumeration for the image alignment; for values see ::VmbAlignment + */ +typedef VmbUint32_t VmbAlignment_t; + +/** + * \name Library Info + * \defgroup Library Info + * \{ + */ + +/** + * \brief States of the multi media technology support for operating system and processor. + */ +typedef struct VmbSupportState_t +{ + VmbBool_t Processor; //!< technology supported by the processor + VmbBool_t OperatingSystem; //!< technology supported by the OS +} VmbSupportState_t; + +/** + * \brief States of the support for different multimedia technologies + */ +typedef struct VmbTechInfo_t +{ + VmbSupportState_t IntelMMX; //!< INTEL first gen MultiMedia eXtension + VmbSupportState_t IntelSSE; //!< INTEL Streaming SIMD Extension + VmbSupportState_t IntelSSE2; //!< INTEL Streaming SIMD Extension 2 + VmbSupportState_t IntelSSE3; //!< INTEL Streaming SIMD Extension 3 + VmbSupportState_t IntelSSSE3; //!< INTEL Supplemental Streaming SIMD Extension 3 + VmbSupportState_t AMD3DNow; //!< AMD 3DNow +} VmbTechInfo_t; + +/** + * \brief API info types + */ +typedef enum VmbAPIInfo +{ + VmbAPIInfoAll, //!< All the info (platform, build type and technologies) + VmbAPIInfoPlatform, //!< Platform the api was build for + VmbAPIInfoBuild, //!< build type (debug or release) + VmbAPIInfoTechnology, //!< info about special technologies uses in building the API + VmbAPIInfoLast +} VmbAPIInfo; + +/** + * \brief API info type; for values see ::VmbAPIInfo + */ +typedef VmbUint32_t VmbAPIInfo_t; + +/** + * \} + */ + +/** + * \name Pixel Access Structs + * \defgroup Pixel Access Structs + * \{ + */ + +/** + * \brief Structure for accessing data in 12-bit transfer mode. + * + * Two pixel are coded into 3 bytes. + */ +typedef struct Vmb12BitPackedPair_t +{ + VmbUint8_t m_nVal8_1 ; //!< High byte of the first Pixel + VmbUint8_t m_nVal8_1Low : 4; //!< Low nibble of the first pixel + VmbUint8_t m_nVal8_2Low : 4; //!< Low nibble of the second pixel + VmbUint8_t m_nVal8_2 ; //!< High byte of the second pixel +} Vmb12BitPackedPair_t; + +/** + * \brief Struct for accessing data of a 8 bit grayscale image stored as unsigned integer. + * + * This corresponds to ::VmbPixelFormatMono8. + */ +typedef struct VmbMono8_t +{ +#ifdef __cplusplus + typedef VmbUint8_t value_type; +#endif + VmbUint8_t Y; //!< gray part +} VmbMono8_t; + +/** + * \brief Struct for accessing data of a 8 bit grayscale image stored as signed integer. + */ +typedef struct VmbMono8s_t +{ +#ifdef __cplusplus + typedef VmbInt8_t value_type; +#endif + VmbInt8_t Y; //!< gray part +} VmbMono8s_t; + +/** + * \brief Struct for accessing pixel data of a 10 bit mono padded buffer. + * + * The pixel data is LSB aligned and little endianness encoded on little endian systems. + * + * The pixel data is MSB aligned and big endianness encoded on big endian systems. + * + * On little endian systems this corresponds to ::VmbPixelFormatMono10. + */ +typedef struct VmbMono10_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t Y; //!< gray part +} VmbMono10_t; + +/** + * \brief Struct for accessing pixel data of a 12 bit mono padded buffer. + * + * The pixel data is LSB aligned and little endianness encoded on little endian systems. + * + * The pixel data is MSB aligned and big endianness encoded on big endian systems. + * + * On little endian systems this corresponds to ::VmbPixelFormatMono12. + */ +typedef struct VmbMono12_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t Y; //!< gray part +} VmbMono12_t; + +/** + * \brief Struct for accessing pixel data of a 14 bit mono padded buffer. + * + * The pixel data is LSB aligned and little endianness encoded on little endian systems. + * + * The pixel data is MSB aligned and big endianness encoded on big endian systems. + * + * On little endian systems this corresponds to ::VmbPixelFormatMono14. + */ +typedef struct VmbMono14_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t Y; //!< gray part +} VmbMono14_t; + +/** + * \brief Struct for accessing 16 bit grayscale image stored as unsigned integer. + * + * The pixel data is LSB aligned and little endianness encoded on little endian systems. + * + * The pixel data is MSB aligned and big endianness encoded on big endian systems. + * + * On little endian systems this corresponds to ::VmbPixelFormatMono16. + */ +typedef struct VmbMono16_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t Y; //!< gray part +} VmbMono16_t; + +/** + * \brief Struct for accessing 16 bit grayscale image stored as signed integer. + */ +typedef struct VmbMono16s_t +{ +#ifdef __cplusplus + typedef VmbInt16_t value_type; +#endif + VmbInt16_t Y; //!< gray part +} VmbMono16s_t; + +/** + * \brief Structure for accessing RGB data using 8 bit per channel. + * + * This corresponds to ::VmbPixelFormatRgb8 + */ +typedef struct VmbRGB8_t +{ +#ifdef __cplusplus + typedef VmbUint8_t value_type; +#endif + VmbUint8_t R; //!< red part + VmbUint8_t G; //!< green part + VmbUint8_t B; //!< blue part +} VmbRGB8_t; + +/** + * \brief Structure for accessing RGB data using 10 bit per channel padded to 16 bit; 48 bits per pixel are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatRgb10 on little endian systems. + */ +typedef struct VmbRGB10_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t R; //!< red part + VmbUint16_t G; //!< green part + VmbUint16_t B; //!< blue part +} VmbRGB10_t; + +/** + * \brief Structure for accessing RGB data using 12 bit per channel padded to 16 bit; 48 bits per pixel are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatRgb12 on little endian systems. + */ +typedef struct VmbRGB12_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t R; //!< red part + VmbUint16_t G; //!< green part + VmbUint16_t B; //!< blue part +} VmbRGB12_t; + +/** + * \brief Structure for accessing RGB data using 14 bit per channel padded to 16 bit; 48 bits per pixel are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatRgb14 on little endian systems. + */ +typedef struct VmbRGB14_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t R; //!< red part + VmbUint16_t G; //!< green part + VmbUint16_t B; //!< blue part +} VmbRGB14_t; + +/** + * \brief Struct for accessing RGB pixels stored as 16 bit unsigend integer per channel. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatRgb16 on little endian systems. + */ +typedef struct VmbRGB16_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t R; //!< red part + VmbUint16_t G; //!< green part + VmbUint16_t B; //!< blue part +} VmbRGB16_t; + +/** + * \brief Structure for accessing BGR data using 8 bit per channel. + * + * Corresponds to ::VmbPixelFormatBgr8 + */ +typedef struct VmbBGR8_t +{ +#ifdef __cplusplus + typedef VmbUint8_t value_type; +#endif + VmbUint8_t B; //!< blue part + VmbUint8_t G; //!< green part + VmbUint8_t R; //!< red part +} VmbBGR8_t; + +/** + * \brief Structure for accessing BGR data using 10 bit per channel padded to 16 bit; 48 bits per pixel are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatBgr10 on little endian systems. + */ +typedef struct VmbBGR10_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t B; //!< blue part + VmbUint16_t G; //!< green part + VmbUint16_t R; //!< red part +} VmbBGR10_t; + +/** + * \brief Structure for accessing BGR data using 12 bit per channel padded to 16 bit; 48 bits per pixel are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatBgr12 on little endian systems. + */ +typedef struct VmbBGR12_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t B; //!< blue part + VmbUint16_t G; //!< green part + VmbUint16_t R; //!< red part +} VmbBGR12_t; + +/** + * \brief Structure for accessing BGR data using 14 bit per channel padded to 16 bit; 48 bits per pixel are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatBgr14 on little endian systems. + */ +typedef struct VmbBGR14_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t B; //!< blue part + VmbUint16_t G; //!< green part + VmbUint16_t R; //!< red part +} VmbBGR14_t; + +/** + * \brief Structure for accessing BGR data using 16 bit per channel; 48 bits per pixel are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatBgr16 on little endian systems. + */ +typedef struct VmbBGR16_t +{ +#ifdef __cplusplus + typedef VmbUint16_t value_type; +#endif + VmbUint16_t B; //!< blue part + VmbUint16_t G; //!< green part + VmbUint16_t R; //!< red part +} VmbBGR16_t; + +/** + * \brief Structure for accessing RGBA data using 8 bit per channel. + */ +typedef struct VmbRGBA8_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint8_t value_type; +#endif + VmbUint8_t R; //!< red part + VmbUint8_t G; //!< green part + VmbUint8_t B; //!< blue part + VmbUint8_t A; //!< unused +} VmbRGBA8_t; + +/** + * \brief Alias for ::VmbRGBA8_t + */ +typedef VmbRGBA8_t VmbRGBA32_t; + +/** + * \brief Structure for accessing BGRA data using 8 bit per channel. + * + * This corresponds to ::VmbPixelFormatBgra8 + */ +typedef struct VmbBGRA8_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint8_t value_type; +#endif + VmbUint8_t B; //!< blue part + VmbUint8_t G; //!< green part + VmbUint8_t R; //!< red part + VmbUint8_t A; //!< unused +} VmbBGRA8_t; + +/** + * \brief Alias for ::VmbBGRA8_t + */ +typedef VmbBGRA8_t VmbBGRA32_t; + +/** + * \brief Struct for accessing ARGB values stored using a 8 bit unsigned integer per channel. + * + * Corresponds to ::VmbPixelFormatArgb8 + */ +typedef struct VmbARGB8_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint8_t value_type; +#endif + VmbUint8_t A; //!< unused + VmbUint8_t R; //!< red part + VmbUint8_t G; //!< green part + VmbUint8_t B; //!< blue part +} VmbARGB8_t; + +/** + * \brief Alias for ::VmbARGB8_t + */ +typedef VmbARGB8_t VmbARGB32_t; + +/** + * \brief Structure for accessing BGRA data using a 8 bit unsigned integer per channel. + */ +typedef struct VmbABGR8_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint8_t value_type; +#endif + VmbUint8_t A; //!< unused + VmbUint8_t B; //!< blue part + VmbUint8_t G; //!< green part + VmbUint8_t R; //!< red part +} VmbABGR8_t; + +/** + * \brief Alias for ::VmbABGR8_t + */ +typedef VmbABGR8_t VmbABGR32_t; + +/** + * \brief Structure for accessing RGBA data using 10 bit per channel padded to 16 bit; 64 bit are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatRgba10 on little endian systems. + */ +typedef struct VmbRGBA10_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint16_t value_type; +#endif + VmbUint16_t R; //!< red part + VmbUint16_t G; //!< green part + VmbUint16_t B; //!< blue part + VmbUint16_t A; //!< unused +} VmbRGBA10_t; + +/** + * \brief Structure for accessing BGRA data using 10 bit per channel padded to 16 bit; 64 bit are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatBgra10 on little endian systems. + */ +typedef struct VmbBGRA10_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint16_t value_type; +#endif + VmbUint16_t B; //!< blue part + VmbUint16_t G; //!< green part + VmbUint16_t R; //!< red part + VmbUint16_t A; //!< unused +} VmbBGRA10_t; + +/** + * \brief Structure for accessing RGBA data using 12 bit per channel padded to 16 bit; 64 bit are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatRgba12 on little endian systems. + */ +typedef struct VmbRGBA12_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint16_t value_type; +#endif + VmbUint16_t R; //!< red part + VmbUint16_t G; //!< green part + VmbUint16_t B; //!< blue part + VmbUint16_t A; //!< unused +} VmbRGBA12_t; + +/** + * \brief Structure for accessing RGBA data using 14 bit per channel padded to 16 bit; 64 bit are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatRgba14 on little endian systems. + */ +typedef struct VmbRGBA14_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint16_t value_type; +#endif + VmbUint16_t R; //!< red part + VmbUint16_t G; //!< green part + VmbUint16_t B; //!< blue part + VmbUint16_t A; //!< unused +} VmbRGBA14_t; + +/** + * \brief Structure for accessing BGRA data using 12 bit per channel padded to 16 bit; 64 bit are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatBgra12 on little endian systems. + */ +typedef struct VmbBGRA12_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint16_t value_type; +#endif + VmbUint16_t B; //!< blue part + VmbUint16_t G; //!< green part + VmbUint16_t R; //!< red part + VmbUint16_t A; //!< unused +} VmbBGRA12_t; + +/** + * \brief Structure for accessing BGRA data using 14 bit per channel padded to 16 bit; 64 bit are used in total. + * + * Each channel is LSB aligned and little endianness encoded on little endian systems. + * + * Each channel is MSB aligned and big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatBgra14 on little endian systems. + */ +typedef struct VmbBGRA14_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint16_t value_type; +#endif + VmbUint16_t B; //!< blue part + VmbUint16_t G; //!< green part + VmbUint16_t R; //!< red part + VmbUint16_t A; //!< unused +} VmbBGRA14_t; + +/** + * \brief Structure for accessing RGBA data using 16 bit per channel. + * + * Each channel is little endianness encoded on little endian systems. + * + * Each channel is big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatRgba16 on little endian systems. + */ +typedef struct VmbRGBA16_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint16_t value_type; +#endif + VmbUint16_t R; //!< red part + VmbUint16_t G; //!< green part + VmbUint16_t B; //!< blue part + VmbUint16_t A; //!< unused +} VmbRGBA16_t; + +/** + * \brief Alias for ::VmbRGBA16_t + */ +typedef VmbRGBA16_t VmbRGBA64_t; + +/** + * \brief Structure for accessing BGRA data using 16 bit per channel. + * + * Each channel is little endianness encoded on little endian systems. + * + * Each channel is big endianness encoded on big endian systems. + * + * Corresponds to ::VmbPixelFormatBgra16 on little endian systems. + */ +typedef struct VmbBGRA16_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint16_t value_type; +#endif + VmbUint16_t B; //!< blue part + VmbUint16_t G; //!< green part + VmbUint16_t R; //!< red part + VmbUint16_t A; //!< unused +} VmbBGRA16_t; + +/** + * \brief Alias for ::VmbBGRA64_t + */ +typedef VmbBGRA16_t VmbBGRA64_t; + +/** + * \brief Structure for accessing data in the YUV 4:4:4 format (YUV) prosilica component order. + * + * Corresponds to ::VmbPixelFormatYuv444 + */ +typedef struct VmbYUV444_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint8_t value_type; +#endif + VmbUint8_t U; //!< U + VmbUint8_t Y; //!< Luma + VmbUint8_t V; //!< V +} VmbYUV444_t; + +/** + * \brief Structure for accessing data in the YUV 4:2:2 format (UYVY) + * + * This struct provides data for 2 pixels (Y0, U, V) and (Y1, U, V) + * + * Corresponds to ::VmbPixelFormatYuv422 + */ +typedef struct VmbYUV422_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint8_t value_type; +#endif + VmbUint8_t U; //!< the U part for both pixels + VmbUint8_t Y0; //!< the intensity of the first pixel + VmbUint8_t V; //!< the V part for both pixels + VmbUint8_t Y1; //!< the intensity of the second pixel +} VmbYUV422_t; + +/** + * \brief Structure for accessing data in the YUV 4:1:1 format (UYYVYY) + * + * This struct provides data for 2 pixels (Y0, U, V), (Y1, U, V), (Y2, U, V) and (Y3, U, V) + * + * Corresponds to ::VmbPixelFormatYuv411 + */ +typedef struct VmbYUV411_t +{ +#ifdef __cplusplus + /** + * \brief The data type used to store one channel. + */ + typedef VmbUint8_t value_type; +#endif + VmbUint8_t U; //!< the U part for all four pixels + VmbUint8_t Y0; //!< the intensity of the first pixel + VmbUint8_t Y1; //!< the intensity of the second pixel + VmbUint8_t V; //!< the V part for all four pixels + VmbUint8_t Y2; //!< the intensity of the third pixel + VmbUint8_t Y3; //!< the intensity of the fourth pixel +} VmbYUV411_t; + +/** + * \} + */ + +/** + * \brief Image pixel layout information. + */ +typedef enum VmbPixelLayout +{ + VmbPixelLayoutMono, //!< Monochrome pixel data; pixels are padded, if necessary. + VmbPixelLayoutMonoPacked, //!< Monochrome pixel data; pixels some bytes contain data for more than one pixel. + VmbPixelLayoutRaw, //!< Some Bayer pixel format where pixels each byte contains only data for a single pixel. + VmbPixelLayoutRawPacked, //!< Some Bayer pixel format where some bytes contain data for more than one pixel. + VmbPixelLayoutRGB, //!< Non-packed RGB data in channel order R, G, B + VmbPixelLayoutBGR, //!< Non-packed RGB data in channel order B, G, R + VmbPixelLayoutRGBA, //!< Non-packed RGBA data in channel order R, G, B, A + VmbPixelLayoutBGRA, //!< Non-packed RGBA data in channel order B, G, R, A + VmbPixelLayoutYUV411_UYYVYY, //!< YUV data; pixel order for 4 pixels is U, Y0, Y1, V, Y2, Y3 + VmbPixelLayoutYUV411_YYUYYV, //!< YUV data; pixel order for 4 pixels is Y0, Y1, U, Y2, Y3, V + VmbPixelLayoutYUV422_UYVY, //!< YUV data; pixel order for 2 pixels is U, Y0, V, Y1 + VmbPixelLayoutYUV422_YUYV, //!< YUV data; pixel order for 2 pixels is Y0, U, Y1, V + VmbPixelLayoutYUV444_UYV, //!< YUV data; pixel order is U, Y, V + VmbPixelLayoutYUV444_YUV, //!< YUV data; pixel order is Y, U, V + VmbPixelLayoutMonoP, //!< Monochrome pixel data; pixels are padded, if necessary. \todo What is the difference to VmbPixelLayoutMono? + VmbPixelLayoutMonoPl, //!< \todo unused, remove? + VmbPixelLayoutRawP, //!< Some Bayer pixel format where pixels each byte contains only data for a single pixel. \todo What's the difference to VmbPixelLayoutRawPacked? + VmbPixelLayoutRawPl, //!< \todo unused, remove? + VmbPixelLayoutYYCbYYCr411 = VmbPixelLayoutYUV411_YYUYYV, //!< Alias for ::VmbPixelLayoutYUV411_YYUYYV + VmbPixelLayoutCbYYCrYY411 = VmbPixelLayoutYUV411_UYYVYY, //!< Alias for ::VmbPixelLayoutYUV411_UYYVYY + VmbPixelLayoutYCbYCr422 = VmbPixelLayoutYUV422_YUYV, //!< Alias for ::VmbPixelLayoutYUV422_YUYV + VmbPixelLayoutCbYCrY422 = VmbPixelLayoutYUV422_UYVY, //!< Alias for ::VmbPixelLayoutYUV422_UYVY + VmbPixelLayoutYCbCr444 = VmbPixelLayoutYUV444_YUV, //!< Alias for ::VmbPixelLayoutYUV444_YUV + VmbPixelLayoutCbYCr444 = VmbPixelLayoutYUV444_UYV, //!< Alias for ::VmbPixelLayoutYUV444_UYV + + VmbPixelLayoutLAST, +} VmbPixelLayout; + +/** + * \brief Image pixel layout information; for values see ::VmbPixelLayout + */ +typedef VmbUint32_t VmbPixelLayout_t; + +/** + * \brief Image color space information. + */ +typedef enum VmbColorSpace +{ + VmbColorSpaceUndefined, + VmbColorSpaceITU_BT709, //!< \todo color space description + VmbColorSpaceITU_BT601, //!< \todo color space description + +} VmbColorSpace; + +/** + * \brief Image color space information; for values see ::VmbColorSpace + */ +typedef VmbUint32_t VmbColorSpace_t; + +/** + * \brief Image pixel information + */ +typedef struct VmbPixelInfo +{ + VmbUint32_t BitsPerPixel; //!< The number of bits used to store the data for one pixel + VmbUint32_t BitsUsed; //!< The number of bits that actually contain data. + VmbAlignment_t Alignment; //!< Indicates, if the most significant or the least significant bit is filled for pixel formats not using all bits of the buffer to store data. + VmbEndianness_t Endianness; //!< Endianness of the pixel data + VmbPixelLayout_t PixelLayout; //!< Channel order, and in case of YUV formats relative order. + VmbBayerPattern_t BayerPattern; //!< The bayer pattern + VmbColorSpace_t Reserved; //!< Unused member reserved for future use. +} VmbPixelInfo; + +/** + * \brief Struct containing information about the image data in the Data member of a ::VmbImage. + */ +typedef struct VmbImageInfo +{ + VmbUint32_t Width; //!< The width of the image in pixels + VmbUint32_t Height; //!< The height of the image in pixels + VmbInt32_t Stride; //!< \todo description; do we actually use this + VmbPixelInfo PixelInfo; //!< Information about the pixel format +} VmbImageInfo; + +/** + * \brief vmb image type + */ +typedef struct VmbImage +{ + VmbUint32_t Size; //!< The size of this struct; If set incorrectly, API functions will return ::VmbErrorStructSize + void* Data; //!< The image data + VmbImageInfo ImageInfo; //!< Information about pixel format, size, and stride of the image. + +} VmbImage; + +/** + * \brief Transform info for special debayering modes. + */ +typedef enum VmbDebayerMode +{ + VmbDebayerMode2x2, //!< \todo description + VmbDebayerMode3x3, //!< \todo description + VmbDebayerModeLCAA, //!< \todo description + VmbDebayerModeLCAAV, //!< \todo description + VmbDebayerModeYUV422, //!< \todo description +} VmbDebayerMode; + +/** + * \brief Transform info for special debayering mode; for values see ::VmbDebayerMode + */ +typedef VmbUint32_t VmbDebayerMode_t; + +/** + * \name Transformation Parameters + * \defgroup Transformation Parameters + * \{ + */ + +/** + * \brief Transform parameter types. + */ +typedef enum VmbTransformType +{ + VmbTransformTypeNone, //!< Invalid type + VmbTransformTypeDebayerMode, //!< Debayering mode + VmbTransformTypeColorCorrectionMatrix, //!< Color correction matrix + VmbTransformTypeGammaCorrection, //!< Gamma correction + VmbTransformTypeOffset, //!< Offset + VmbTransformTypeGain, //!< Gain +} VmbTransformType; + +/** + * \brief Transform parameter type; for avalues see ::VmbTransformType + */ +typedef VmbUint32_t VmbTransformType_t; + +/** + * \brief Struct definition for holding the debayering mode. + * + * The struct is used to pass the data to ::VmbImageTransform via transform parameter. + * It corresponds to the ::VmbTransformTypeDebayerMode parameter type. + */ +typedef struct VmbTransformParameteDebayer +{ + VmbDebayerMode_t Method; //!< The DeBayering method to use. +} VmbTransformParameterDebayer; + + +/** + * \brief Transform info for color correction using a 3x3 matrix multiplication. + * + * The struct is used to pass the data to ::VmbImageTransform via transform parameter. + * It corresponds to the ::VmbTransformTypeColorCorrectionMatrix parameter type. + * + * \todo what does each index represent; how to get from 2d to 1d? + */ +typedef struct VmbTransformParameterMatrix3x3 +{ + VmbFloat_t Matrix[9]; //!< The color correction matrix to use for the transformation. +} VmbTransformParameterMatrix3x3; + +/** + * \brief Struct definition for a gamma value. + * + * This is currently not supported by ::VmbImageTransform. + * It corresponds to the ::VmbTransformTypeGammaCorrection parameter type. + */ +typedef struct VmbTransformParameterGamma +{ + VmbFloat_t Gamma; //!< The gamma value to use for the transformation +} VmbTransformParameterGamma; + +/** + * \brief Struct definition for holding the offset to pass via transform parameter. + * + * The struct is used to pass the data to ::VmbImageTransform via transform parameter. + * It corresponds to the ::VmbTransformTypeOffset parameter type. + */ +typedef struct VmbTransformParameterOffset +{ + VmbInt32_t Offset; //!< The offset to use for the transformation. +} VmbTransformParameterOffset; + +/** + * \brief Struct definition for holding the gain value. + * + * The struct is used to pass the data to ::VmbImageTransform via transform parameter. + * It corresponds to the ::VmbTransformTypeGain parameter type. + */ +typedef struct VmbTransformParameterGain +{ + VmbUint32_t Gain; //!< The gain to use for the transformation +} VmbTransformParameterGain; + +/** + * \brief Union for possible transformation parameter types. + * + * Each possible data type corresponds to a constant of ::VmbTransformType. + */ +typedef union VmbTransformParameter +{ + VmbTransformParameterMatrix3x3 Matrix3x3; //!< A matrix with 3 rows and 3 columns. + VmbTransformParameterDebayer Debayer; //!< A debayering mode + VmbTransformParameterGamma Gamma; //!< A gamma value + VmbTransformParameterOffset Offset; //!< \todo offset (is this even used?) + VmbTransformParameterGain Gain; //!< A gain value +} VmbTransformParameter; + +/** + * \} + */ + +/** + * \brief Transform info interface structure. + */ +typedef struct VmbTransformInfo +{ + VmbTransformType_t TransformType; //!< The type of the information stored in the Parameter member. + VmbTransformParameter Parameter; //!< The parameter data. +} VmbTransformInfo; + +#endif // VMB_TRANSFORM_TYPES_H_ diff --git a/micromanager.sln b/micromanager.sln index f994b2d34..f96e0037f 100644 --- a/micromanager.sln +++ b/micromanager.sln @@ -468,6 +468,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AtikCamera", "DeviceAdapter EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MPBLaser", "DeviceAdapters\MPBLaser\MPBLaser.vcxproj", "{5E193242-B386-49EF-B592-9ED3552D273D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AlliedVisionCamera", "DeviceAdapters\AlliedVisionCamera\AlliedVisionCamera.vcxproj", "{12E75CB0-4B48-4D7C-BB26-D928F18488C2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -1406,6 +1408,10 @@ Global {5E193242-B386-49EF-B592-9ED3552D273D}.Debug|x64.Build.0 = Debug|x64 {5E193242-B386-49EF-B592-9ED3552D273D}.Release|x64.ActiveCfg = Release|x64 {5E193242-B386-49EF-B592-9ED3552D273D}.Release|x64.Build.0 = Release|x64 + {12E75CB0-4B48-4D7C-BB26-D928F18488C2}.Debug|x64.ActiveCfg = Debug|x64 + {12E75CB0-4B48-4D7C-BB26-D928F18488C2}.Debug|x64.Build.0 = Debug|x64 + {12E75CB0-4B48-4D7C-BB26-D928F18488C2}.Release|x64.ActiveCfg = Release|x64 + {12E75CB0-4B48-4D7C-BB26-D928F18488C2}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From b533ed5d1c20721afb8757d86d3574e90b03ef5e Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Thu, 29 Jun 2023 20:56:08 +0200 Subject: [PATCH 008/141] Property page support Changes: - Property page support - draft version - Binning support - Exposure time support Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 677 ++++++++++++++++-- .../AlliedVisionCamera/AlliedVisionCamera.h | 138 +++- .../SDK/Loader/LibLoader.cpp | 16 +- .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 16 +- 4 files changed, 742 insertions(+), 105 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 3df9abb8e..2d1e71923 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -16,6 +16,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =============================================================================*/ +#define NOMINMAX // @@ -74,8 +76,6 @@ AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) m_frames{}, m_buffer{}, m_bufferSize{0}, - m_imageWidth{}, - m_imageHeight{}, m_isAcquisitionRunning{false} { // [Rule] Create properties here (pre-init only) InitializeDefaultErrorMessages(); @@ -131,9 +131,25 @@ const unsigned char* AlliedVisionCamera::GetImageBuffer() { return reinterpret_cast(m_buffer[0]); } -unsigned AlliedVisionCamera::GetImageWidth() const { return m_imageWidth; } +unsigned AlliedVisionCamera::GetImageWidth() const { + char value[MM::MaxStrLength]; + int ret = GetProperty(g_Width, value); + if (ret != DEVICE_OK) { + return 0; + } -unsigned AlliedVisionCamera::GetImageHeight() const { return m_imageHeight; } + return atoi(value); +} + +unsigned AlliedVisionCamera::GetImageHeight() const { + char value[MM::MaxStrLength]; + int ret = GetProperty(g_Height, value); + if (ret != DEVICE_OK) { + return 0; + } + + return atoi(value); +} unsigned AlliedVisionCamera::GetImageBytesPerPixel() const { // TODO implement @@ -200,46 +216,204 @@ int AlliedVisionCamera::SnapImage() { } long AlliedVisionCamera::GetImageBufferSize() const { return m_bufferSize; } + unsigned AlliedVisionCamera::GetBitDepth() const { // TODO implement return 8; } + int AlliedVisionCamera::GetBinning() const { - // TODO implement - return 1; + char value[MM::MaxStrLength]; + int ret = GetProperty(MM::g_Keyword_Binning, value); + if (ret != DEVICE_OK) { + return 0; + } + + return atoi(value); } + int AlliedVisionCamera::SetBinning(int binSize) { - // TODO implement - return VmbErrorSuccess; + return SetProperty(MM::g_Keyword_Binning, + CDeviceUtils::ConvertToString(binSize)); } -void AlliedVisionCamera::SetExposure(double exp_ms) { - // TODO implement + +int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, + MM::ActionType eAct) { + // Get horizonal binning + VmbFeatureInfo_t featureInfoHorizontal; + VmbError_t err = g_api->VmbFeatureInfoQuery_t( + m_handle, g_BinningHorizontalFeature, &featureInfoHorizontal, + sizeof(featureInfoHorizontal)); + if (VmbErrorSuccess != err) { + return err; + } + + // Get vertical binning + VmbFeatureInfo_t featureInfoVertical; + err = g_api->VmbFeatureInfoQuery_t(m_handle, g_BinningVerticalFeature, + &featureInfoVertical, + sizeof(featureInfoVertical)); + if (VmbErrorSuccess != err) { + return err; + } + + VmbInt64_t minHorizontal, maxHorizontal, minVertical, maxVertical, min, max; + std::string value, valueHorizontal, valueVertical; + bool rMode, wMode; + std::vector strValues; + + // Cast to Property to have an access to setting read/write mode + MM::Property* pChildProperty = (MM::Property*)pProp; + + // Get read/write mode - assume binning horizontal and vertical has the same + // mode + err = g_api->VmbFeatureAccessQuery_t(m_handle, g_BinningVerticalFeature, + &rMode, &wMode); + if (VmbErrorSuccess != err) { + return err; + } + + switch (eAct) { + case MM::ActionType::BeforeGet: + // Get horizontal binning value + err = getFeatureValue(&featureInfoHorizontal, g_BinningHorizontalFeature, + valueHorizontal); + if (VmbErrorSuccess != err) { + return err; + } + // Get vertical binning value + err = getFeatureValue(&featureInfoVertical, g_BinningVerticalFeature, + valueVertical); + if (VmbErrorSuccess != err) { + return err; + } + + // Get min value from these two + min = + std::min(atoi(valueHorizontal.c_str()), atoi(valueVertical.c_str())); + pProp->Set(std::to_string(min).c_str()); + + // Update binning limits + err = g_api->VmbFeatureIntRangeQuery_t( + m_handle, g_BinningHorizontalFeature, &minHorizontal, &maxHorizontal); + if (VmbErrorSuccess != err) { + return err; + } + + err = g_api->VmbFeatureIntRangeQuery_t(m_handle, g_BinningVerticalFeature, + &minVertical, &maxVertical); + if (VmbErrorSuccess != err) { + return err; + } + min = std::max(minHorizontal, minVertical); + max = std::min(maxHorizontal, maxVertical); + + for (VmbInt64_t i = min; i <= max; i++) { + strValues.push_back(std::to_string(i)); + } + SetAllowedValues(pProp->GetName().c_str(), strValues); + // Update access mode + pChildProperty->SetReadOnly(rMode && !wMode); + break; + case MM::ActionType::AfterSet: + pProp->Get(value); + err = setFeatureValue(&featureInfoHorizontal, g_BinningHorizontalFeature, + value); + if (VmbErrorSuccess != err) { + return err; + } + err = setFeatureValue(&featureInfoVertical, g_BinningVerticalFeature, + value); + break; + default: + // nothing + break; + } + + // TODO return uManager error + return err; } + double AlliedVisionCamera::GetExposure() const { - // TODO implement - return 8058.96; + char strExposure[MM::MaxStrLength]; + int ret = GetProperty(MM::g_Keyword_Exposure, strExposure); + if (ret != DEVICE_OK) { + return 0.0; + } + + return strtod(strExposure, nullptr); } + +void AlliedVisionCamera::SetExposure(double exp_ms) { + SetProperty(MM::g_Keyword_Exposure, CDeviceUtils::ConvertToString(exp_ms)); + GetCoreCallback()->OnExposureChanged(this, exp_ms); +} + +int AlliedVisionCamera::OnExposure(MM::PropertyBase* pProp, + MM::ActionType eAct) { + const auto propertyName = pProp->GetName(); + VmbFeatureInfo_t featureInfo; + VmbError_t err = g_api->VmbFeatureInfoQuery_t( + m_handle, g_ExposureFeature, &featureInfo, sizeof(featureInfo)); + if (VmbErrorSuccess != err) { + return err; + } + + std::string value; + bool rMode, wMode; + err = g_api->VmbFeatureAccessQuery_t(m_handle, propertyName.c_str(), &rMode, + &wMode); + MM::Property* pChildProperty = (MM::Property*)pProp; + + switch (eAct) { + case MM::ActionType::BeforeGet: + // Update limits + setAllowedValues(&featureInfo); + // Update access mode + pChildProperty->SetReadOnly(rMode && !wMode); + // Update value + err = getFeatureValue(&featureInfo, g_ExposureFeature, value); + pProp->Set(value.c_str()); + break; + case MM::ActionType::AfterSet: + pProp->Get(value); + err = setFeatureValue(&featureInfo, g_ExposureFeature, value); + break; + default: + // nothing + break; + } + + // TODO return uManager error + return err; +} + int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) { // TODO implement return VmbErrorSuccess; } + int AlliedVisionCamera::GetROI(unsigned& x, unsigned& y, unsigned& xSize, unsigned& ySize) { // TODO implement return VmbErrorSuccess; } + int AlliedVisionCamera::ClearROI() { // TODO implement return 0; } + int AlliedVisionCamera::IsExposureSequenceable(bool& isSequenceable) const { // TODO implement return VmbErrorSuccess; } + void AlliedVisionCamera::GetName(char* name) const { CDeviceUtils::CopyLimitedString(name, m_cameraName.c_str()); } + bool AlliedVisionCamera::IsCapturing() { return m_isAcquisitionRunning; } void AlliedVisionCamera::setApiErrorMessages() { @@ -255,7 +429,6 @@ void AlliedVisionCamera::setApiErrorMessages() { VmbError_t AlliedVisionCamera::getCamerasList() { VmbUint32_t camNum; - // Get the number of connected cameras first VmbError_t err = g_api->VmbCamerasList_t(nullptr, 0, &camNum, 0); if (VmbErrorSuccess == err) { @@ -278,17 +451,7 @@ VmbError_t AlliedVisionCamera::getCamerasList() { } VmbError_t AlliedVisionCamera::resizeImageBuffer() { - auto err = g_api->VmbFeatureIntGet_t(m_handle, "Width", &m_imageWidth); - if (err != VmbErrorSuccess) { - return err; - } - - err = g_api->VmbFeatureIntGet_t(m_handle, "Height", &m_imageHeight); - if (err != VmbErrorSuccess) { - return err; - } - - err = g_api->VmbPayloadSizeGet_t(m_handle, &m_bufferSize); + VmbError_t err = g_api->VmbPayloadSizeGet_t(m_handle, &m_bufferSize); if (err != VmbErrorSuccess) { return err; } @@ -298,77 +461,108 @@ VmbError_t AlliedVisionCamera::resizeImageBuffer() { m_buffer[i] = new VmbUint8_t[m_bufferSize]; } - return VmbErrorSuccess; + return err; } -int AlliedVisionCamera::OnPixelTypeChanged(MM::PropertyBase* pProp, - MM::ActionType eAct) { +int AlliedVisionCamera::OnPixelType(MM::PropertyBase* pProp, + MM::ActionType eAct) { // TODO implement - resizeImageBuffer(); return 0; } -int AlliedVisionCamera::OnBinningChanged(MM::PropertyBase* pProp, - MM::ActionType eAct) { - // TODO implement - resizeImageBuffer(); - return 0; -} +int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, + MM::ActionType eAct) { + const auto propertyName = pProp->GetName(); + VmbFeatureInfo_t featureInfo; + VmbError_t err = g_api->VmbFeatureInfoQuery_t( + m_handle, propertyName.c_str(), &featureInfo, sizeof(featureInfo)); + if (VmbErrorSuccess != err) { + return err; + } -VmbError_t AlliedVisionCamera::createPropertyFromFeature( - const VmbFeatureInfo_t* feature) { - // TODO - // Implemnet onProperyChanged for some properties and buffer resize - // Implement readOnly/WriteOnly reading - if (feature == nullptr) { - return VmbErrorInvalidValue; + std::string value{}; + bool rMode, wMode; + err = g_api->VmbFeatureAccessQuery_t(m_handle, propertyName.c_str(), &rMode, + &wMode); + MM::Property* pChildProperty = (MM::Property*)pProp; + switch (eAct) { + case MM::ActionType::BeforeGet: + // Update limits + setAllowedValues(&featureInfo); + // Update access mode + pChildProperty->SetReadOnly(rMode && !wMode); + // Update value + //TODO error handling + getFeatureValue(&featureInfo, propertyName.c_str(), value); + pProp->Set(value.c_str()); + break; + case MM::ActionType::AfterSet: + // Update value + pProp->Get(value); + // TODO error handling + setFeatureValue(&featureInfo, propertyName.c_str(), value); + break; + default: + // nothing + break; } + return err; +} + +VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, + const char* featureName, + std::string& value) { VmbError_t err = VmbErrorSuccess; - switch (feature->featureDataType) { + switch (featureInfo->featureDataType) { case VmbFeatureDataBool: { - VmbBool_t value; - err = g_api->VmbFeatureBoolGet_t(m_handle, feature->name, &value); - if (VmbErrorSuccess == err) { - CreateIntegerProperty(feature->name, value, true, nullptr); + VmbBool_t out; + err = g_api->VmbFeatureBoolGet_t(m_handle, featureName, &out); + if (err != VmbErrorSuccess) { + break; } + value = std::to_string(out); break; } case VmbFeatureDataEnum: { - char const* value = nullptr; - err = g_api->VmbFeatureEnumGet_t(m_handle, feature->name, &value); - if (VmbErrorSuccess == err) { - CreateStringProperty(feature->name, value, true, nullptr); + const char* out = nullptr; + err = g_api->VmbFeatureEnumGet_t(m_handle, featureName, &out); + if (err != VmbErrorSuccess) { + break; } + value = std::string(out); break; } case VmbFeatureDataFloat: { - double value; - err = g_api->VmbFeatureFloatGet_t(m_handle, feature->name, &value); - if (err == VmbErrorSuccess) { - CreateFloatProperty(feature->name, value, true, nullptr); + double out; + err = g_api->VmbFeatureFloatGet_t(m_handle, featureName, &out); + if (err != VmbErrorSuccess) { + break; } + value = std::to_string(out); break; } case VmbFeatureDataInt: { - VmbInt64_t value; - err = g_api->VmbFeatureIntGet_t(m_handle, feature->name, &value); - if (err == VmbErrorSuccess) { - CreateIntegerProperty(feature->name, value, true, nullptr); + VmbInt64_t out; + err = g_api->VmbFeatureIntGet_t(m_handle, featureName, &out); + if (err != VmbErrorSuccess) { + break; } + value = std::to_string(out); break; } case VmbFeatureDataString: { VmbUint32_t size = 0; - err = g_api->VmbFeatureStringGet_t(m_handle, feature->name, nullptr, 0, + err = g_api->VmbFeatureStringGet_t(m_handle, featureName, nullptr, 0, &size); if (VmbErrorSuccess == err && size > 0) { std::shared_ptr buff = std::shared_ptr(new char[size]); - err = g_api->VmbFeatureStringGet_t(m_handle, feature->name, buff.get(), + err = g_api->VmbFeatureStringGet_t(m_handle, featureName, buff.get(), size, &size); - if (VmbErrorSuccess == err) { - CreateStringProperty(feature->name, buff.get(), true, nullptr); + if (err != VmbErrorSuccess) { + break; } + value = std::string(buff.get()); } break; } @@ -377,7 +571,333 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( case VmbFeatureDataRaw: case VmbFeatureDataNone: default: - err = VmbErrorFeaturesUnavailable; + // nothing + break; + } + return err; +} + +VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, + const char* featureName, + std::string& value) { + VmbError_t err = VmbErrorSuccess; + std::stringstream ss(value); + + switch (featureInfo->featureDataType) { + case VmbFeatureDataBool: { + VmbBool_t out; + ss >> out; + err = g_api->VmbFeatureBoolSet_t(m_handle, featureName, out); + break; + } + case VmbFeatureDataEnum: { + err = g_api->VmbFeatureEnumSet_t(m_handle, featureName, value.c_str()); + break; + } + case VmbFeatureDataFloat: { + double out; + ss >> out; + err = g_api->VmbFeatureFloatSet_t(m_handle, featureName, out); + break; + } + case VmbFeatureDataInt: { + VmbInt64_t out; + ss >> out; + err = g_api->VmbFeatureIntSet_t(m_handle, featureName, out); + break; + } + case VmbFeatureDataString: { + err = g_api->VmbFeatureStringSet_t(m_handle, featureName, value.c_str()); + break; + } + case VmbFeatureDataCommand: + case VmbFeatureDataUnknown: + case VmbFeatureDataRaw: + case VmbFeatureDataNone: + default: + // nothing + break; + } + return err; +} + +VmbError_t AlliedVisionCamera::createPropertyFromFeature( + const VmbFeatureInfo_t* feature, MM::ActionFunctor* callback, + const char* propertyName, bool skipVmbCallback) { + if (feature == nullptr) { + return VmbErrorInvalidValue; + } + + auto featureName = feature->name; + auto propName = (propertyName != nullptr) ? propertyName : featureName; + VmbError_t err = VmbErrorSuccess; + + if (!skipVmbCallback) { + // Vmb callback for given feature + auto vmbCallback = [](VmbHandle_t handle, const char* name, + void* userContext) { + AlliedVisionCamera* camera = + reinterpret_cast(userContext); + camera->UpdateProperty(name); + }; + // Register VMb callback + err = g_api->VmbFeatureInvalidationRegister_t(m_handle, featureName, + vmbCallback, this); + if (err != VmbErrorSuccess) { + return err; + } + } + + if (HasProperty(propName)) { + // Already exist + return err; + } + + switch (feature->featureDataType) { + case VmbFeatureDataBool: { + CreateIntegerProperty(propName, 0, true, callback); + break; + } + case VmbFeatureDataEnum: { + CreateStringProperty(propName, "", true, callback); + break; + } + case VmbFeatureDataFloat: { + CreateFloatProperty(propName, 0.0, true, callback); + break; + } + case VmbFeatureDataInt: { + CreateIntegerProperty(propName, 0, true, callback); + break; + } + case VmbFeatureDataString: { + CreateStringProperty(propName, "", true, callback); + break; + } + case VmbFeatureDataCommand: + case VmbFeatureDataUnknown: + case VmbFeatureDataRaw: + case VmbFeatureDataNone: + default: + // nothing + break; + } + + return err; +} + +VmbError_t AlliedVisionCamera::createCoreProperties() { + VmbError_t err = VmbErrorSuccess; + //=== Create PIXEL_TYPE from PIXEL_FORMAT + { + VmbFeatureInfo_t feature; + err = g_api->VmbFeatureInfoQuery_t(m_handle, g_PixelFormatFeature, &feature, + sizeof(VmbFeatureInfo_t)); + if (err != VmbErrorSuccess) { + return err; + } + // uManager callback + CPropertyAction* callback = + new CPropertyAction(this, &AlliedVisionCamera::OnPixelType); + err = createPropertyFromFeature(&feature, callback, MM::g_Keyword_PixelType, + true); + if (err != VmbErrorSuccess) { + return err; + } + + err = setAllowedValues(&feature, MM::g_Keyword_PixelType); + if (err != VmbErrorSuccess) { + return err; + } + + // Vmb callback + auto vmbCallback = [](VmbHandle_t handle, const char* name, + void* userContext) { + AlliedVisionCamera* camera = + reinterpret_cast(userContext); + camera->UpdateProperty(MM::g_Keyword_PixelType); + }; + err = g_api->VmbFeatureInvalidationRegister_t( + m_handle, g_PixelFormatFeature, vmbCallback, this); + if (err != VmbErrorSuccess) { + return err; + } + } + + //=== Create EXPOSURE from EXPOSURE_TIME + { + VmbFeatureInfo_t feature; + err = g_api->VmbFeatureInfoQuery_t(m_handle, g_ExposureFeature, &feature, + sizeof(VmbFeatureInfo_t)); + if (err != VmbErrorSuccess) { + return err; + } + + // uManager callback + CPropertyAction* callback = + new CPropertyAction(this, &AlliedVisionCamera::OnExposure); + err = createPropertyFromFeature(&feature, callback, MM::g_Keyword_Exposure, + true); + if (err != VmbErrorSuccess) { + return err; + } + + err = setAllowedValues(&feature, MM::g_Keyword_Exposure); + if (err != VmbErrorSuccess) { + return err; + } + + // Vmb callback + auto vmbCallback = [](VmbHandle_t handle, const char* name, + void* userContext) { + AlliedVisionCamera* camera = + reinterpret_cast(userContext); + camera->UpdateProperty(MM::g_Keyword_Exposure); + }; + + err = g_api->VmbFeatureInvalidationRegister_t(m_handle, g_ExposureFeature, + vmbCallback, this); + if (err != VmbErrorSuccess) { + return err; + } + } + + //=== Create BINNING from BINNING_HORIZONTAL and BINNING_VERTICAL + { + VmbFeatureInfo_t feature; + err = g_api->VmbFeatureInfoQuery_t(m_handle, g_BinningHorizontalFeature, + &feature, sizeof(VmbFeatureInfo_t)); + if (err != VmbErrorSuccess) { + return err; + } + + // uManager callback + CPropertyAction* callback = + new CPropertyAction(this, &AlliedVisionCamera::OnBinning); + err = createPropertyFromFeature(&feature, callback, MM::g_Keyword_Binning, + true); + if (err != VmbErrorSuccess) { + return err; + } + + // Set limits for BINNING + VmbInt64_t minHorizontal, maxHorizontal, minVertical, maxVertical; + err = g_api->VmbFeatureIntRangeQuery_t(m_handle, g_BinningHorizontalFeature, + &minHorizontal, &maxHorizontal); + if (VmbErrorSuccess != err) { + return err; + } + + err = g_api->VmbFeatureIntRangeQuery_t(m_handle, g_BinningVerticalFeature, + &minVertical, &maxVertical); + if (VmbErrorSuccess != err) { + return err; + } + auto min = std::max(minHorizontal, minVertical); + auto max = std::min(maxHorizontal, maxVertical); + + std::vector strValues; + for (VmbInt64_t i = min; i <= max; i++) { + strValues.push_back(std::to_string(i)); + } + + SetAllowedValues(MM::g_Keyword_Binning, strValues); + + // Vmb callback for horizontal binning + auto vmbCallbackHorizontal = [](VmbHandle_t handle, const char* name, + void* userContext) { + AlliedVisionCamera* camera = + reinterpret_cast(userContext); + camera->UpdateProperty(MM::g_Keyword_Binning); + }; + + err = g_api->VmbFeatureInvalidationRegister_t( + m_handle, g_BinningHorizontalFeature, vmbCallbackHorizontal, this); + if (err != VmbErrorSuccess) { + return err; + } + + // Vmb callback for vertical binning + auto vmbCallbackVertical = [](VmbHandle_t handle, const char* name, + void* userContext) { + AlliedVisionCamera* camera = + reinterpret_cast(userContext); + camera->UpdateProperty(MM::g_Keyword_Binning); + }; + + err = g_api->VmbFeatureInvalidationRegister_t( + m_handle, g_BinningVerticalFeature, vmbCallbackVertical, this); + if (err != VmbErrorSuccess) { + return err; + } + } + + return err; +} + +VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, + const char* propertyName) { + if (feature == nullptr) { + return VmbErrorInvalidValue; + } + + auto propName = (propertyName != nullptr) ? propertyName : feature->name; + VmbError_t err = VmbErrorSuccess; + + switch (feature->featureDataType) { + case VmbFeatureDataBool: { + // TODO check if possible values needs to be set here + break; + } + case VmbFeatureDataFloat: { + double min = 0; + double max = 0; + err = g_api->VmbFeatureFloatRangeQuery_t(m_handle, feature->name, &min, + &max); + if (VmbErrorSuccess != err) { + return err; + } + + err = SetPropertyLimits(propName, min, max); + break; + } + case VmbFeatureDataEnum: { + std::array values; + std::vector strValues; + VmbUint32_t valuesNum = 0; + err = g_api->VmbFeatureEnumRangeQuery_t( + m_handle, feature->name, values.data(), MM::MaxStrLength, &valuesNum); + if (VmbErrorSuccess != err) { + return err; + } + + for (size_t i = 0; i < valuesNum; i++) { + strValues.push_back(values[i]); + } + SetAllowedValues(propName, strValues); + + break; + } + case VmbFeatureDataInt: { + VmbInt64_t min = 0; + VmbInt64_t max = 0; + err = + g_api->VmbFeatureIntRangeQuery_t(m_handle, feature->name, &min, &max); + if (VmbErrorSuccess != err) { + return err; + } + + err = SetPropertyLimits(propName, min, max); + break; + } + case VmbFeatureDataString: { + break; + } + case VmbFeatureDataRaw: + case VmbFeatureDataCommand: + case VmbFeatureDataNone: + default: + // nothing break; } @@ -394,24 +914,31 @@ VmbError_t AlliedVisionCamera::setupProperties() { std::shared_ptr features = std::shared_ptr(new VmbFeatureInfo_t[featureCount]); - err = g_api->VmbFeaturesList_t(m_handle, features.get(), featureCount, &featureCount, sizeof(VmbFeatureInfo_t)); - if (err != VmbErrorSuccess) { return err; } + // TODO handle error + err = createCoreProperties(); + const VmbFeatureInfo_t* end = features.get() + featureCount; for (VmbFeatureInfo_t* feature = features.get(); feature != end; ++feature) { - std::stringstream ss; - ss << "/// Feature Name: " << feature->name << "\n"; - ss << "/// Display Name: " << feature->displayName << "\n"; - ss << "/// Tooltip: " << feature->tooltip << "\n"; - ss << "/// Description: " << feature->description << "\n"; - ss << "/// SNFC Namespace: " << feature->sfncNamespace << "\n"; - LogMessage(ss.str().c_str()); - createPropertyFromFeature(feature); + auto featureName = std::string(feature->name); + // Skip these features as they are mapped to the Core Properties + if (featureName == std::string(g_PixelFormatFeature) || + featureName == std::string(g_BinningHorizontalFeature) || + featureName == std::string(g_BinningVerticalFeature) || + featureName == std::string(g_ExposureFeature)) { + continue; + } + + // uManager callback + CPropertyAction* callback = + new CPropertyAction(this, &AlliedVisionCamera::onProperty); + // TODO handle error + err = createPropertyFromFeature(feature, callback); } } @@ -512,8 +1039,8 @@ void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { md.put("Camera", m_cameraName); // TODO implement parameters - auto err = GetCoreCallback()->InsertImage(this, buffer, m_imageWidth, - m_imageHeight, 1, 1, + auto err = GetCoreCallback()->InsertImage(this, buffer, GetImageWidth(), + GetImageHeight(), 1, 1, md.Serialize().c_str()); if (err == DEVICE_BUFFER_OVERFLOW) { diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index a6b208606..8e3866c05 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -20,25 +20,39 @@ #define ALLIEDVISIONCAMERA_H #include +#include +#include #include "DeviceBase.h" #include "Loader/LibLoader.h" /** * @brief Pointer to the Vimba API -*/ + */ static std::unique_ptr g_api; +/////////////////////////////////////////////////////////////////////////////// +// STATIC FEATURE NAMES (FROM VIMBA) +/////////////////////////////////////////////////////////////////////////////// +static constexpr const char* g_PixelFormatFeature = "PixelFormat"; +static constexpr const char* g_ExposureFeature = "ExposureTime"; +static constexpr const char* g_BinningHorizontalFeature = "BinningHorizontal"; +static constexpr const char* g_BinningVerticalFeature = "BinningVertical"; +static constexpr const char* g_Width = "Width"; +static constexpr const char* g_Height = "Height"; + /** * @brief Main Allied Vision Camera class -*/ + */ class AlliedVisionCamera : public CCameraBase { + /////////////////////////////////////////////////////////////////////////////// // PUBLIC + /////////////////////////////////////////////////////////////////////////////// public: /** * @brief Contructor of Allied Vision Camera * @param[in] deviceName Device name - */ + */ AlliedVisionCamera(const char* deviceName); /** * @brief Allied Vision Camera destructor @@ -48,10 +62,12 @@ class AlliedVisionCamera : public CCameraBase { /** * @brief Get connected camera list * @return VmbError_t - */ + */ static VmbError_t getCamerasList(); - // API Methods + /////////////////////////////////////////////////////////////////////////////// + // uMANAGER API METHODS + /////////////////////////////////////////////////////////////////////////////// int Initialize() override; int Shutdown() override; const unsigned char* GetImageBuffer() override; @@ -77,56 +93,122 @@ class AlliedVisionCamera : public CCameraBase { int StopSequenceAcquisition() override; bool IsCapturing() override; - // Callbacks - int OnPixelTypeChanged(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnBinningChanged(MM::PropertyBase* pProp, MM::ActionType eAct); - - // Static variables - static constexpr const VmbUint8_t MAX_FRAMES = 7; - + /////////////////////////////////////////////////////////////////////////////// + // uMANAGER CALLBACKS + /////////////////////////////////////////////////////////////////////////////// + int OnPixelType(MM::PropertyBase* pProp, + MM::ActionType eAct); //!<< PixelType property callback + int OnBinning(MM::PropertyBase* pProp, + MM::ActionType eAct); //!<< Binning property callback + int OnExposure(MM::PropertyBase* pProp, + MM::ActionType eAct); //!<< Exposure property callback + int onProperty(MM::PropertyBase* pProp, + MM::ActionType eAct); //!<< General property callback + + /////////////////////////////////////////////////////////////////////////////// // PRIVATE + /////////////////////////////////////////////////////////////////////////////// private: + // Static variables + static constexpr const VmbUint8_t MAX_FRAMES = + 7; //!<< Max frame number in the buffer + /** * @brief Setup error messages for Vimba API - */ + */ void setApiErrorMessages(); /** * @brief Resize all buffers for image frames * @return VmbError_t - */ + */ VmbError_t resizeImageBuffer(); /** * @brief Setup uManager properties from Vimba features * @return VmbError_t - */ + */ VmbError_t setupProperties(); /** * @brief Helper method to create single uManager property from Vimba feature - * @param[in] feature Pointer to the Vimba feature + * @param[in] feature Pointer to the Vimba feature + * @return VmbError_t + */ + /** + * @brief Helper method to create single uManager property from Vimba feature + * @param[in] feature Pointer to the Vimba feature + * @param[in] callback uManager callback for given property + * @param[in] propertyName uManager propery name (if differs from + * feature name). By default nullptr + * @param[in] skipVmbCallback If set to true, VmbCallback will not be + * added to this feature. By default false + * @return VmbError_t + */ + VmbError_t createPropertyFromFeature(const VmbFeatureInfo_t* feature, + MM::ActionFunctor* callback, + const char* propertyName = nullptr, + bool skipVmbCallback = false); + + /** + * @brief Helper method to create core properties from feature. * @return VmbError_t - */ - VmbError_t createPropertyFromFeature(const VmbFeatureInfo_t* feature); + * + * It is used to create properties which names does not match to the Vimba + * feature. As example these are Binning, Exposure, PixelType + */ + VmbError_t createCoreProperties(); + + /** + * @brief Helper method to set allowed values for given property, based on + * its feature type + * @param[in] feature Vimba feature name + * @param[in] propertyName uManager propery name (if differs from + * feature name). By default nullptr + * @return + */ + VmbError_t setAllowedValues(const VmbFeatureInfo_t* feature, + const char* propertyName = nullptr); /** * @brief Insert ready frame to the uManager * @param[in] frame Pointer to the frame - */ + */ void insertFrame(VmbFrame_t* frame); - // MEMBERS - VmbHandle_t m_handle; // m_frames; // m_buffer; // m_frames; // m_buffer; // Date: Fri, 7 Jul 2023 17:49:46 +0200 Subject: [PATCH 009/141] Property browser and refactoring Changes: - Code refactoring - Property browser fixes and improvements Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 968 +++++++++--------- .../AlliedVisionCamera/AlliedVisionCamera.h | 69 +- .../AlliedVisionCamera.vcxproj | 2 + .../AlliedVisionCamera.vcxproj.filters | 6 + .../AlliedVisionCamera/PropertyItem.cpp | 5 + .../AlliedVisionCamera/PropertyItem.h | 43 + .../SDK/Loader/LibLoader.cpp | 10 +- .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 17 +- 8 files changed, 596 insertions(+), 524 deletions(-) create mode 100644 DeviceAdapters/AlliedVisionCamera/PropertyItem.cpp create mode 100644 DeviceAdapters/AlliedVisionCamera/PropertyItem.h diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 2d1e71923..07afa78b6 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -30,6 +30,23 @@ #include "ModuleInterface.h" #include "VmbC/VmbC.h" +/////////////////////////////////////////////////////////////////////////////// +// STATIC VALUES +/////////////////////////////////////////////////////////////////////////////// +const std::unordered_map + AlliedVisionCamera::m_featureToProperty = { + {g_PixelFormatFeature, MM::g_Keyword_PixelType}, + {g_ExposureFeature, MM::g_Keyword_Exposure}, + {g_BinningHorizontalFeature, MM::g_Keyword_Binning}, + {g_BinningVerticalFeature, MM::g_Keyword_Binning}}; + +const std::unordered_multimap + AlliedVisionCamera::m_propertyToFeature = { + {MM::g_Keyword_PixelType, g_PixelFormatFeature}, + {MM::g_Keyword_Exposure, g_ExposureFeature}, + {MM::g_Keyword_Binning, g_BinningHorizontalFeature}, + {MM::g_Keyword_Binning, g_BinningVerticalFeature}}; + /////////////////////////////////////////////////////////////////////////////// // Exported MMDevice API /////////////////////////////////////////////////////////////////////////////// @@ -107,6 +124,7 @@ int AlliedVisionCamera::Initialize() { } // Init properties and buffer + // TODO handle error setupProperties(); resizeImageBuffer(); @@ -127,92 +145,172 @@ int AlliedVisionCamera::Shutdown() { return DEVICE_OK; } -const unsigned char* AlliedVisionCamera::GetImageBuffer() { - return reinterpret_cast(m_buffer[0]); +void AlliedVisionCamera::setApiErrorMessages() { + SetErrorText(VmbErrorApiNotStarted, "Vimba X API not started"); + SetErrorText(VmbErrorNotFound, "Device cannot be found"); + SetErrorText(VmbErrorDeviceNotOpen, "Device cannot be opened"); + SetErrorText(VmbErrorBadParameter, + "Invalid parameter passed to the function"); + SetErrorText(VmbErrorNotImplemented, "Feature not implemented"); + SetErrorText(VmbErrorNotSupported, "Feature not supported"); + SetErrorText(VmbErrorUnknown, "Unknown error"); + SetErrorText(VmbErrorInvalidValue, + "The value is not valid: either out of bounds or not an " + "increment of the minimum"); } -unsigned AlliedVisionCamera::GetImageWidth() const { - char value[MM::MaxStrLength]; - int ret = GetProperty(g_Width, value); - if (ret != DEVICE_OK) { - return 0; +VmbError_t AlliedVisionCamera::setupProperties() { + VmbUint32_t featureCount = 0; + VmbError_t err = g_api->VmbFeaturesList_t(m_handle, NULL, 0, &featureCount, + sizeof(VmbFeatureInfo_t)); + if (err != VmbErrorSuccess || !featureCount) { + return err; } - return atoi(value); -} - -unsigned AlliedVisionCamera::GetImageHeight() const { - char value[MM::MaxStrLength]; - int ret = GetProperty(g_Height, value); - if (ret != DEVICE_OK) { - return 0; + std::shared_ptr features = + std::shared_ptr(new VmbFeatureInfo_t[featureCount]); + err = g_api->VmbFeaturesList_t(m_handle, features.get(), featureCount, + &featureCount, sizeof(VmbFeatureInfo_t)); + if (err != VmbErrorSuccess) { + return err; } - return atoi(value); -} + const VmbFeatureInfo_t* end = features.get() + featureCount; + for (VmbFeatureInfo_t* feature = features.get(); feature != end; ++feature) { + // uManager callback + CPropertyAction* callback = + new CPropertyAction(this, &AlliedVisionCamera::onProperty); -unsigned AlliedVisionCamera::GetImageBytesPerPixel() const { - // TODO implement - return 1; + err = createPropertyFromFeature(feature, callback); + if (err != VmbErrorSuccess) { + LogMessageCode(err); + continue; + } + } } -int AlliedVisionCamera::SnapImage() { - if (m_isAcquisitionRunning) { - return DEVICE_CAMERA_BUSY_ACQUIRING; - } - resizeImageBuffer(); +VmbError_t AlliedVisionCamera::getCamerasList() { + VmbUint32_t camNum; + // Get the number of connected cameras first + VmbError_t err = g_api->VmbCamerasList_t(nullptr, 0, &camNum, 0); + if (VmbErrorSuccess == err) { + VmbCameraInfo_t* camInfo = new VmbCameraInfo_t[camNum]; - VmbFrame_t frame; - frame.buffer = m_buffer[0]; - frame.bufferSize = m_bufferSize; + // Get the cameras + err = g_api->VmbCamerasList_t(camInfo, camNum, &camNum, sizeof *camInfo); - VmbError_t err = - g_api->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); - if (err != VmbErrorSuccess) { - return err; - } + if (err == VmbErrorSuccess) { + for (VmbUint32_t i = 0; i < camNum; ++i) { + RegisterDevice(camInfo[i].cameraIdString, MM::CameraDevice, + camInfo[i].cameraName); + } + } - err = g_api->VmbCaptureStart_t(m_handle); - if (err != VmbErrorSuccess) { - return err; + delete[] camInfo; } - err = g_api->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); + return err; +} + +VmbError_t AlliedVisionCamera::resizeImageBuffer() { + VmbError_t err = g_api->VmbPayloadSizeGet_t(m_handle, &m_bufferSize); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); - if (err != VmbErrorSuccess) { - return err; + for (size_t i = 0; i < MAX_FRAMES; i++) { + delete[] m_buffer[i]; + m_buffer[i] = new VmbUint8_t[m_bufferSize]; } - err = g_api->VmbCaptureFrameWait_t(m_handle, &frame, 3000); - if (err != VmbErrorSuccess) { - return err; + return err; +} + +VmbError_t AlliedVisionCamera::createPropertyFromFeature( + const VmbFeatureInfo_t* feature, MM::ActionFunctor* callback) { + if (feature == nullptr) { + return VmbErrorInvalidValue; } - err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); + auto featureName = feature->name; + VmbError_t err = VmbErrorSuccess; + std::string propName = {}; + mapFeatureNameToPropertyName(featureName, propName); + + // Vimba callback + auto vmbCallback = [](VmbHandle_t handle, const char* name, + void* userContext) { + AlliedVisionCamera* camera = + reinterpret_cast(userContext); + std::string propertyName; + camera->mapFeatureNameToPropertyName(name, propertyName); + camera->UpdateProperty(propertyName.c_str()); + }; + + // Add property to the list + m_propertyItems.insert({propName, {propName}}); + + // Register VMb callback + err = g_api->VmbFeatureInvalidationRegister_t(m_handle, featureName, + vmbCallback, this); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbCaptureEnd_t(m_handle); - if (err != VmbErrorSuccess) { - return err; + switch (feature->featureDataType) { + case VmbFeatureDataInt: { + err = CreateIntegerProperty(propName.c_str(), 0, true, callback); + break; + } + case VmbFeatureDataBool: + case VmbFeatureDataEnum: + case VmbFeatureDataCommand: + case VmbFeatureDataString: { + err = CreateStringProperty(propName.c_str(), "", true, callback); + break; + } + case VmbFeatureDataFloat: { + err = CreateFloatProperty(propName.c_str(), 0.0, true, callback); + break; + } + case VmbFeatureDataUnknown: + case VmbFeatureDataRaw: + case VmbFeatureDataNone: + default: + // nothing + break; } - err = g_api->VmbCaptureQueueFlush_t(m_handle); - if (err != VmbErrorSuccess) { - return err; + return err; +} + +const unsigned char* AlliedVisionCamera::GetImageBuffer() { + return reinterpret_cast(m_buffer[0]); +} + +unsigned AlliedVisionCamera::GetImageWidth() const { + char value[MM::MaxStrLength]; + int ret = GetProperty(g_Width, value); + if (ret != DEVICE_OK) { + return 0; } - err = g_api->VmbFrameRevokeAll_t(m_handle); - if (err != VmbErrorSuccess) { - return err; + return atoi(value); +} + +unsigned AlliedVisionCamera::GetImageHeight() const { + char value[MM::MaxStrLength]; + int ret = GetProperty(g_Height, value); + if (ret != DEVICE_OK) { + return 0; } - return err; + return atoi(value); +} + +unsigned AlliedVisionCamera::GetImageBytesPerPixel() const { + // TODO implement + return 1; } long AlliedVisionCamera::GetImageBufferSize() const { return m_bufferSize; } @@ -237,103 +335,6 @@ int AlliedVisionCamera::SetBinning(int binSize) { CDeviceUtils::ConvertToString(binSize)); } -int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, - MM::ActionType eAct) { - // Get horizonal binning - VmbFeatureInfo_t featureInfoHorizontal; - VmbError_t err = g_api->VmbFeatureInfoQuery_t( - m_handle, g_BinningHorizontalFeature, &featureInfoHorizontal, - sizeof(featureInfoHorizontal)); - if (VmbErrorSuccess != err) { - return err; - } - - // Get vertical binning - VmbFeatureInfo_t featureInfoVertical; - err = g_api->VmbFeatureInfoQuery_t(m_handle, g_BinningVerticalFeature, - &featureInfoVertical, - sizeof(featureInfoVertical)); - if (VmbErrorSuccess != err) { - return err; - } - - VmbInt64_t minHorizontal, maxHorizontal, minVertical, maxVertical, min, max; - std::string value, valueHorizontal, valueVertical; - bool rMode, wMode; - std::vector strValues; - - // Cast to Property to have an access to setting read/write mode - MM::Property* pChildProperty = (MM::Property*)pProp; - - // Get read/write mode - assume binning horizontal and vertical has the same - // mode - err = g_api->VmbFeatureAccessQuery_t(m_handle, g_BinningVerticalFeature, - &rMode, &wMode); - if (VmbErrorSuccess != err) { - return err; - } - - switch (eAct) { - case MM::ActionType::BeforeGet: - // Get horizontal binning value - err = getFeatureValue(&featureInfoHorizontal, g_BinningHorizontalFeature, - valueHorizontal); - if (VmbErrorSuccess != err) { - return err; - } - // Get vertical binning value - err = getFeatureValue(&featureInfoVertical, g_BinningVerticalFeature, - valueVertical); - if (VmbErrorSuccess != err) { - return err; - } - - // Get min value from these two - min = - std::min(atoi(valueHorizontal.c_str()), atoi(valueVertical.c_str())); - pProp->Set(std::to_string(min).c_str()); - - // Update binning limits - err = g_api->VmbFeatureIntRangeQuery_t( - m_handle, g_BinningHorizontalFeature, &minHorizontal, &maxHorizontal); - if (VmbErrorSuccess != err) { - return err; - } - - err = g_api->VmbFeatureIntRangeQuery_t(m_handle, g_BinningVerticalFeature, - &minVertical, &maxVertical); - if (VmbErrorSuccess != err) { - return err; - } - min = std::max(minHorizontal, minVertical); - max = std::min(maxHorizontal, maxVertical); - - for (VmbInt64_t i = min; i <= max; i++) { - strValues.push_back(std::to_string(i)); - } - SetAllowedValues(pProp->GetName().c_str(), strValues); - // Update access mode - pChildProperty->SetReadOnly(rMode && !wMode); - break; - case MM::ActionType::AfterSet: - pProp->Get(value); - err = setFeatureValue(&featureInfoHorizontal, g_BinningHorizontalFeature, - value); - if (VmbErrorSuccess != err) { - return err; - } - err = setFeatureValue(&featureInfoVertical, g_BinningVerticalFeature, - value); - break; - default: - // nothing - break; - } - - // TODO return uManager error - return err; -} - double AlliedVisionCamera::GetExposure() const { char strExposure[MM::MaxStrLength]; int ret = GetProperty(MM::g_Keyword_Exposure, strExposure); @@ -349,45 +350,6 @@ void AlliedVisionCamera::SetExposure(double exp_ms) { GetCoreCallback()->OnExposureChanged(this, exp_ms); } -int AlliedVisionCamera::OnExposure(MM::PropertyBase* pProp, - MM::ActionType eAct) { - const auto propertyName = pProp->GetName(); - VmbFeatureInfo_t featureInfo; - VmbError_t err = g_api->VmbFeatureInfoQuery_t( - m_handle, g_ExposureFeature, &featureInfo, sizeof(featureInfo)); - if (VmbErrorSuccess != err) { - return err; - } - - std::string value; - bool rMode, wMode; - err = g_api->VmbFeatureAccessQuery_t(m_handle, propertyName.c_str(), &rMode, - &wMode); - MM::Property* pChildProperty = (MM::Property*)pProp; - - switch (eAct) { - case MM::ActionType::BeforeGet: - // Update limits - setAllowedValues(&featureInfo); - // Update access mode - pChildProperty->SetReadOnly(rMode && !wMode); - // Update value - err = getFeatureValue(&featureInfo, g_ExposureFeature, value); - pProp->Set(value.c_str()); - break; - case MM::ActionType::AfterSet: - pProp->Get(value); - err = setFeatureValue(&featureInfo, g_ExposureFeature, value); - break; - default: - // nothing - break; - } - - // TODO return uManager error - return err; -} - int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) { // TODO implement @@ -416,51 +378,116 @@ void AlliedVisionCamera::GetName(char* name) const { bool AlliedVisionCamera::IsCapturing() { return m_isAcquisitionRunning; } -void AlliedVisionCamera::setApiErrorMessages() { - SetErrorText(VmbErrorApiNotStarted, "Vimba X API not started"); - SetErrorText(VmbErrorNotFound, "Device cannot be found"); - SetErrorText(VmbErrorDeviceNotOpen, "Device cannot be opened"); - SetErrorText(VmbErrorBadParameter, - "Invalid parameter passed to the function"); - SetErrorText(VmbErrorNotImplemented, "Feature not implemented"); - SetErrorText(VmbErrorNotSupported, "Feature not supported"); - SetErrorText(VmbErrorUnknown, "Unknown error"); -} +int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, + MM::ActionType eAct) { + // Get horizonal binning + VmbFeatureInfo_t featureInfoHorizontal; + VmbError_t err = g_api->VmbFeatureInfoQuery_t( + m_handle, g_BinningHorizontalFeature, &featureInfoHorizontal, + sizeof(featureInfoHorizontal)); + if (VmbErrorSuccess != err) { + return err; + } -VmbError_t AlliedVisionCamera::getCamerasList() { - VmbUint32_t camNum; - // Get the number of connected cameras first - VmbError_t err = g_api->VmbCamerasList_t(nullptr, 0, &camNum, 0); - if (VmbErrorSuccess == err) { - VmbCameraInfo_t* camInfo = new VmbCameraInfo_t[camNum]; + // Get vertical binning + VmbFeatureInfo_t featureInfoVertical; + err = g_api->VmbFeatureInfoQuery_t(m_handle, g_BinningVerticalFeature, + &featureInfoVertical, + sizeof(featureInfoVertical)); + if (VmbErrorSuccess != err) { + return err; + } - // Get the cameras - err = g_api->VmbCamerasList_t(camInfo, camNum, &camNum, sizeof *camInfo); + // Get read/write mode - assume binning horizontal and vertical has the same + // mode + bool rMode, wMode, readOnly, featureAvailable; + err = g_api->VmbFeatureAccessQuery_t(m_handle, g_BinningVerticalFeature, + &rMode, &wMode); + if (VmbErrorSuccess != err) { + return err; + } - if (err == VmbErrorSuccess) { - for (VmbUint32_t i = 0; i < camNum; ++i) { - RegisterDevice(camInfo[i].cameraIdString, MM::CameraDevice, - camInfo[i].cameraName); + featureAvailable = (rMode || wMode); + if (!featureAvailable) { + return err; + } + + readOnly = featureAvailable && (rMode && !wMode); + + // Get values of property and features + std::string propertyValue, featureHorizontalValue, featureVerticalValue; + pProp->Get(propertyValue); + err = getFeatureValue(&featureInfoHorizontal, g_BinningHorizontalFeature, + featureHorizontalValue); + if (VmbErrorSuccess != err) { + return err; + } + err = getFeatureValue(&featureInfoVertical, g_BinningVerticalFeature, + featureVerticalValue); + if (VmbErrorSuccess != err) { + return err; + } + + std::string featureValue = + std::to_string(std::min(atoi(featureHorizontalValue.c_str()), + atoi(featureVerticalValue.c_str()))); + + MM::Property* pChildProperty = (MM::Property*)pProp; + std::vector strValues; + VmbInt64_t minHorizontal, maxHorizontal, minVertical, maxVertical, min, max; + + switch (eAct) { + case MM::ActionType::BeforeGet: + // Update property + if (propertyValue != featureValue) { + pProp->Set(featureValue.c_str()); } - } + // Update property's access mode + pChildProperty->SetReadOnly(readOnly); + // Update property's limits and allowed values + if (!readOnly) { + // Update binning limits + err = g_api->VmbFeatureIntRangeQuery_t(m_handle, + g_BinningHorizontalFeature, + &minHorizontal, &maxHorizontal); + if (VmbErrorSuccess != err) { + return err; + } - delete[] camInfo; - } + err = g_api->VmbFeatureIntRangeQuery_t( + m_handle, g_BinningVerticalFeature, &minVertical, &maxVertical); + if (VmbErrorSuccess != err) { + return err; + } - return err; -} + //[IMPORTANT] For binning, increment step is ignored -VmbError_t AlliedVisionCamera::resizeImageBuffer() { - VmbError_t err = g_api->VmbPayloadSizeGet_t(m_handle, &m_bufferSize); - if (err != VmbErrorSuccess) { - return err; - } + min = std::max(minHorizontal, minVertical); + max = std::min(maxHorizontal, maxVertical); - for (size_t i = 0; i < MAX_FRAMES; i++) { - delete[] m_buffer[i]; - m_buffer[i] = new VmbUint8_t[m_bufferSize]; + for (VmbInt64_t i = min; i <= max; i++) { + strValues.push_back(std::to_string(i)); + } + err = SetAllowedValues(pProp->GetName().c_str(), strValues); + } + break; + case MM::ActionType::AfterSet: + if (propertyValue != featureValue) { + VmbError_t errHor = setFeatureValue( + &featureInfoHorizontal, g_BinningHorizontalFeature, propertyValue); + VmbError_t errVer = setFeatureValue( + &featureInfoVertical, g_BinningVerticalFeature, propertyValue); + if (VmbErrorSuccess != errHor || VmbErrorSuccess != errVer) { + //[IMPORTANT] For binning, adjust value is ignored + } + } + break; + default: + // nothing + break; } + //// TODO return uManager error return err; } @@ -472,39 +499,96 @@ int AlliedVisionCamera::OnPixelType(MM::PropertyBase* pProp, int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, MM::ActionType eAct) { + // Init + std::vector featureNames = {}; + VmbError_t err = VmbErrorSuccess; + MM::Property* pChildProperty = (MM::Property*)pProp; const auto propertyName = pProp->GetName(); - VmbFeatureInfo_t featureInfo; - VmbError_t err = g_api->VmbFeatureInfoQuery_t( - m_handle, propertyName.c_str(), &featureInfo, sizeof(featureInfo)); - if (VmbErrorSuccess != err) { - return err; - } - std::string value{}; - bool rMode, wMode; - err = g_api->VmbFeatureAccessQuery_t(m_handle, propertyName.c_str(), &rMode, - &wMode); - MM::Property* pChildProperty = (MM::Property*)pProp; - switch (eAct) { - case MM::ActionType::BeforeGet: - // Update limits - setAllowedValues(&featureInfo); - // Update access mode - pChildProperty->SetReadOnly(rMode && !wMode); - // Update value - //TODO error handling - getFeatureValue(&featureInfo, propertyName.c_str(), value); - pProp->Set(value.c_str()); - break; - case MM::ActionType::AfterSet: - // Update value - pProp->Get(value); - // TODO error handling - setFeatureValue(&featureInfo, propertyName.c_str(), value); - break; - default: - // nothing - break; + // Check property mapping + mapPropertyNameToFeatureNames(propertyName.c_str(), featureNames); + + if (propertyName == std::string(MM::g_Keyword_Binning)) { + // Binning requires special handling and combining two features into one + // property + OnBinning(pProp, eAct); + } else { + // Retrive each feature + for (const auto& featureName : featureNames) { + // Get Feature Info + VmbFeatureInfo_t featureInfo; + err = g_api->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), + &featureInfo, sizeof(featureInfo)); + if (VmbErrorSuccess != err) { + return err; + } + + // Get Access Mode + bool rMode, wMode, readOnly, featureAvailable; + err = g_api->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), + &rMode, &wMode); + if (VmbErrorSuccess != err) { + return err; + } + + featureAvailable = (rMode || wMode); + if (!featureAvailable) { + return err; + } + + readOnly = featureAvailable && (rMode && !wMode); + + // Get values + std::string propertyValue, featureValue; + pProp->Get(propertyValue); + err = getFeatureValue(&featureInfo, featureName.c_str(), featureValue); + if (VmbErrorSuccess != err) { + return err; + } + + // Handle property value change + switch (eAct) { + case MM::ActionType::BeforeGet: //!< Update property from feature + // Update property + if (propertyValue != featureValue) { + pProp->Set(featureValue.c_str()); + } + + // Update property's access mode + pChildProperty->SetReadOnly(readOnly); + // Update property's limits and allowed values + if (!readOnly) { + err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (VmbErrorSuccess != err) { + return err; + } + } + break; + case MM::ActionType::AfterSet: //!< Update feature from property + if (propertyValue != featureValue) { + err = setFeatureValue(&featureInfo, featureName.c_str(), + propertyValue); + if (err != VmbErrorSuccess) { + if (featureInfo.featureDataType == + VmbFeatureDataType::VmbFeatureDataFloat || + featureInfo.featureDataType == + VmbFeatureDataType::VmbFeatureDataInt) { + auto propertyItem = m_propertyItems.at(propertyName); + std::string adjustedValue = + adjustValue(propertyItem.m_min, propertyItem.m_max, + propertyItem.m_step, std::stod(propertyValue)); + pProp->Set(adjustedValue.c_str()); + (void)setFeatureValue(&featureInfo, featureName.c_str(), + adjustedValue); + } + } + } + break; + default: + // nothing + break; + } + } } return err; @@ -521,7 +605,7 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, if (err != VmbErrorSuccess) { break; } - value = std::to_string(out); + value = (out ? g_True : g_False); break; } case VmbFeatureDataEnum: { @@ -567,6 +651,7 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, break; } case VmbFeatureDataCommand: + // TODO case VmbFeatureDataUnknown: case VmbFeatureDataRaw: case VmbFeatureDataNone: @@ -585,8 +670,7 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, switch (featureInfo->featureDataType) { case VmbFeatureDataBool: { - VmbBool_t out; - ss >> out; + VmbBool_t out = (value == g_True ? true : false); err = g_api->VmbFeatureBoolSet_t(m_handle, featureName, out); break; } @@ -611,6 +695,7 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, break; } case VmbFeatureDataCommand: + // TODO case VmbFeatureDataUnknown: case VmbFeatureDataRaw: case VmbFeatureDataNone: @@ -621,232 +706,65 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, return err; } -VmbError_t AlliedVisionCamera::createPropertyFromFeature( - const VmbFeatureInfo_t* feature, MM::ActionFunctor* callback, - const char* propertyName, bool skipVmbCallback) { - if (feature == nullptr) { - return VmbErrorInvalidValue; - } - - auto featureName = feature->name; - auto propName = (propertyName != nullptr) ? propertyName : featureName; - VmbError_t err = VmbErrorSuccess; - - if (!skipVmbCallback) { - // Vmb callback for given feature - auto vmbCallback = [](VmbHandle_t handle, const char* name, - void* userContext) { - AlliedVisionCamera* camera = - reinterpret_cast(userContext); - camera->UpdateProperty(name); - }; - // Register VMb callback - err = g_api->VmbFeatureInvalidationRegister_t(m_handle, featureName, - vmbCallback, this); - if (err != VmbErrorSuccess) { - return err; - } - } - - if (HasProperty(propName)) { - // Already exist - return err; +void AlliedVisionCamera::mapFeatureNameToPropertyName( + const char* feature, std::string& property) const { + property = std::string(feature); + auto search = m_featureToProperty.find(property); + if (search != m_featureToProperty.end()) { + property = search->second; } - - switch (feature->featureDataType) { - case VmbFeatureDataBool: { - CreateIntegerProperty(propName, 0, true, callback); - break; - } - case VmbFeatureDataEnum: { - CreateStringProperty(propName, "", true, callback); - break; - } - case VmbFeatureDataFloat: { - CreateFloatProperty(propName, 0.0, true, callback); - break; - } - case VmbFeatureDataInt: { - CreateIntegerProperty(propName, 0, true, callback); - break; - } - case VmbFeatureDataString: { - CreateStringProperty(propName, "", true, callback); - break; +} +void AlliedVisionCamera::mapPropertyNameToFeatureNames( + const char* property, std::vector& featureNames) const { + // Check property mapping + auto searchRange = m_propertyToFeature.equal_range(property); + if (searchRange.first != m_propertyToFeature.end()) { + // Features that are mapped from property + for (auto it = searchRange.first; it != searchRange.second; ++it) { + featureNames.push_back(it->second); } - case VmbFeatureDataCommand: - case VmbFeatureDataUnknown: - case VmbFeatureDataRaw: - case VmbFeatureDataNone: - default: - // nothing - break; + } else { + // for rest + featureNames.push_back(property); } - - return err; } -VmbError_t AlliedVisionCamera::createCoreProperties() { - VmbError_t err = VmbErrorSuccess; - //=== Create PIXEL_TYPE from PIXEL_FORMAT - { - VmbFeatureInfo_t feature; - err = g_api->VmbFeatureInfoQuery_t(m_handle, g_PixelFormatFeature, &feature, - sizeof(VmbFeatureInfo_t)); - if (err != VmbErrorSuccess) { - return err; - } - // uManager callback - CPropertyAction* callback = - new CPropertyAction(this, &AlliedVisionCamera::OnPixelType); - err = createPropertyFromFeature(&feature, callback, MM::g_Keyword_PixelType, - true); - if (err != VmbErrorSuccess) { - return err; - } - - err = setAllowedValues(&feature, MM::g_Keyword_PixelType); - if (err != VmbErrorSuccess) { - return err; - } - - // Vmb callback - auto vmbCallback = [](VmbHandle_t handle, const char* name, - void* userContext) { - AlliedVisionCamera* camera = - reinterpret_cast(userContext); - camera->UpdateProperty(MM::g_Keyword_PixelType); - }; - err = g_api->VmbFeatureInvalidationRegister_t( - m_handle, g_PixelFormatFeature, vmbCallback, this); - if (err != VmbErrorSuccess) { - return err; - } +std::string AlliedVisionCamera::adjustValue(double min, double max, double step, + double propertyValue) const { + if (propertyValue > max) { + return std::to_string(max); } - - //=== Create EXPOSURE from EXPOSURE_TIME - { - VmbFeatureInfo_t feature; - err = g_api->VmbFeatureInfoQuery_t(m_handle, g_ExposureFeature, &feature, - sizeof(VmbFeatureInfo_t)); - if (err != VmbErrorSuccess) { - return err; - } - - // uManager callback - CPropertyAction* callback = - new CPropertyAction(this, &AlliedVisionCamera::OnExposure); - err = createPropertyFromFeature(&feature, callback, MM::g_Keyword_Exposure, - true); - if (err != VmbErrorSuccess) { - return err; - } - - err = setAllowedValues(&feature, MM::g_Keyword_Exposure); - if (err != VmbErrorSuccess) { - return err; - } - - // Vmb callback - auto vmbCallback = [](VmbHandle_t handle, const char* name, - void* userContext) { - AlliedVisionCamera* camera = - reinterpret_cast(userContext); - camera->UpdateProperty(MM::g_Keyword_Exposure); - }; - - err = g_api->VmbFeatureInvalidationRegister_t(m_handle, g_ExposureFeature, - vmbCallback, this); - if (err != VmbErrorSuccess) { - return err; - } + if (propertyValue < min) { + return std::to_string(min); } - //=== Create BINNING from BINNING_HORIZONTAL and BINNING_VERTICAL - { - VmbFeatureInfo_t feature; - err = g_api->VmbFeatureInfoQuery_t(m_handle, g_BinningHorizontalFeature, - &feature, sizeof(VmbFeatureInfo_t)); - if (err != VmbErrorSuccess) { - return err; - } - - // uManager callback - CPropertyAction* callback = - new CPropertyAction(this, &AlliedVisionCamera::OnBinning); - err = createPropertyFromFeature(&feature, callback, MM::g_Keyword_Binning, - true); - if (err != VmbErrorSuccess) { - return err; - } - - // Set limits for BINNING - VmbInt64_t minHorizontal, maxHorizontal, minVertical, maxVertical; - err = g_api->VmbFeatureIntRangeQuery_t(m_handle, g_BinningHorizontalFeature, - &minHorizontal, &maxHorizontal); - if (VmbErrorSuccess != err) { - return err; - } - - err = g_api->VmbFeatureIntRangeQuery_t(m_handle, g_BinningVerticalFeature, - &minVertical, &maxVertical); - if (VmbErrorSuccess != err) { - return err; - } - auto min = std::max(minHorizontal, minVertical); - auto max = std::min(maxHorizontal, maxVertical); - - std::vector strValues; - for (VmbInt64_t i = min; i <= max; i++) { - strValues.push_back(std::to_string(i)); - } - - SetAllowedValues(MM::g_Keyword_Binning, strValues); + VmbInt64_t factor = static_cast((propertyValue - min) / step); + double prev = min + factor * step; + double next = min + (factor + 1) * step; - // Vmb callback for horizontal binning - auto vmbCallbackHorizontal = [](VmbHandle_t handle, const char* name, - void* userContext) { - AlliedVisionCamera* camera = - reinterpret_cast(userContext); - camera->UpdateProperty(MM::g_Keyword_Binning); - }; + double prevDiff = abs(propertyValue - prev); + double nextDiff = abs(next - propertyValue); - err = g_api->VmbFeatureInvalidationRegister_t( - m_handle, g_BinningHorizontalFeature, vmbCallbackHorizontal, this); - if (err != VmbErrorSuccess) { - return err; - } - - // Vmb callback for vertical binning - auto vmbCallbackVertical = [](VmbHandle_t handle, const char* name, - void* userContext) { - AlliedVisionCamera* camera = - reinterpret_cast(userContext); - camera->UpdateProperty(MM::g_Keyword_Binning); - }; - - err = g_api->VmbFeatureInvalidationRegister_t( - m_handle, g_BinningVerticalFeature, vmbCallbackVertical, this); - if (err != VmbErrorSuccess) { - return err; - } - } - - return err; + return (nextDiff < prevDiff) ? std::to_string(next) : std::to_string(prev); } VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, const char* propertyName) { - if (feature == nullptr) { + if (feature == nullptr || propertyName == nullptr) { return VmbErrorInvalidValue; } - auto propName = (propertyName != nullptr) ? propertyName : feature->name; VmbError_t err = VmbErrorSuccess; + auto search = m_propertyItems.find(propertyName); + if (search == m_propertyItems.end()) { + LogMessage("Cannot find propery on internal list"); + return VmbErrorInvalidValue; + } switch (feature->featureDataType) { case VmbFeatureDataBool: { - // TODO check if possible values needs to be set here + AddAllowedValue(propertyName, g_False); + AddAllowedValue(propertyName, g_True); break; } case VmbFeatureDataFloat: { @@ -854,11 +772,30 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, double max = 0; err = g_api->VmbFeatureFloatRangeQuery_t(m_handle, feature->name, &min, &max); + if (VmbErrorSuccess != err || min == max) { + return err; + } + + double step = 0.0; + bool isIncremental = false; + err = g_api->VmbFeatureFloatIncrementQuery_t(m_handle, feature->name, + &isIncremental, &step); if (VmbErrorSuccess != err) { return err; } - err = SetPropertyLimits(propName, min, max); + PropertyItem tempProp{propertyName, static_cast(min), + static_cast(max), + static_cast(step)}; + if (tempProp == search->second) { + break; + } + + m_propertyItems.at(propertyName).m_min = static_cast(min); + m_propertyItems.at(propertyName).m_max = static_cast(max); + m_propertyItems.at(propertyName).m_step = static_cast(step); + + err = SetPropertyLimits(propertyName, min, max); break; } case VmbFeatureDataEnum: { @@ -874,20 +811,38 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, for (size_t i = 0; i < valuesNum; i++) { strValues.push_back(values[i]); } - SetAllowedValues(propName, strValues); + err = SetAllowedValues(propertyName, strValues); break; } case VmbFeatureDataInt: { - VmbInt64_t min = 0; - VmbInt64_t max = 0; + VmbInt64_t min, max, step; + std::vector strValues; + err = g_api->VmbFeatureIntRangeQuery_t(m_handle, feature->name, &min, &max); + if (VmbErrorSuccess != err || min == max) { + return err; + } + + err = + g_api->VmbFeatureIntIncrementQuery_t(m_handle, feature->name, &step); if (VmbErrorSuccess != err) { return err; } - err = SetPropertyLimits(propName, min, max); + PropertyItem tempProp{propertyName, static_cast(min), + static_cast(max), + static_cast(step)}; + if (tempProp == search->second) { + break; + } + + m_propertyItems.at(propertyName).m_min = static_cast(min); + m_propertyItems.at(propertyName).m_max = static_cast(max); + m_propertyItems.at(propertyName).m_step = static_cast(step); + + err = SetPropertyLimits(propertyName, min, max); break; } case VmbFeatureDataString: { @@ -904,42 +859,63 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, return err; } -VmbError_t AlliedVisionCamera::setupProperties() { - VmbUint32_t featureCount = 0; - VmbError_t err = g_api->VmbFeaturesList_t(m_handle, NULL, 0, &featureCount, - sizeof(VmbFeatureInfo_t)); - if (err != VmbErrorSuccess || !featureCount) { +int AlliedVisionCamera::SnapImage() { + if (m_isAcquisitionRunning) { + return DEVICE_CAMERA_BUSY_ACQUIRING; + } + resizeImageBuffer(); + + VmbFrame_t frame; + frame.buffer = m_buffer[0]; + frame.bufferSize = m_bufferSize; + + VmbError_t err = + g_api->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); + if (err != VmbErrorSuccess) { return err; } - std::shared_ptr features = - std::shared_ptr(new VmbFeatureInfo_t[featureCount]); - err = g_api->VmbFeaturesList_t(m_handle, features.get(), featureCount, - &featureCount, sizeof(VmbFeatureInfo_t)); + err = g_api->VmbCaptureStart_t(m_handle); if (err != VmbErrorSuccess) { return err; } - // TODO handle error - err = createCoreProperties(); + err = g_api->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); + if (err != VmbErrorSuccess) { + return err; + } - const VmbFeatureInfo_t* end = features.get() + featureCount; - for (VmbFeatureInfo_t* feature = features.get(); feature != end; ++feature) { - auto featureName = std::string(feature->name); - // Skip these features as they are mapped to the Core Properties - if (featureName == std::string(g_PixelFormatFeature) || - featureName == std::string(g_BinningHorizontalFeature) || - featureName == std::string(g_BinningVerticalFeature) || - featureName == std::string(g_ExposureFeature)) { - continue; - } + err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); + if (err != VmbErrorSuccess) { + return err; + } - // uManager callback - CPropertyAction* callback = - new CPropertyAction(this, &AlliedVisionCamera::onProperty); - // TODO handle error - err = createPropertyFromFeature(feature, callback); + err = g_api->VmbCaptureFrameWait_t(m_handle, &frame, 3000); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbCaptureEnd_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbCaptureQueueFlush_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } + + err = g_api->VmbFrameRevokeAll_t(m_handle); + if (err != VmbErrorSuccess) { + return err; } + + return err; } int AlliedVisionCamera::StartSequenceAcquisition(long numImages, @@ -1032,7 +1008,7 @@ int AlliedVisionCamera::StopSequenceAcquisition() { void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { if (frame != nullptr && frame->receiveStatus == VmbFrameStatusComplete) { - VmbUint8_t* buffer = reinterpret_cast(frame->buffer); + VmbUint8_t* buffer = reinterpret_cast(frame->imageData); // TODO implement metadata Metadata md; diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 8e3866c05..3decc5603 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -25,6 +25,7 @@ #include "DeviceBase.h" #include "Loader/LibLoader.h" +#include "PropertyItem.h" /** * @brief Pointer to the Vimba API @@ -41,6 +42,11 @@ static constexpr const char* g_BinningVerticalFeature = "BinningVertical"; static constexpr const char* g_Width = "Width"; static constexpr const char* g_Height = "Height"; +/////////////////////////////////////////////////////////////////////////////// +// STATIC VARIABLES +/////////////////////////////////////////////////////////////////////////////// +static constexpr const char* g_True = "True"; +static constexpr const char* g_False = "False"; /** * @brief Main Allied Vision Camera class */ @@ -100,8 +106,6 @@ class AlliedVisionCamera : public CCameraBase { MM::ActionType eAct); //!<< PixelType property callback int OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct); //!<< Binning property callback - int OnExposure(MM::PropertyBase* pProp, - MM::ActionType eAct); //!<< Exposure property callback int onProperty(MM::PropertyBase* pProp, MM::ActionType eAct); //!<< General property callback @@ -130,45 +134,25 @@ class AlliedVisionCamera : public CCameraBase { */ VmbError_t setupProperties(); - /** - * @brief Helper method to create single uManager property from Vimba feature - * @param[in] feature Pointer to the Vimba feature - * @return VmbError_t - */ /** * @brief Helper method to create single uManager property from Vimba feature * @param[in] feature Pointer to the Vimba feature * @param[in] callback uManager callback for given property - * @param[in] propertyName uManager propery name (if differs from - * feature name). By default nullptr - * @param[in] skipVmbCallback If set to true, VmbCallback will not be - * added to this feature. By default false * @return VmbError_t */ VmbError_t createPropertyFromFeature(const VmbFeatureInfo_t* feature, - MM::ActionFunctor* callback, - const char* propertyName = nullptr, - bool skipVmbCallback = false); - - /** - * @brief Helper method to create core properties from feature. - * @return VmbError_t - * - * It is used to create properties which names does not match to the Vimba - * feature. As example these are Binning, Exposure, PixelType - */ - VmbError_t createCoreProperties(); + MM::ActionFunctor* callback); /** * @brief Helper method to set allowed values for given property, based on * its feature type * @param[in] feature Vimba feature name * @param[in] propertyName uManager propery name (if differs from - * feature name). By default nullptr + * feature name) * @return */ VmbError_t setAllowedValues(const VmbFeatureInfo_t* feature, - const char* propertyName = nullptr); + const char* propertyName); /** * @brief Insert ready frame to the uManager @@ -198,6 +182,35 @@ class AlliedVisionCamera : public CCameraBase { VmbError_t setFeatureValue(VmbFeatureInfo_t* featureInfo, const char* featureName, std::string& value); + /** + * @brief Helper method to map feature name into property name of uManager + * @param[in] feature Vimba Feature name + * @param property uManager property name + */ + void mapFeatureNameToPropertyName(const char* feature, + std::string& property) const; + + /** + * @brief Helper method to map uManager property in Vimba feature or features + * name + * @param[in] property uManager property name + * @param featureNames Vimba feature or features name + */ + void mapPropertyNameToFeatureNames( + const char* property, std::vector& featureNames) const; + + /** + * @brief In case trying to set invalid value, adjust it to the closest with + * inceremntal step + * @param[in] min Minimum for given property + * @param[in] max Maximum for given property + * @param[in] step Incremental step + * @param[in] propertyValue Value that was tried to be set + * @return Adjusted value resresented as a string + */ + std::string adjustValue(double min, double max, double step, + double propertyValue) const; + /////////////////////////////////////////////////////////////////////////////// // MEMBERS /////////////////////////////////////////////////////////////////////////////// @@ -209,6 +222,12 @@ class AlliedVisionCamera : public CCameraBase { VmbUint32_t m_bufferSize; // + m_propertyItems; //!< Internal map of properties + static const std::unordered_map + m_featureToProperty; //!< Map of features name into uManager properties + static const std::unordered_multimap + m_propertyToFeature; //!< Map of uManager properties into Vimba features }; #endif diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj index fb5846871..7e3e86303 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj @@ -20,6 +20,7 @@ + @@ -31,6 +32,7 @@ + diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters index 03328d086..14c49d5a3 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters @@ -42,6 +42,9 @@ Header Files + + Header Files + @@ -50,5 +53,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/PropertyItem.cpp b/DeviceAdapters/AlliedVisionCamera/PropertyItem.cpp new file mode 100644 index 000000000..4b0865174 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/PropertyItem.cpp @@ -0,0 +1,5 @@ +#include "PropertyItem.h" + +PropertyItem::PropertyItem(const std::string& name, double min, double max, + double step) + : m_name(name), m_min(min), m_max(max), m_step(step) {} \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/PropertyItem.h b/DeviceAdapters/AlliedVisionCamera/PropertyItem.h new file mode 100644 index 000000000..ed1232dd7 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/PropertyItem.h @@ -0,0 +1,43 @@ +/*============================================================================= + Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ +#ifndef PROPERTYITEM_H +#define PROPERTYITEM_H + +#include + +/** + * @brief + */ +struct PropertyItem { + PropertyItem(const std::string& name, double min = 0.0, double max = 0.0, double step = 1.0); + ~PropertyItem() = default; + + bool operator==(PropertyItem& rhs) { + return rhs.m_name == m_name && rhs.m_min == m_min && rhs.m_max == m_max && + rhs.m_step == m_step; + } + bool operator!=(PropertyItem& rhs) { return !(rhs == *this); } + + std::string m_name; + double m_min; + double m_max; + double m_step; +}; + +#endif // PROPERTYITEM_H diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp index 386d3256b..57b659336 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp @@ -50,16 +50,22 @@ VimbaXApi::VimbaXApi() : m_sdk(VIMBA_X_LIB_NAME, VIMBA_X_LIB_DIR.c_str()) { VmbFeatureStringGet_t = m_sdk.resolveFunction("VmbFeatureStringGet"); VmbFeatureStringSet_t = m_sdk.resolveFunction("VmbFeatureStringSet"); VmbChunkDataAccess_t = m_sdk.resolveFunction("VmbChunkDataAccess"); - VmbFeatureEnumRangeQuery_t = m_sdk.resolveFunction("VmbFeatureEnumRangeQuery"); + VmbFeatureEnumRangeQuery_t = + m_sdk.resolveFunction("VmbFeatureEnumRangeQuery"); VmbFeatureIntRangeQuery_t = m_sdk.resolveFunction("VmbFeatureIntRangeQuery"); - VmbFeatureStringMaxlengthQuery_t = m_sdk.resolveFunction("VmbFeatureStringMaxlengthQuery"); + VmbFeatureStringMaxlengthQuery_t = + m_sdk.resolveFunction("VmbFeatureStringMaxlengthQuery"); VmbFeatureInfoQuery_t = m_sdk.resolveFunction("VmbFeatureInfoQuery"); VmbFeatureFloatRangeQuery_t = m_sdk.resolveFunction("VmbFeatureFloatRangeQuery"); VmbFeatureInvalidationRegister_t = m_sdk.resolveFunction("VmbFeatureInvalidationRegister"); VmbFeatureAccessQuery_t = m_sdk.resolveFunction("VmbFeatureAccessQuery"); + VmbFeatureIntIncrementQuery_t = + m_sdk.resolveFunction("VmbFeatureIntIncrementQuery"); + VmbFeatureFloatIncrementQuery_t = + m_sdk.resolveFunction("VmbFeatureFloatIncrementQuery"); } } diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h index 2b0e64ce7..7f3c3d158 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h @@ -29,7 +29,9 @@ * @brief Wrapper for single Windows process */ class ProcWrapper { + /////////////////////////////////////////////////////////////////////////////// // PUBLIC + /////////////////////////////////////////////////////////////////////////////// public: /** * @brief Constructor of wrapper @@ -44,7 +46,9 @@ class ProcWrapper { operator T*() const { return reinterpret_cast(m_funPtr); } + /////////////////////////////////////////////////////////////////////////////// // PRIVATE + /////////////////////////////////////////////////////////////////////////////// private: FARPROC m_funPtr; // Date: Mon, 10 Jul 2023 13:43:35 +0200 Subject: [PATCH 010/141] Support for command features Changes: - Support for command features Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 37 ++++++++++++++++--- .../AlliedVisionCamera/AlliedVisionCamera.h | 3 ++ .../SDK/Loader/LibLoader.cpp | 2 + .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 1 + 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 07afa78b6..40ebec3d1 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -651,7 +651,8 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, break; } case VmbFeatureDataCommand: - // TODO + value = std::string(g_Command); + break; case VmbFeatureDataUnknown: case VmbFeatureDataRaw: case VmbFeatureDataNone: @@ -667,6 +668,8 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, std::string& value) { VmbError_t err = VmbErrorSuccess; std::stringstream ss(value); + bool isDone = false; + VmbUint32_t maxLen = 0; switch (featureInfo->featureDataType) { case VmbFeatureDataBool: { @@ -691,11 +694,33 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, break; } case VmbFeatureDataString: { - err = g_api->VmbFeatureStringSet_t(m_handle, featureName, value.c_str()); + err = g_api->VmbFeatureStringMaxlengthQuery_t(m_handle, featureName, + &maxLen); + if (err != VmbErrorSuccess) { + LogMessageCode(err); + break; + } + if (value.size() > maxLen) { + err = VmbErrorInvalidValue; + } else { + err = + g_api->VmbFeatureStringSet_t(m_handle, featureName, value.c_str()); + } break; } case VmbFeatureDataCommand: - // TODO + err = g_api->VmbFeatureCommandRun_t(m_handle, featureName); + if (err != VmbErrorSuccess) { + break; + } + while (!isDone) { + err = g_api->VmbFeatureCommandIsDone_t(m_handle, featureName, &isDone); + if (err != VmbErrorSuccess) { + LogMessageCode(err); + break; + } + } + break; case VmbFeatureDataUnknown: case VmbFeatureDataRaw: case VmbFeatureDataNone: @@ -845,11 +870,13 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, err = SetPropertyLimits(propertyName, min, max); break; } - case VmbFeatureDataString: { + case VmbFeatureDataCommand: { + AddAllowedValue(propertyName, g_Command); + AddAllowedValue(propertyName, g_Execute); break; } + case VmbFeatureDataString: case VmbFeatureDataRaw: - case VmbFeatureDataCommand: case VmbFeatureDataNone: default: // nothing diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 3decc5603..cf9fe7f60 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -47,6 +47,9 @@ static constexpr const char* g_Height = "Height"; /////////////////////////////////////////////////////////////////////////////// static constexpr const char* g_True = "True"; static constexpr const char* g_False = "False"; +static constexpr const char* g_Execute = "Execute"; +static constexpr const char* g_Command = "Command"; + /** * @brief Main Allied Vision Camera class */ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp index 57b659336..3270532db 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp @@ -66,6 +66,8 @@ VimbaXApi::VimbaXApi() : m_sdk(VIMBA_X_LIB_NAME, VIMBA_X_LIB_DIR.c_str()) { m_sdk.resolveFunction("VmbFeatureIntIncrementQuery"); VmbFeatureFloatIncrementQuery_t = m_sdk.resolveFunction("VmbFeatureFloatIncrementQuery"); + VmbFeatureCommandIsDone_t = + m_sdk.resolveFunction("VmbFeatureCommandIsDone"); } } diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h index 7f3c3d158..7401a1a3b 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h @@ -162,6 +162,7 @@ class VimbaXApi { nullptr; decltype(VmbFeatureFloatIncrementQuery)* VmbFeatureFloatIncrementQuery_t = nullptr; + decltype(VmbFeatureCommandIsDone)* VmbFeatureCommandIsDone_t = nullptr; /////////////////////////////////////////////////////////////////////////////// // PRIVATE /////////////////////////////////////////////////////////////////////////////// From 753deaa508e1940f722134433106f5cd513df7b6 Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Mon, 10 Jul 2023 16:31:11 +0200 Subject: [PATCH 011/141] ROI support Changes: - ROI support Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 160 +++++++++++++++++- .../AlliedVisionCamera/AlliedVisionCamera.h | 21 +++ 2 files changed, 174 insertions(+), 7 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 40ebec3d1..a2bb0c6e3 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -352,19 +352,141 @@ void AlliedVisionCamera::SetExposure(double exp_ms) { int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) { - // TODO implement - return VmbErrorSuccess; + auto width = GetImageWidth(); + auto height = GetImageHeight(); + VmbError_t err = VmbErrorSuccess; + + if (xSize > width) { + std::string strValueX = std::to_string(x); + err = SetProperty(g_OffsetX, strValueX.c_str()); + if (err != DEVICE_OK) { + return err; + } + + std::string strValueWidth = std::to_string(xSize); + err = SetProperty(g_Width, strValueWidth.c_str()); + if (err != DEVICE_OK) { + return err; + } + + } else { + std::string strValueWidth = std::to_string(xSize); + err = SetProperty(g_Width, strValueWidth.c_str()); + if (err != DEVICE_OK) { + return err; + } + + std::string strValueX = std::to_string(x); + err = SetProperty(g_OffsetX, strValueX.c_str()); + if (err != DEVICE_OK) { + return err; + } + } + + if (ySize > height) { + std::string strValueY = std::to_string(y); + err = SetProperty(g_OffsetY, strValueY.c_str()); + if (err != DEVICE_OK) { + return err; + } + + std::string strValueHeight = std::to_string(ySize); + err = SetProperty(g_Height, strValueHeight.c_str()); + if (err != DEVICE_OK) { + return err; + } + + } else { + std::string strValueHeight = std::to_string(ySize); + err = SetProperty(g_Height, strValueHeight.c_str()); + if (err != DEVICE_OK) { + return err; + } + + std::string strValueY = std::to_string(y); + err = SetProperty(g_OffsetY, strValueY.c_str()); + if (err != DEVICE_OK) { + return err; + } + } + + return resizeImageBuffer(); } int AlliedVisionCamera::GetROI(unsigned& x, unsigned& y, unsigned& xSize, unsigned& ySize) { - // TODO implement - return VmbErrorSuccess; + { + char strX[MM::MaxStrLength]; + auto ret = GetProperty(g_OffsetX, strX); + if (ret != DEVICE_OK) { + return ret; + } + x = atoi(strX); + } + { + char strY[MM::MaxStrLength]; + auto ret = GetProperty(g_OffsetY, strY); + if (ret != DEVICE_OK) { + return ret; + } + y = atoi(strY); + } + { + char strXSize[MM::MaxStrLength]; + auto ret = GetProperty(g_Width, strXSize); + if (ret != DEVICE_OK) { + return ret; + } + xSize = atoi(strXSize); + } + { + char strYSize[MM::MaxStrLength]; + auto ret = GetProperty(g_Height, strYSize); + if (ret != DEVICE_OK) { + return ret; + } + ySize = atoi(strYSize); + } + + return DEVICE_OK; } int AlliedVisionCamera::ClearROI() { - // TODO implement - return 0; + std::string maxWidth, maxHeight; + VmbError_t err = getFeatureValue(g_WidthMax, maxWidth); + if (VmbErrorSuccess != err) { + return err; + } + + err = getFeatureValue(g_HeightMax, maxHeight); + if (VmbErrorSuccess != err) { + return err; + } + + std::string offsetXval = "0"; + std::string offsetYval = "0"; + + err = setFeatureValue(g_OffsetX, offsetXval); + if (VmbErrorSuccess != err) { + return err; + } + + err = setFeatureValue(g_OffsetY, offsetYval); + if (VmbErrorSuccess != err) { + return err; + } + + err = setFeatureValue(g_Width, maxWidth); + if (VmbErrorSuccess != err) { + return err; + } + + err = setFeatureValue(g_Height, maxHeight); + if (VmbErrorSuccess != err) { + return err; + } + + return resizeImageBuffer(); } int AlliedVisionCamera::IsExposureSequenceable(bool& isSequenceable) const { @@ -578,7 +700,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, adjustValue(propertyItem.m_min, propertyItem.m_max, propertyItem.m_step, std::stod(propertyValue)); pProp->Set(adjustedValue.c_str()); - (void)setFeatureValue(&featureInfo, featureName.c_str(), + err = setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); } } @@ -663,6 +785,18 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, return err; } +VmbError_t AlliedVisionCamera::getFeatureValue(const char* featureName, + std::string& value) { + VmbFeatureInfo_t featureInfo; + VmbError_t err = g_api->VmbFeatureInfoQuery_t( + m_handle, featureName, &featureInfo, sizeof(featureInfo)); + if (VmbErrorSuccess != err) { + return err; + } + + return getFeatureValue(&featureInfo, featureName, value); +} + VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, const char* featureName, std::string& value) { @@ -731,6 +865,18 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, return err; } +VmbError_t AlliedVisionCamera::setFeatureValue(const char* featureName, + std::string& value) { + VmbFeatureInfo_t featureInfo; + VmbError_t err = g_api->VmbFeatureInfoQuery_t( + m_handle, featureName, &featureInfo, sizeof(featureInfo)); + if (VmbErrorSuccess != err) { + return err; + } + + return setFeatureValue(&featureInfo, featureName, value); +} + void AlliedVisionCamera::mapFeatureNameToPropertyName( const char* feature, std::string& property) const { property = std::string(feature); diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index cf9fe7f60..0d3fae798 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -41,6 +41,10 @@ static constexpr const char* g_BinningHorizontalFeature = "BinningHorizontal"; static constexpr const char* g_BinningVerticalFeature = "BinningVertical"; static constexpr const char* g_Width = "Width"; static constexpr const char* g_Height = "Height"; +static constexpr const char* g_OffsetX = "OffsetX"; +static constexpr const char* g_OffsetY = "OffsetY"; +static constexpr const char* g_WidthMax = "WidthMax"; +static constexpr const char* g_HeightMax = "HeightMax"; /////////////////////////////////////////////////////////////////////////////// // STATIC VARIABLES @@ -173,6 +177,14 @@ class AlliedVisionCamera : public CCameraBase { */ VmbError_t getFeatureValue(VmbFeatureInfo_t* featureInfo, const char* featureName, std::string& value); + /** + * @brief Method to get feature value, based on its type. Feature value is + * always a string type. + * @param[in] featureName Feature name + * @param[out] value Value of feature, read from device + * @return VmbError_t + */ + VmbError_t getFeatureValue(const char* featureName, std::string& value); /** * @brief Method to set a feature value, bases on its type. Feature value is @@ -185,6 +197,15 @@ class AlliedVisionCamera : public CCameraBase { VmbError_t setFeatureValue(VmbFeatureInfo_t* featureInfo, const char* featureName, std::string& value); + /** + * @brief Method to set a feature value, bases on its type. Feature value is + * always a string type. + * @param[in] featureName Feature name + * @param[in] value Value of feature to be set + * @return VmbError_t + */ + VmbError_t setFeatureValue(const char* featureName, std::string& value); + /** * @brief Helper method to map feature name into property name of uManager * @param[in] feature Vimba Feature name From 704332c7bf642070195095b9d3bcd5e84e4414ab Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Wed, 12 Jul 2023 14:37:20 +0200 Subject: [PATCH 012/141] Device HUB and refactoring Changes: - Added device HUB type - Fixed issue with reading write only features from SDK - Refactored SDK loading and handling approach to be based on unique_ptr Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 208 ++++++++---------- .../AlliedVisionCamera/AlliedVisionCamera.h | 29 ++- .../AlliedVisionCamera.vcxproj | 2 + .../AlliedVisionCamera.vcxproj.filters | 6 + .../AlliedVisionCamera/AlliedVisionHub.cpp | 53 +++++ .../AlliedVisionCamera/AlliedVisionHub.h | 65 ++++++ .../SDK/Loader/LibLoader.cpp | 31 ++- .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 19 +- 8 files changed, 260 insertions(+), 153 deletions(-) create mode 100644 DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp create mode 100644 DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index a2bb0c6e3..3735710fb 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -27,6 +27,7 @@ #include #include +#include "AlliedVisionHub.h" #include "ModuleInterface.h" #include "VmbC/VmbC.h" @@ -51,17 +52,7 @@ const std::unordered_multimap // Exported MMDevice API /////////////////////////////////////////////////////////////////////////////// MODULE_API void InitializeModuleData() { - g_api = std::make_unique(); - assert(g_api != nullptr); - auto err = g_api->VmbStartup_t(nullptr); - assert(err == VmbErrorSuccess); - - err = AlliedVisionCamera::getCamerasList(); - if (err != VmbErrorSuccess) { - // TODO Handle error - } - - g_api->VmbShutdown_t(); + RegisterDevice(g_hubName, MM::HubDevice, "Allied Vision Hub"); } MODULE_API MM::Device* CreateDevice(const char* deviceName) { @@ -69,7 +60,18 @@ MODULE_API MM::Device* CreateDevice(const char* deviceName) { return nullptr; } - return new AlliedVisionCamera(deviceName); + if (g_api == nullptr) { + g_api = std::make_unique(); + } + if(g_api == nullptr || !g_api->isInitialized()){ + return nullptr; + } + + if (std::string(deviceName) == std::string(g_hubName)) { + return new AlliedVisionHub(g_api); + } else { + return new AlliedVisionCamera(deviceName, g_api); + } } MODULE_API void DeleteDevice(MM::Device* pDevice) { delete pDevice; } @@ -86,8 +88,10 @@ AlliedVisionCamera::~AlliedVisionCamera() { } } -AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) +AlliedVisionCamera::AlliedVisionCamera(const char* deviceName, + std::unique_ptr& sdk) : CCameraBase(), + m_sdk(sdk), m_handle{nullptr}, m_cameraName{deviceName}, m_frames{}, @@ -101,24 +105,9 @@ AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) int AlliedVisionCamera::Initialize() { // [Rule] Implement communication here - LogMessage("Initializing Vimba X API..."); - VmbError_t err = g_api->VmbStartup_t(nullptr); - if (err != VmbErrorSuccess) { - return err; - } - - VmbVersionInfo_t ver; - err = g_api->VmbVersionQuery_t(&ver, sizeof(ver)); - if (err != VmbErrorSuccess) { - return err; - } - std::string v = std::to_string(ver.major) + "." + std::to_string(ver.minor) + - "." + std::to_string(ver.patch); - LogMessage("SDK version:" + v); - LogMessage("Opening camera: " + m_cameraName); - err = g_api->VmbCameraOpen_t(m_cameraName.c_str(), - VmbAccessModeType::VmbAccessModeFull, &m_handle); + VmbError_t err = m_sdk->VmbCameraOpen_t( + m_cameraName.c_str(), VmbAccessModeType::VmbAccessModeFull, &m_handle); if (err != VmbErrorSuccess || m_handle == nullptr) { return err; } @@ -135,13 +124,12 @@ int AlliedVisionCamera::Shutdown() { // [Rule] Implement disconnection here LogMessage("Shutting down camera: " + m_cameraName); if (m_handle != nullptr) { - VmbError_t err = g_api->VmbCameraClose_t(m_handle); + VmbError_t err = m_sdk->VmbCameraClose_t(m_handle); if (err != VmbErrorSuccess) { return err; } } - g_api->VmbShutdown_t(); return DEVICE_OK; } @@ -161,7 +149,7 @@ void AlliedVisionCamera::setApiErrorMessages() { VmbError_t AlliedVisionCamera::setupProperties() { VmbUint32_t featureCount = 0; - VmbError_t err = g_api->VmbFeaturesList_t(m_handle, NULL, 0, &featureCount, + VmbError_t err = m_sdk->VmbFeaturesList_t(m_handle, NULL, 0, &featureCount, sizeof(VmbFeatureInfo_t)); if (err != VmbErrorSuccess || !featureCount) { return err; @@ -169,7 +157,7 @@ VmbError_t AlliedVisionCamera::setupProperties() { std::shared_ptr features = std::shared_ptr(new VmbFeatureInfo_t[featureCount]); - err = g_api->VmbFeaturesList_t(m_handle, features.get(), featureCount, + err = m_sdk->VmbFeaturesList_t(m_handle, features.get(), featureCount, &featureCount, sizeof(VmbFeatureInfo_t)); if (err != VmbErrorSuccess) { return err; @@ -189,31 +177,8 @@ VmbError_t AlliedVisionCamera::setupProperties() { } } -VmbError_t AlliedVisionCamera::getCamerasList() { - VmbUint32_t camNum; - // Get the number of connected cameras first - VmbError_t err = g_api->VmbCamerasList_t(nullptr, 0, &camNum, 0); - if (VmbErrorSuccess == err) { - VmbCameraInfo_t* camInfo = new VmbCameraInfo_t[camNum]; - - // Get the cameras - err = g_api->VmbCamerasList_t(camInfo, camNum, &camNum, sizeof *camInfo); - - if (err == VmbErrorSuccess) { - for (VmbUint32_t i = 0; i < camNum; ++i) { - RegisterDevice(camInfo[i].cameraIdString, MM::CameraDevice, - camInfo[i].cameraName); - } - } - - delete[] camInfo; - } - - return err; -} - VmbError_t AlliedVisionCamera::resizeImageBuffer() { - VmbError_t err = g_api->VmbPayloadSizeGet_t(m_handle, &m_bufferSize); + VmbError_t err = m_sdk->VmbPayloadSizeGet_t(m_handle, &m_bufferSize); if (err != VmbErrorSuccess) { return err; } @@ -251,7 +216,7 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( m_propertyItems.insert({propName, {propName}}); // Register VMb callback - err = g_api->VmbFeatureInvalidationRegister_t(m_handle, featureName, + err = m_sdk->VmbFeatureInvalidationRegister_t(m_handle, featureName, vmbCallback, this); if (err != VmbErrorSuccess) { return err; @@ -504,7 +469,7 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) { // Get horizonal binning VmbFeatureInfo_t featureInfoHorizontal; - VmbError_t err = g_api->VmbFeatureInfoQuery_t( + VmbError_t err = m_sdk->VmbFeatureInfoQuery_t( m_handle, g_BinningHorizontalFeature, &featureInfoHorizontal, sizeof(featureInfoHorizontal)); if (VmbErrorSuccess != err) { @@ -513,7 +478,7 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, // Get vertical binning VmbFeatureInfo_t featureInfoVertical; - err = g_api->VmbFeatureInfoQuery_t(m_handle, g_BinningVerticalFeature, + err = m_sdk->VmbFeatureInfoQuery_t(m_handle, g_BinningVerticalFeature, &featureInfoVertical, sizeof(featureInfoVertical)); if (VmbErrorSuccess != err) { @@ -523,7 +488,7 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, // Get read/write mode - assume binning horizontal and vertical has the same // mode bool rMode, wMode, readOnly, featureAvailable; - err = g_api->VmbFeatureAccessQuery_t(m_handle, g_BinningVerticalFeature, + err = m_sdk->VmbFeatureAccessQuery_t(m_handle, g_BinningVerticalFeature, &rMode, &wMode); if (VmbErrorSuccess != err) { return err; @@ -569,14 +534,14 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, // Update property's limits and allowed values if (!readOnly) { // Update binning limits - err = g_api->VmbFeatureIntRangeQuery_t(m_handle, + err = m_sdk->VmbFeatureIntRangeQuery_t(m_handle, g_BinningHorizontalFeature, &minHorizontal, &maxHorizontal); if (VmbErrorSuccess != err) { return err; } - err = g_api->VmbFeatureIntRangeQuery_t( + err = m_sdk->VmbFeatureIntRangeQuery_t( m_handle, g_BinningVerticalFeature, &minVertical, &maxVertical); if (VmbErrorSuccess != err) { return err; @@ -639,7 +604,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, for (const auto& featureName : featureNames) { // Get Feature Info VmbFeatureInfo_t featureInfo; - err = g_api->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), + err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), &featureInfo, sizeof(featureInfo)); if (VmbErrorSuccess != err) { return err; @@ -647,7 +612,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Get Access Mode bool rMode, wMode, readOnly, featureAvailable; - err = g_api->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), + err = m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, &wMode); if (VmbErrorSuccess != err) { return err; @@ -663,31 +628,34 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Get values std::string propertyValue, featureValue; pProp->Get(propertyValue); - err = getFeatureValue(&featureInfo, featureName.c_str(), featureValue); - if (VmbErrorSuccess != err) { - return err; - } // Handle property value change switch (eAct) { case MM::ActionType::BeforeGet: //!< Update property from feature - // Update property - if (propertyValue != featureValue) { - pProp->Set(featureValue.c_str()); - } - - // Update property's access mode - pChildProperty->SetReadOnly(readOnly); - // Update property's limits and allowed values - if (!readOnly) { - err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (rMode) { + err = getFeatureValue(&featureInfo, featureName.c_str(), + featureValue); if (VmbErrorSuccess != err) { return err; } + // Update property + if (propertyValue != featureValue) { + pProp->Set(featureValue.c_str()); + } + + // Update property's access mode + pChildProperty->SetReadOnly(readOnly); + // Update property's limits and allowed values + if (!readOnly) { + err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (VmbErrorSuccess != err) { + return err; + } + } } break; case MM::ActionType::AfterSet: //!< Update feature from property - if (propertyValue != featureValue) { + if (wMode) { err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); if (err != VmbErrorSuccess) { @@ -723,7 +691,7 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, switch (featureInfo->featureDataType) { case VmbFeatureDataBool: { VmbBool_t out; - err = g_api->VmbFeatureBoolGet_t(m_handle, featureName, &out); + err = m_sdk->VmbFeatureBoolGet_t(m_handle, featureName, &out); if (err != VmbErrorSuccess) { break; } @@ -732,7 +700,7 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, } case VmbFeatureDataEnum: { const char* out = nullptr; - err = g_api->VmbFeatureEnumGet_t(m_handle, featureName, &out); + err = m_sdk->VmbFeatureEnumGet_t(m_handle, featureName, &out); if (err != VmbErrorSuccess) { break; } @@ -741,7 +709,7 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, } case VmbFeatureDataFloat: { double out; - err = g_api->VmbFeatureFloatGet_t(m_handle, featureName, &out); + err = m_sdk->VmbFeatureFloatGet_t(m_handle, featureName, &out); if (err != VmbErrorSuccess) { break; } @@ -750,7 +718,7 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, } case VmbFeatureDataInt: { VmbInt64_t out; - err = g_api->VmbFeatureIntGet_t(m_handle, featureName, &out); + err = m_sdk->VmbFeatureIntGet_t(m_handle, featureName, &out); if (err != VmbErrorSuccess) { break; } @@ -759,11 +727,11 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, } case VmbFeatureDataString: { VmbUint32_t size = 0; - err = g_api->VmbFeatureStringGet_t(m_handle, featureName, nullptr, 0, + err = m_sdk->VmbFeatureStringGet_t(m_handle, featureName, nullptr, 0, &size); if (VmbErrorSuccess == err && size > 0) { std::shared_ptr buff = std::shared_ptr(new char[size]); - err = g_api->VmbFeatureStringGet_t(m_handle, featureName, buff.get(), + err = m_sdk->VmbFeatureStringGet_t(m_handle, featureName, buff.get(), size, &size); if (err != VmbErrorSuccess) { break; @@ -788,7 +756,7 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, VmbError_t AlliedVisionCamera::getFeatureValue(const char* featureName, std::string& value) { VmbFeatureInfo_t featureInfo; - VmbError_t err = g_api->VmbFeatureInfoQuery_t( + VmbError_t err = m_sdk->VmbFeatureInfoQuery_t( m_handle, featureName, &featureInfo, sizeof(featureInfo)); if (VmbErrorSuccess != err) { return err; @@ -808,27 +776,27 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, switch (featureInfo->featureDataType) { case VmbFeatureDataBool: { VmbBool_t out = (value == g_True ? true : false); - err = g_api->VmbFeatureBoolSet_t(m_handle, featureName, out); + err = m_sdk->VmbFeatureBoolSet_t(m_handle, featureName, out); break; } case VmbFeatureDataEnum: { - err = g_api->VmbFeatureEnumSet_t(m_handle, featureName, value.c_str()); + err = m_sdk->VmbFeatureEnumSet_t(m_handle, featureName, value.c_str()); break; } case VmbFeatureDataFloat: { double out; ss >> out; - err = g_api->VmbFeatureFloatSet_t(m_handle, featureName, out); + err = m_sdk->VmbFeatureFloatSet_t(m_handle, featureName, out); break; } case VmbFeatureDataInt: { VmbInt64_t out; ss >> out; - err = g_api->VmbFeatureIntSet_t(m_handle, featureName, out); + err = m_sdk->VmbFeatureIntSet_t(m_handle, featureName, out); break; } case VmbFeatureDataString: { - err = g_api->VmbFeatureStringMaxlengthQuery_t(m_handle, featureName, + err = m_sdk->VmbFeatureStringMaxlengthQuery_t(m_handle, featureName, &maxLen); if (err != VmbErrorSuccess) { LogMessageCode(err); @@ -838,17 +806,17 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, err = VmbErrorInvalidValue; } else { err = - g_api->VmbFeatureStringSet_t(m_handle, featureName, value.c_str()); + m_sdk->VmbFeatureStringSet_t(m_handle, featureName, value.c_str()); } break; } case VmbFeatureDataCommand: - err = g_api->VmbFeatureCommandRun_t(m_handle, featureName); + err = m_sdk->VmbFeatureCommandRun_t(m_handle, featureName); if (err != VmbErrorSuccess) { break; } while (!isDone) { - err = g_api->VmbFeatureCommandIsDone_t(m_handle, featureName, &isDone); + err = m_sdk->VmbFeatureCommandIsDone_t(m_handle, featureName, &isDone); if (err != VmbErrorSuccess) { LogMessageCode(err); break; @@ -868,7 +836,7 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, VmbError_t AlliedVisionCamera::setFeatureValue(const char* featureName, std::string& value) { VmbFeatureInfo_t featureInfo; - VmbError_t err = g_api->VmbFeatureInfoQuery_t( + VmbError_t err = m_sdk->VmbFeatureInfoQuery_t( m_handle, featureName, &featureInfo, sizeof(featureInfo)); if (VmbErrorSuccess != err) { return err; @@ -941,7 +909,7 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, case VmbFeatureDataFloat: { double min = 0; double max = 0; - err = g_api->VmbFeatureFloatRangeQuery_t(m_handle, feature->name, &min, + err = m_sdk->VmbFeatureFloatRangeQuery_t(m_handle, feature->name, &min, &max); if (VmbErrorSuccess != err || min == max) { return err; @@ -949,7 +917,7 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, double step = 0.0; bool isIncremental = false; - err = g_api->VmbFeatureFloatIncrementQuery_t(m_handle, feature->name, + err = m_sdk->VmbFeatureFloatIncrementQuery_t(m_handle, feature->name, &isIncremental, &step); if (VmbErrorSuccess != err) { return err; @@ -973,7 +941,7 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, std::array values; std::vector strValues; VmbUint32_t valuesNum = 0; - err = g_api->VmbFeatureEnumRangeQuery_t( + err = m_sdk->VmbFeatureEnumRangeQuery_t( m_handle, feature->name, values.data(), MM::MaxStrLength, &valuesNum); if (VmbErrorSuccess != err) { return err; @@ -991,13 +959,13 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, std::vector strValues; err = - g_api->VmbFeatureIntRangeQuery_t(m_handle, feature->name, &min, &max); + m_sdk->VmbFeatureIntRangeQuery_t(m_handle, feature->name, &min, &max); if (VmbErrorSuccess != err || min == max) { return err; } err = - g_api->VmbFeatureIntIncrementQuery_t(m_handle, feature->name, &step); + m_sdk->VmbFeatureIntIncrementQuery_t(m_handle, feature->name, &step); if (VmbErrorSuccess != err) { return err; } @@ -1043,47 +1011,47 @@ int AlliedVisionCamera::SnapImage() { frame.bufferSize = m_bufferSize; VmbError_t err = - g_api->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); + m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbCaptureStart_t(m_handle); + err = m_sdk->VmbCaptureStart_t(m_handle); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); + err = m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); + err = m_sdk->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbCaptureFrameWait_t(m_handle, &frame, 3000); + err = m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); + err = m_sdk->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbCaptureEnd_t(m_handle); + err = m_sdk->VmbCaptureEnd_t(m_handle); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbCaptureQueueFlush_t(m_handle); + err = m_sdk->VmbCaptureQueueFlush_t(m_handle); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbFrameRevokeAll_t(m_handle); + err = m_sdk->VmbFrameRevokeAll_t(m_handle); if (err != VmbErrorSuccess) { return err; } @@ -1117,12 +1085,12 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, reinterpret_cast(i); //VmbFrameAnnounce_t(m_handle, &(m_frames[i]), sizeof(VmbFrame_t)); + m_sdk->VmbFrameAnnounce_t(m_handle, &(m_frames[i]), sizeof(VmbFrame_t)); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbCaptureFrameQueue_t( + err = m_sdk->VmbCaptureFrameQueue_t( m_handle, &(m_frames[i]), [](const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, VmbFrame_t* frame) { @@ -1134,12 +1102,12 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, } } - err = g_api->VmbCaptureStart_t(m_handle); + err = m_sdk->VmbCaptureStart_t(m_handle); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); + err = m_sdk->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); if (err != VmbErrorSuccess) { return err; } @@ -1153,7 +1121,7 @@ int AlliedVisionCamera::StartSequenceAcquisition(double interval_ms) { } int AlliedVisionCamera::StopSequenceAcquisition() { if (m_isAcquisitionRunning) { - auto err = g_api->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); + auto err = m_sdk->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); if (err != VmbErrorSuccess) { return err; } @@ -1161,17 +1129,17 @@ int AlliedVisionCamera::StopSequenceAcquisition() { m_isAcquisitionRunning = false; } - auto err = g_api->VmbCaptureEnd_t(m_handle); + auto err = m_sdk->VmbCaptureEnd_t(m_handle); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbCaptureQueueFlush_t(m_handle); + err = m_sdk->VmbCaptureQueueFlush_t(m_handle); if (err != VmbErrorSuccess) { return err; } - err = g_api->VmbFrameRevokeAll_t(m_handle); + err = m_sdk->VmbFrameRevokeAll_t(m_handle); if (err != VmbErrorSuccess) { return err; } @@ -1201,7 +1169,7 @@ void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { } if (m_isAcquisitionRunning) { - g_api->VmbCaptureFrameQueue_t( + m_sdk->VmbCaptureFrameQueue_t( m_handle, frame, [](const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, VmbFrame_t* frame) { diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 0d3fae798..4f62e5032 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -16,8 +16,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =============================================================================*/ -#ifndef ALLIEDVISIONCAMERA_H -#define ALLIEDVISIONCAMERA_H +#ifndef AlliedVisionCamera_H +#define AlliedVisionCamera_H #include #include @@ -27,11 +27,6 @@ #include "Loader/LibLoader.h" #include "PropertyItem.h" -/** - * @brief Pointer to the Vimba API - */ -static std::unique_ptr g_api; - /////////////////////////////////////////////////////////////////////////////// // STATIC FEATURE NAMES (FROM VIMBA) /////////////////////////////////////////////////////////////////////////////// @@ -54,6 +49,12 @@ static constexpr const char* g_False = "False"; static constexpr const char* g_Execute = "Execute"; static constexpr const char* g_Command = "Command"; +/** + * @brief Global pointer to the Vimba API, that needs to be released in a + * correct way at the end + */ +static std::unique_ptr g_api{nullptr}; + /** * @brief Main Allied Vision Camera class */ @@ -64,19 +65,14 @@ class AlliedVisionCamera : public CCameraBase { public: /** * @brief Contructor of Allied Vision Camera - * @param[in] deviceName Device name + * @param[in] deviceName Device name + * @param[in] sdk Unique pointer to the SDK */ - AlliedVisionCamera(const char* deviceName); + AlliedVisionCamera(const char* deviceName, std::unique_ptr& sdk); /** * @brief Allied Vision Camera destructor */ - ~AlliedVisionCamera(); - - /** - * @brief Get connected camera list - * @return VmbError_t - */ - static VmbError_t getCamerasList(); + virtual ~AlliedVisionCamera(); /////////////////////////////////////////////////////////////////////////////// // uMANAGER API METHODS @@ -238,6 +234,7 @@ class AlliedVisionCamera : public CCameraBase { /////////////////////////////////////////////////////////////////////////////// // MEMBERS /////////////////////////////////////////////////////////////////////////////// + std::unique_ptr& m_sdk; // m_frames; // + @@ -32,6 +33,7 @@ + diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters index 14c49d5a3..2d7e866f7 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters @@ -45,6 +45,9 @@ Header Files + + Header Files + @@ -56,5 +59,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp new file mode 100644 index 000000000..73b85e7e7 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp @@ -0,0 +1,53 @@ +#include "AlliedVisionHub.h" + +#include "AlliedVisionCamera.h" + +AlliedVisionHub::AlliedVisionHub(std::unique_ptr& sdk) : m_sdk(sdk) {} + +AlliedVisionHub::~AlliedVisionHub() { + // Release static SDK variable from DLL, otherwise process will not be + // killed, destructor not called + m_sdk.reset(); +} + +int AlliedVisionHub::DetectInstalledDevices() { + VmbUint32_t camNum; + // Get the number of connected cameras first + VmbError_t err = m_sdk->VmbCamerasList_t(nullptr, 0, &camNum, 0); + if (VmbErrorSuccess == err) { + VmbCameraInfo_t* camInfo = new VmbCameraInfo_t[camNum]; + + // Get the cameras + err = m_sdk->VmbCamerasList_t(camInfo, camNum, &camNum, sizeof *camInfo); + + if (err == VmbErrorSuccess) { + for (VmbUint32_t i = 0; i < camNum; ++i) { + if (camInfo[i].permittedAccess & VmbAccessModeFull) { + MM::Device* pDev = + new AlliedVisionCamera(camInfo[i].cameraIdString, m_sdk); + AddInstalledDevice(pDev); + } + } + } + + delete[] camInfo; + } + + return err; +} + +int AlliedVisionHub::Initialize() { + LogMessage("Init HUB"); + return DEVICE_OK; +} + +int AlliedVisionHub::Shutdown() { + LogMessage("Shutting down HUB"); + return DEVICE_OK; +} + +void AlliedVisionHub::GetName(char* name) const { + CDeviceUtils::CopyLimitedString(name, g_hubName); +} + +bool AlliedVisionHub::Busy() { return false; } diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h new file mode 100644 index 000000000..dea6c36db --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h @@ -0,0 +1,65 @@ +/*============================================================================= + Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ +#ifndef AlliedVisionHub_H +#define AlliedVisionHub_H + +#include "DeviceBase.h" +#include "SDK/Loader/LibLoader.h" + +/////////////////////////////////////////////////////////////////////////////// +// STATIC VARIABLES +/////////////////////////////////////////////////////////////////////////////// +static constexpr const char* g_hubName = "AlliedVisionHub"; + +/** + * @brief Class that represents a HUB of supported devices + */ +class AlliedVisionHub : public HubBase { + /////////////////////////////////////////////////////////////////////////////// + // PUBLIC + /////////////////////////////////////////////////////////////////////////////// + public: + /** + * @brief Contructor of a HUB + * @param sdk Unique pointer to the SDK + */ + AlliedVisionHub(std::unique_ptr& sdk); + + /** + * @brief Destructor of a HUB + */ + virtual ~AlliedVisionHub(); + + /////////////////////////////////////////////////////////////////////////////// + // uMANAGER API METHODS + /////////////////////////////////////////////////////////////////////////////// + int DetectInstalledDevices() override; + int Initialize() override; + int Shutdown() override; + void GetName(char* name) const override; + bool Busy() override; + + /////////////////////////////////////////////////////////////////////////////// + // PRIVATE + /////////////////////////////////////////////////////////////////////////////// + private: + std::unique_ptr& m_sdk; // Date: Thu, 13 Jul 2023 14:38:23 +0200 Subject: [PATCH 013/141] Fixes Changes: - Fix to hide event/chunk category properties - Fix for command properties that were broken - Fix for handling TriggerMode scenario and capturing images in LIVE mode - Fix for cleaning capturing state of camera in case of error Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 206 +++++++++--------- .../AlliedVisionCamera/AlliedVisionCamera.h | 5 + 2 files changed, 102 insertions(+), 109 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 3735710fb..d64998ab9 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -63,7 +63,7 @@ MODULE_API MM::Device* CreateDevice(const char* deviceName) { if (g_api == nullptr) { g_api = std::make_unique(); } - if(g_api == nullptr || !g_api->isInitialized()){ + if (g_api == nullptr || !g_api->isInitialized()) { return nullptr; } @@ -98,6 +98,7 @@ AlliedVisionCamera::AlliedVisionCamera(const char* deviceName, m_buffer{}, m_bufferSize{0}, m_isAcquisitionRunning{false} { + CreateHubIDProperty(); // [Rule] Create properties here (pre-init only) InitializeDefaultErrorMessages(); setApiErrorMessages(); @@ -122,6 +123,7 @@ int AlliedVisionCamera::Initialize() { int AlliedVisionCamera::Shutdown() { // [Rule] Implement disconnection here + (void)StopSequenceAcquisition(); LogMessage("Shutting down camera: " + m_cameraName); if (m_handle != nullptr) { VmbError_t err = m_sdk->VmbCameraClose_t(m_handle); @@ -222,14 +224,30 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( return err; } + std::string featureCategory = feature->category; + if (featureCategory.find(g_EventCategory) != std::string::npos || + featureCategory.find(g_ChunkCategory) != std::string::npos) { + return err; + } + switch (feature->featureDataType) { case VmbFeatureDataInt: { err = CreateIntegerProperty(propName.c_str(), 0, true, callback); break; } - case VmbFeatureDataBool: + case VmbFeatureDataBool: { + err = CreateStringProperty(propName.c_str(), g_False, true, callback); + AddAllowedValue(propName.c_str(), g_False); + AddAllowedValue(propName.c_str(), g_True); + break; + } + case VmbFeatureDataCommand: { + err = CreateStringProperty(propName.c_str(), g_Command, true, callback); + AddAllowedValue(propName.c_str(), g_Command); + AddAllowedValue(propName.c_str(), g_Execute); + break; + } case VmbFeatureDataEnum: - case VmbFeatureDataCommand: case VmbFeatureDataString: { err = CreateStringProperty(propName.c_str(), "", true, callback); break; @@ -494,30 +512,11 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, return err; } - featureAvailable = (rMode || wMode); - if (!featureAvailable) { - return err; - } - - readOnly = featureAvailable && (rMode && !wMode); + readOnly = (rMode && !wMode); // Get values of property and features std::string propertyValue, featureHorizontalValue, featureVerticalValue; pProp->Get(propertyValue); - err = getFeatureValue(&featureInfoHorizontal, g_BinningHorizontalFeature, - featureHorizontalValue); - if (VmbErrorSuccess != err) { - return err; - } - err = getFeatureValue(&featureInfoVertical, g_BinningVerticalFeature, - featureVerticalValue); - if (VmbErrorSuccess != err) { - return err; - } - - std::string featureValue = - std::to_string(std::min(atoi(featureHorizontalValue.c_str()), - atoi(featureVerticalValue.c_str()))); MM::Property* pChildProperty = (MM::Property*)pProp; std::vector strValues; @@ -525,14 +524,32 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, switch (eAct) { case MM::ActionType::BeforeGet: - // Update property - if (propertyValue != featureValue) { - pProp->Set(featureValue.c_str()); + if (rMode) { + err = + getFeatureValue(&featureInfoHorizontal, g_BinningHorizontalFeature, + featureHorizontalValue); + if (VmbErrorSuccess != err) { + return err; + } + err = getFeatureValue(&featureInfoVertical, g_BinningVerticalFeature, + featureVerticalValue); + if (VmbErrorSuccess != err) { + return err; + } + + std::string featureValue = + std::to_string(std::min(atoi(featureHorizontalValue.c_str()), + atoi(featureVerticalValue.c_str()))); + // Update property + if (propertyValue != featureValue) { + pProp->Set(featureValue.c_str()); + } } + // Update property's access mode pChildProperty->SetReadOnly(readOnly); // Update property's limits and allowed values - if (!readOnly) { + if (wMode) { // Update binning limits err = m_sdk->VmbFeatureIntRangeQuery_t(m_handle, g_BinningHorizontalFeature, @@ -559,7 +576,7 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, } break; case MM::ActionType::AfterSet: - if (propertyValue != featureValue) { + if (wMode) { VmbError_t errHor = setFeatureValue( &featureInfoHorizontal, g_BinningHorizontalFeature, propertyValue); VmbError_t errVer = setFeatureValue( @@ -618,12 +635,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, return err; } - featureAvailable = (rMode || wMode); - if (!featureAvailable) { - return err; - } - - readOnly = featureAvailable && (rMode && !wMode); + readOnly = (rMode && !wMode); // Get values std::string propertyValue, featureValue; @@ -642,15 +654,15 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, if (propertyValue != featureValue) { pProp->Set(featureValue.c_str()); } + } - // Update property's access mode - pChildProperty->SetReadOnly(readOnly); - // Update property's limits and allowed values - if (!readOnly) { - err = setAllowedValues(&featureInfo, propertyName.c_str()); - if (VmbErrorSuccess != err) { - return err; - } + // Update property's access mode + pChildProperty->SetReadOnly(readOnly); + // Update property's limits and allowed values + if (wMode) { + err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (VmbErrorSuccess != err) { + return err; } } break; @@ -772,6 +784,7 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, std::stringstream ss(value); bool isDone = false; VmbUint32_t maxLen = 0; + std::string property{}; switch (featureInfo->featureDataType) { case VmbFeatureDataBool: { @@ -811,15 +824,25 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, break; } case VmbFeatureDataCommand: - err = m_sdk->VmbFeatureCommandRun_t(m_handle, featureName); - if (err != VmbErrorSuccess) { - break; - } - while (!isDone) { - err = m_sdk->VmbFeatureCommandIsDone_t(m_handle, featureName, &isDone); - if (err != VmbErrorSuccess) { - LogMessageCode(err); - break; + if (value == g_Execute) { + mapFeatureNameToPropertyName(featureName, property); + if (!property.empty()) { + err = m_sdk->VmbFeatureCommandRun_t(m_handle, featureName); + if (err != VmbErrorSuccess) { + break; + } + while (!isDone) { + err = m_sdk->VmbFeatureCommandIsDone_t(m_handle, featureName, + &isDone); + if (err != VmbErrorSuccess) { + LogMessageCode(err); + break; + } + } + // Set back property to "Command" + SetProperty(property.c_str(), g_Command); + } else { + err = VmbErrorInvalidValue; } } break; @@ -901,9 +924,9 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, } switch (feature->featureDataType) { + case VmbFeatureDataCommand: case VmbFeatureDataBool: { - AddAllowedValue(propertyName, g_False); - AddAllowedValue(propertyName, g_True); + // Already set in creation break; } case VmbFeatureDataFloat: { @@ -984,11 +1007,6 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, err = SetPropertyLimits(propertyName, min, max); break; } - case VmbFeatureDataCommand: { - AddAllowedValue(propertyName, g_Command); - AddAllowedValue(propertyName, g_Execute); - break; - } case VmbFeatureDataString: case VmbFeatureDataRaw: case VmbFeatureDataNone: @@ -1001,7 +1019,7 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, } int AlliedVisionCamera::SnapImage() { - if (m_isAcquisitionRunning) { + if (IsCapturing()) { return DEVICE_CAMERA_BUSY_ACQUIRING; } resizeImageBuffer(); @@ -1012,57 +1030,27 @@ int AlliedVisionCamera::SnapImage() { VmbError_t err = m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbCaptureStart_t(m_handle); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbCaptureEnd_t(m_handle); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbCaptureQueueFlush_t(m_handle); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbFrameRevokeAll_t(m_handle); - if (err != VmbErrorSuccess) { - return err; + if (err == VmbErrorSuccess) { + err = m_sdk->VmbCaptureStart_t(m_handle); + if (err == VmbErrorSuccess) { + err = m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); + if (err == VmbErrorSuccess) { + err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); + m_isAcquisitionRunning = true; + if (err == VmbErrorSuccess) { + err = m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); + } + } + } } - + (void)StopSequenceAcquisition(); return err; } int AlliedVisionCamera::StartSequenceAcquisition(long numImages, double interval_ms, bool stopOnOverflow) { - if (m_isAcquisitionRunning) { + if (IsCapturing()) { return DEVICE_CAMERA_BUSY_ACQUIRING; } @@ -1107,12 +1095,13 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, return err; } - err = m_sdk->VmbFeatureCommandRun_t(m_handle, "AcquisitionStart"); + err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); if (err != VmbErrorSuccess) { return err; } m_isAcquisitionRunning = true; + return err; } @@ -1120,13 +1109,12 @@ int AlliedVisionCamera::StartSequenceAcquisition(double interval_ms) { return StartSequenceAcquisition(LONG_MAX, interval_ms, true); } int AlliedVisionCamera::StopSequenceAcquisition() { - if (m_isAcquisitionRunning) { - auto err = m_sdk->VmbFeatureCommandRun_t(m_handle, "AcquisitionStop"); + if (IsCapturing()) { + auto err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStop); + m_isAcquisitionRunning = false; if (err != VmbErrorSuccess) { return err; } - - m_isAcquisitionRunning = false; } auto err = m_sdk->VmbCaptureEnd_t(m_handle); @@ -1168,7 +1156,7 @@ void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { md.Serialize().c_str(), false); } - if (m_isAcquisitionRunning) { + if (IsCapturing()) { m_sdk->VmbCaptureFrameQueue_t( m_handle, frame, [](const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 4f62e5032..4c0d553b8 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -48,6 +48,11 @@ static constexpr const char* g_True = "True"; static constexpr const char* g_False = "False"; static constexpr const char* g_Execute = "Execute"; static constexpr const char* g_Command = "Command"; +static constexpr const char* g_ChunkCategory = "ChunkDataControl"; +static constexpr const char* g_EventCategory = "EventControl"; +static constexpr const char* g_AcquisitionStart = "AcquisitionStart"; +static constexpr const char* g_AcquisitionStop = "AcquisitionStop"; +static constexpr const char* g_AcqusitionStatus = "AcqusitionStatus"; /** * @brief Global pointer to the Vimba API, that needs to be released in a From fa12156c3b1672b0962f07ac4be06fa76b7cecb4 Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Fri, 14 Jul 2023 10:57:10 +0200 Subject: [PATCH 014/141] Refactoring Changes: - Added error handling for symbols resolve and SDK init - Changed way of SDK object creation/deletion to shared_ptr Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 25 ++-- .../AlliedVisionCamera/AlliedVisionCamera.h | 11 +- .../AlliedVisionCamera/AlliedVisionHub.cpp | 15 +-- .../AlliedVisionCamera/AlliedVisionHub.h | 13 +- .../SDK/Loader/LibLoader.cpp | 121 ++++++++++-------- .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 6 +- 6 files changed, 103 insertions(+), 88 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index d64998ab9..024e23efd 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -60,17 +60,10 @@ MODULE_API MM::Device* CreateDevice(const char* deviceName) { return nullptr; } - if (g_api == nullptr) { - g_api = std::make_unique(); - } - if (g_api == nullptr || !g_api->isInitialized()) { - return nullptr; - } - if (std::string(deviceName) == std::string(g_hubName)) { - return new AlliedVisionHub(g_api); + return new AlliedVisionHub(); } else { - return new AlliedVisionCamera(deviceName, g_api); + return new AlliedVisionCamera(deviceName); } } @@ -88,23 +81,29 @@ AlliedVisionCamera::~AlliedVisionCamera() { } } -AlliedVisionCamera::AlliedVisionCamera(const char* deviceName, - std::unique_ptr& sdk) +AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) : CCameraBase(), - m_sdk(sdk), + m_sdk(nullptr), m_handle{nullptr}, m_cameraName{deviceName}, m_frames{}, m_buffer{}, m_bufferSize{0}, m_isAcquisitionRunning{false} { - CreateHubIDProperty(); // [Rule] Create properties here (pre-init only) + CreateHubIDProperty(); InitializeDefaultErrorMessages(); setApiErrorMessages(); } int AlliedVisionCamera::Initialize() { + auto parentHub = dynamic_cast(GetParentHub()); + if (parentHub == nullptr) { + LogMessage("Parent HUB not found!"); + return DEVICE_ERR; + } + + m_sdk = parentHub->getSDK(); // [Rule] Implement communication here LogMessage("Opening camera: " + m_cameraName); VmbError_t err = m_sdk->VmbCameraOpen_t( diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 4c0d553b8..146b41ebd 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -54,12 +54,6 @@ static constexpr const char* g_AcquisitionStart = "AcquisitionStart"; static constexpr const char* g_AcquisitionStop = "AcquisitionStop"; static constexpr const char* g_AcqusitionStatus = "AcqusitionStatus"; -/** - * @brief Global pointer to the Vimba API, that needs to be released in a - * correct way at the end - */ -static std::unique_ptr g_api{nullptr}; - /** * @brief Main Allied Vision Camera class */ @@ -71,9 +65,8 @@ class AlliedVisionCamera : public CCameraBase { /** * @brief Contructor of Allied Vision Camera * @param[in] deviceName Device name - * @param[in] sdk Unique pointer to the SDK */ - AlliedVisionCamera(const char* deviceName, std::unique_ptr& sdk); + AlliedVisionCamera(const char* deviceName); /** * @brief Allied Vision Camera destructor */ @@ -239,7 +232,7 @@ class AlliedVisionCamera : public CCameraBase { /////////////////////////////////////////////////////////////////////////////// // MEMBERS /////////////////////////////////////////////////////////////////////////////// - std::unique_ptr& m_sdk; // m_sdk; // m_frames; //& sdk) : m_sdk(sdk) {} - -AlliedVisionHub::~AlliedVisionHub() { - // Release static SDK variable from DLL, otherwise process will not be - // killed, destructor not called - m_sdk.reset(); -} +AlliedVisionHub::AlliedVisionHub() : m_sdk(std::make_shared()) {} int AlliedVisionHub::DetectInstalledDevices() { VmbUint32_t camNum; @@ -23,8 +17,7 @@ int AlliedVisionHub::DetectInstalledDevices() { if (err == VmbErrorSuccess) { for (VmbUint32_t i = 0; i < camNum; ++i) { if (camInfo[i].permittedAccess & VmbAccessModeFull) { - MM::Device* pDev = - new AlliedVisionCamera(camInfo[i].cameraIdString, m_sdk); + MM::Device* pDev = new AlliedVisionCamera(camInfo[i].cameraIdString); AddInstalledDevice(pDev); } } @@ -37,7 +30,7 @@ int AlliedVisionHub::DetectInstalledDevices() { } int AlliedVisionHub::Initialize() { - LogMessage("Init HUB"); + LogMessage("Init HUB"); return DEVICE_OK; } @@ -51,3 +44,5 @@ void AlliedVisionHub::GetName(char* name) const { } bool AlliedVisionHub::Busy() { return false; } + +std::shared_ptr& AlliedVisionHub::getSDK() { return m_sdk; } diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h index dea6c36db..a18629d94 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h @@ -37,14 +37,19 @@ class AlliedVisionHub : public HubBase { public: /** * @brief Contructor of a HUB - * @param sdk Unique pointer to the SDK */ - AlliedVisionHub(std::unique_ptr& sdk); + AlliedVisionHub(); /** * @brief Destructor of a HUB */ - virtual ~AlliedVisionHub(); + virtual ~AlliedVisionHub() = default; + + /** + * @brief SDK getter + * @return Pointer to SDK + */ + std::shared_ptr& getSDK(); /////////////////////////////////////////////////////////////////////////////// // uMANAGER API METHODS @@ -59,7 +64,7 @@ class AlliedVisionHub : public HubBase { // PRIVATE /////////////////////////////////////////////////////////////////////////////// private: - std::unique_ptr& m_sdk; // m_sdk; // Date: Fri, 14 Jul 2023 21:40:40 +0200 Subject: [PATCH 015/141] Refactoring Changes: - Refactoring of existing solution Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 458 ++++++++---------- .../AlliedVisionCamera/AlliedVisionCamera.h | 22 +- .../AlliedVisionCamera.vcxproj | 2 - .../AlliedVisionCamera.vcxproj.filters | 6 - .../AlliedVisionCamera/AlliedVisionHub.cpp | 1 + .../AlliedVisionCamera/PropertyItem.cpp | 5 - .../AlliedVisionCamera/PropertyItem.h | 43 -- 7 files changed, 204 insertions(+), 333 deletions(-) delete mode 100644 DeviceAdapters/AlliedVisionCamera/PropertyItem.cpp delete mode 100644 DeviceAdapters/AlliedVisionCamera/PropertyItem.h diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 024e23efd..f386d0614 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -72,7 +72,6 @@ MODULE_API void DeleteDevice(MM::Device* pDevice) { delete pDevice; } /////////////////////////////////////////////////////////////////////////////// // AlliedVisionCamera /////////////////////////////////////////////////////////////////////////////// - AlliedVisionCamera::~AlliedVisionCamera() { m_handle = nullptr; @@ -90,7 +89,6 @@ AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) m_buffer{}, m_bufferSize{0}, m_isAcquisitionRunning{false} { - // [Rule] Create properties here (pre-init only) CreateHubIDProperty(); InitializeDefaultErrorMessages(); setApiErrorMessages(); @@ -102,9 +100,8 @@ int AlliedVisionCamera::Initialize() { LogMessage("Parent HUB not found!"); return DEVICE_ERR; } - m_sdk = parentHub->getSDK(); - // [Rule] Implement communication here + LogMessage("Opening camera: " + m_cameraName); VmbError_t err = m_sdk->VmbCameraOpen_t( m_cameraName.c_str(), VmbAccessModeType::VmbAccessModeFull, &m_handle); @@ -115,23 +112,20 @@ int AlliedVisionCamera::Initialize() { // Init properties and buffer // TODO handle error setupProperties(); - resizeImageBuffer(); - return DEVICE_OK; + return resizeImageBuffer(); } int AlliedVisionCamera::Shutdown() { - // [Rule] Implement disconnection here - (void)StopSequenceAcquisition(); LogMessage("Shutting down camera: " + m_cameraName); + VmbError_t err = VmbErrorSuccess; + + (void)StopSequenceAcquisition(); if (m_handle != nullptr) { - VmbError_t err = m_sdk->VmbCameraClose_t(m_handle); - if (err != VmbErrorSuccess) { - return err; - } + err = m_sdk->VmbCameraClose_t(m_handle); } - return DEVICE_OK; + return err; } void AlliedVisionCamera::setApiErrorMessages() { @@ -146,6 +140,13 @@ void AlliedVisionCamera::setApiErrorMessages() { SetErrorText(VmbErrorInvalidValue, "The value is not valid: either out of bounds or not an " "increment of the minimum"); + SetErrorText(VmbErrorBadHandle, "Given device handle is not valid"); + SetErrorText(VmbErrorInvalidAccess, + "Operation is invalid with the current access mode"); + SetErrorText(VmbErrorTimeout, "Timeout occured"); + SetErrorText(VmbErrorNotAvailable, "Something is not available"); + SetErrorText(VmbErrorNotInitialized, "Something is not initialized"); + SetErrorText(VmbErrorAlready, "The operation has been already done"); } VmbError_t AlliedVisionCamera::setupProperties() { @@ -166,16 +167,14 @@ VmbError_t AlliedVisionCamera::setupProperties() { const VmbFeatureInfo_t* end = features.get() + featureCount; for (VmbFeatureInfo_t* feature = features.get(); feature != end; ++feature) { - // uManager callback - CPropertyAction* callback = - new CPropertyAction(this, &AlliedVisionCamera::onProperty); - - err = createPropertyFromFeature(feature, callback); + err = createPropertyFromFeature(feature); if (err != VmbErrorSuccess) { LogMessageCode(err); continue; } } + + return VmbErrorSuccess; } VmbError_t AlliedVisionCamera::resizeImageBuffer() { @@ -193,66 +192,77 @@ VmbError_t AlliedVisionCamera::resizeImageBuffer() { } VmbError_t AlliedVisionCamera::createPropertyFromFeature( - const VmbFeatureInfo_t* feature, MM::ActionFunctor* callback) { + const VmbFeatureInfo_t* feature) { if (feature == nullptr) { return VmbErrorInvalidValue; } - auto featureName = feature->name; VmbError_t err = VmbErrorSuccess; - std::string propName = {}; - mapFeatureNameToPropertyName(featureName, propName); + // Skip Event and Chunk features + std::string featureCategory = feature->category; + if (featureCategory.find(g_EventCategory) != std::string::npos || + featureCategory.find(g_ChunkCategory) != std::string::npos) { + return err; + } + + // Map feature to property name + std::string propertyName = {}; + mapFeatureNameToPropertyName(feature->name, propertyName); + + // uManager callback + CPropertyAction* uManagerCallback = + new CPropertyAction(this, &AlliedVisionCamera::onProperty); // Vimba callback auto vmbCallback = [](VmbHandle_t handle, const char* name, void* userContext) { + (void)handle; AlliedVisionCamera* camera = reinterpret_cast(userContext); std::string propertyName; camera->mapFeatureNameToPropertyName(name, propertyName); - camera->UpdateProperty(propertyName.c_str()); + auto err = camera->UpdateProperty(propertyName.c_str()); + if (err != VmbErrorSuccess) { + camera->LogMessage("Property: " + propertyName + " update failed"); + } }; - // Add property to the list - m_propertyItems.insert({propName, {propName}}); - - // Register VMb callback - err = m_sdk->VmbFeatureInvalidationRegister_t(m_handle, featureName, + // Register VMB callback for given feature + err = m_sdk->VmbFeatureInvalidationRegister_t(m_handle, feature->name, vmbCallback, this); if (err != VmbErrorSuccess) { return err; } - std::string featureCategory = feature->category; - if (featureCategory.find(g_EventCategory) != std::string::npos || - featureCategory.find(g_ChunkCategory) != std::string::npos) { - return err; - } - switch (feature->featureDataType) { case VmbFeatureDataInt: { - err = CreateIntegerProperty(propName.c_str(), 0, true, callback); + err = CreateIntegerProperty(propertyName.c_str(), 0, true, + uManagerCallback); break; } case VmbFeatureDataBool: { - err = CreateStringProperty(propName.c_str(), g_False, true, callback); - AddAllowedValue(propName.c_str(), g_False); - AddAllowedValue(propName.c_str(), g_True); + err = CreateStringProperty(propertyName.c_str(), g_False, true, + uManagerCallback); + AddAllowedValue(propertyName.c_str(), g_False); + AddAllowedValue(propertyName.c_str(), g_True); break; } case VmbFeatureDataCommand: { - err = CreateStringProperty(propName.c_str(), g_Command, true, callback); - AddAllowedValue(propName.c_str(), g_Command); - AddAllowedValue(propName.c_str(), g_Execute); + err = CreateStringProperty(propertyName.c_str(), g_Command, true, + uManagerCallback); + AddAllowedValue(propertyName.c_str(), g_Command); + AddAllowedValue(propertyName.c_str(), g_Execute); break; } case VmbFeatureDataEnum: case VmbFeatureDataString: { - err = CreateStringProperty(propName.c_str(), "", true, callback); + err = CreateStringProperty(propertyName.c_str(), "", true, + uManagerCallback); break; } case VmbFeatureDataFloat: { - err = CreateFloatProperty(propName.c_str(), 0.0, true, callback); + err = CreateFloatProperty(propertyName.c_str(), 0.0, true, + uManagerCallback); break; } case VmbFeatureDataUnknown: @@ -273,7 +283,7 @@ const unsigned char* AlliedVisionCamera::GetImageBuffer() { unsigned AlliedVisionCamera::GetImageWidth() const { char value[MM::MaxStrLength]; int ret = GetProperty(g_Width, value); - if (ret != DEVICE_OK) { + if (ret != VmbErrorSuccess) { return 0; } @@ -283,7 +293,7 @@ unsigned AlliedVisionCamera::GetImageWidth() const { unsigned AlliedVisionCamera::GetImageHeight() const { char value[MM::MaxStrLength]; int ret = GetProperty(g_Height, value); - if (ret != DEVICE_OK) { + if (ret != VmbErrorSuccess) { return 0; } @@ -305,7 +315,7 @@ unsigned AlliedVisionCamera::GetBitDepth() const { int AlliedVisionCamera::GetBinning() const { char value[MM::MaxStrLength]; int ret = GetProperty(MM::g_Keyword_Binning, value); - if (ret != DEVICE_OK) { + if (ret != VmbErrorSuccess) { return 0; } @@ -320,7 +330,7 @@ int AlliedVisionCamera::SetBinning(int binSize) { double AlliedVisionCamera::GetExposure() const { char strExposure[MM::MaxStrLength]; int ret = GetProperty(MM::g_Keyword_Exposure, strExposure); - if (ret != DEVICE_OK) { + if (ret != VmbErrorSuccess) { return 0.0; } @@ -338,56 +348,39 @@ int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, auto height = GetImageHeight(); VmbError_t err = VmbErrorSuccess; - if (xSize > width) { - std::string strValueX = std::to_string(x); - err = SetProperty(g_OffsetX, strValueX.c_str()); - if (err != DEVICE_OK) { - return err; - } + std::function setOffsetXProperty = [this](long x) { + return SetProperty(g_OffsetX, CDeviceUtils::ConvertToString(x)); + }; + std::function setOffsetYProperty = [this](long y) { + return SetProperty(g_OffsetY, CDeviceUtils::ConvertToString(y)); + }; + std::function setWidthProperty = [this](long xSize) { + return SetProperty(g_Width, CDeviceUtils::ConvertToString(xSize)); + }; + std::function setHeightProperty = [this](long ySize) { + return SetProperty(g_Height, CDeviceUtils::ConvertToString(ySize)); + }; - std::string strValueWidth = std::to_string(xSize); - err = SetProperty(g_Width, strValueWidth.c_str()); - if (err != DEVICE_OK) { + if (xSize > width) { + err = setOffsetXProperty(x) || setWidthProperty(xSize); + if (err != VmbErrorSuccess) { return err; } - } else { - std::string strValueWidth = std::to_string(xSize); - err = SetProperty(g_Width, strValueWidth.c_str()); - if (err != DEVICE_OK) { - return err; - } - - std::string strValueX = std::to_string(x); - err = SetProperty(g_OffsetX, strValueX.c_str()); - if (err != DEVICE_OK) { + err = setWidthProperty(xSize) || setOffsetXProperty(x); + if (err != VmbErrorSuccess) { return err; } } if (ySize > height) { - std::string strValueY = std::to_string(y); - err = SetProperty(g_OffsetY, strValueY.c_str()); - if (err != DEVICE_OK) { - return err; - } - - std::string strValueHeight = std::to_string(ySize); - err = SetProperty(g_Height, strValueHeight.c_str()); - if (err != DEVICE_OK) { + err = setOffsetYProperty(y) || setHeightProperty(ySize); + if (err != VmbErrorSuccess) { return err; } - } else { - std::string strValueHeight = std::to_string(ySize); - err = SetProperty(g_Height, strValueHeight.c_str()); - if (err != DEVICE_OK) { - return err; - } - - std::string strValueY = std::to_string(y); - err = SetProperty(g_OffsetY, strValueY.c_str()); - if (err != DEVICE_OK) { + err = setHeightProperty(ySize) || setOffsetYProperty(y); + if (err != VmbErrorSuccess) { return err; } } @@ -397,75 +390,41 @@ int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, int AlliedVisionCamera::GetROI(unsigned& x, unsigned& y, unsigned& xSize, unsigned& ySize) { - { - char strX[MM::MaxStrLength]; - auto ret = GetProperty(g_OffsetX, strX); - if (ret != DEVICE_OK) { - return ret; - } - x = atoi(strX); - } - { - char strY[MM::MaxStrLength]; - auto ret = GetProperty(g_OffsetY, strY); - if (ret != DEVICE_OK) { - return ret; - } - y = atoi(strY); - } - { - char strXSize[MM::MaxStrLength]; - auto ret = GetProperty(g_Width, strXSize); - if (ret != DEVICE_OK) { - return ret; - } - xSize = atoi(strXSize); - } - { - char strYSize[MM::MaxStrLength]; - auto ret = GetProperty(g_Height, strYSize); - if (ret != DEVICE_OK) { - return ret; + std::unordered_map fields = { + {g_OffsetX, x}, {g_OffsetY, y}, {g_Width, xSize}, {g_Height, ySize}}; + + std::array value; + VmbError_t err = VmbErrorSuccess; + for (auto& field : fields) { + value.fill(0x00); // Clean the buffer + err = GetProperty(field.first, value.data()); + if (err != VmbErrorSuccess) { + break; } - ySize = atoi(strYSize); + field.second = atoi(value.data()); } - return DEVICE_OK; + return err; } int AlliedVisionCamera::ClearROI() { std::string maxWidth, maxHeight; - VmbError_t err = getFeatureValue(g_WidthMax, maxWidth); - if (VmbErrorSuccess != err) { - return err; - } - - err = getFeatureValue(g_HeightMax, maxHeight); - if (VmbErrorSuccess != err) { - return err; - } - - std::string offsetXval = "0"; - std::string offsetYval = "0"; - - err = setFeatureValue(g_OffsetX, offsetXval); - if (VmbErrorSuccess != err) { - return err; - } - - err = setFeatureValue(g_OffsetY, offsetYval); + VmbError_t err = getFeatureValue(g_WidthMax, maxWidth) || + getFeatureValue(g_HeightMax, maxHeight); if (VmbErrorSuccess != err) { return err; } - err = setFeatureValue(g_Width, maxWidth); - if (VmbErrorSuccess != err) { - return err; - } + std::unordered_map fields = {{g_OffsetX, "0"}, + {g_OffsetY, "0"}, + {g_Width, maxWidth}, + {g_Height, maxHeight}}; - err = setFeatureValue(g_Height, maxHeight); - if (VmbErrorSuccess != err) { - return err; + for (auto& field : fields) { + err = setFeatureValue(field.first, field.second); + if (err != VmbErrorSuccess) { + break; + } } return resizeImageBuffer(); @@ -504,7 +463,7 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, // Get read/write mode - assume binning horizontal and vertical has the same // mode - bool rMode, wMode, readOnly, featureAvailable; + bool rMode, wMode, readOnly; err = m_sdk->VmbFeatureAccessQuery_t(m_handle, g_BinningVerticalFeature, &rMode, &wMode); if (VmbErrorSuccess != err) { @@ -590,7 +549,6 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, break; } - //// TODO return uManager error return err; } @@ -618,17 +576,12 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, } else { // Retrive each feature for (const auto& featureName : featureNames) { - // Get Feature Info + // Get Feature Info and Access Mode VmbFeatureInfo_t featureInfo; + bool rMode{}, wMode{}, readOnly{}; err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), - &featureInfo, sizeof(featureInfo)); - if (VmbErrorSuccess != err) { - return err; - } - - // Get Access Mode - bool rMode, wMode, readOnly, featureAvailable; - err = m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), + &featureInfo, sizeof(featureInfo)) || + m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, &wMode); if (VmbErrorSuccess != err) { return err; @@ -660,28 +613,31 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Update property's limits and allowed values if (wMode) { err = setAllowedValues(&featureInfo, propertyName.c_str()); - if (VmbErrorSuccess != err) { - return err; - } } break; case MM::ActionType::AfterSet: //!< Update feature from property if (wMode) { err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); - if (err != VmbErrorSuccess) { - if (featureInfo.featureDataType == - VmbFeatureDataType::VmbFeatureDataFloat || - featureInfo.featureDataType == - VmbFeatureDataType::VmbFeatureDataInt) { - auto propertyItem = m_propertyItems.at(propertyName); - std::string adjustedValue = - adjustValue(propertyItem.m_min, propertyItem.m_max, - propertyItem.m_step, std::stod(propertyValue)); - pProp->Set(adjustedValue.c_str()); - err = setFeatureValue(&featureInfo, featureName.c_str(), - adjustedValue); + if (err == VmbErrorInvalidValue) { + // Update limits first to have latest min and max + err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (VmbErrorSuccess != err) { + return err; } + + // Adjust value + double min{}, max{}; + err = GetPropertyLowerLimit(propertyName.c_str(), min) || + GetPropertyUpperLimit(propertyName.c_str(), max); + if (VmbErrorSuccess != err) { + return err; + } + std::string adjustedValue = + adjustValue(featureInfo, min, max, std::stod(propertyValue)); + pProp->Set(adjustedValue.c_str()); + err = setFeatureValue(&featureInfo, featureName.c_str(), + adjustedValue); } } break; @@ -839,7 +795,7 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, } } // Set back property to "Command" - SetProperty(property.c_str(), g_Command); + err = SetProperty(property.c_str(), g_Command); } else { err = VmbErrorInvalidValue; } @@ -890,8 +846,36 @@ void AlliedVisionCamera::mapPropertyNameToFeatureNames( } } -std::string AlliedVisionCamera::adjustValue(double min, double max, double step, +std::string AlliedVisionCamera::adjustValue(VmbFeatureInfo_t& featureInfo, + double min, double max, double propertyValue) const { + VmbError_t err = VmbErrorSuccess; + double step = 1.0; + VmbInt64_t stepI = 1; + bool isIncremental = true; + switch (featureInfo.featureDataType) { + case VmbFeatureDataFloat: + err = m_sdk->VmbFeatureFloatIncrementQuery_t(m_handle, featureInfo.name, + &isIncremental, &step); + break; + case VmbFeatureDataInt: + err = m_sdk->VmbFeatureIntIncrementQuery_t(m_handle, featureInfo.name, + &stepI); + step = static_cast(stepI); + break; + default: + // nothing + break; + } + if (VmbErrorSuccess != err) { + LogMessage("Cannot get feature incremental step to adjust a value"); + return std::to_string(propertyValue); + } + + if (!isIncremental) { + return std::to_string(propertyValue); + } + if (propertyValue > max) { return std::to_string(max); } @@ -916,11 +900,6 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, } VmbError_t err = VmbErrorSuccess; - auto search = m_propertyItems.find(propertyName); - if (search == m_propertyItems.end()) { - LogMessage("Cannot find propery on internal list"); - return VmbErrorInvalidValue; - } switch (feature->featureDataType) { case VmbFeatureDataCommand: @@ -929,33 +908,13 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, break; } case VmbFeatureDataFloat: { - double min = 0; - double max = 0; + double min, max; err = m_sdk->VmbFeatureFloatRangeQuery_t(m_handle, feature->name, &min, &max); if (VmbErrorSuccess != err || min == max) { return err; } - double step = 0.0; - bool isIncremental = false; - err = m_sdk->VmbFeatureFloatIncrementQuery_t(m_handle, feature->name, - &isIncremental, &step); - if (VmbErrorSuccess != err) { - return err; - } - - PropertyItem tempProp{propertyName, static_cast(min), - static_cast(max), - static_cast(step)}; - if (tempProp == search->second) { - break; - } - - m_propertyItems.at(propertyName).m_min = static_cast(min); - m_propertyItems.at(propertyName).m_max = static_cast(max); - m_propertyItems.at(propertyName).m_step = static_cast(step); - err = SetPropertyLimits(propertyName, min, max); break; } @@ -977,7 +936,7 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, break; } case VmbFeatureDataInt: { - VmbInt64_t min, max, step; + VmbInt64_t min, max; std::vector strValues; err = @@ -986,24 +945,7 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, return err; } - err = - m_sdk->VmbFeatureIntIncrementQuery_t(m_handle, feature->name, &step); - if (VmbErrorSuccess != err) { - return err; - } - - PropertyItem tempProp{propertyName, static_cast(min), - static_cast(max), - static_cast(step)}; - if (tempProp == search->second) { - break; - } - - m_propertyItems.at(propertyName).m_min = static_cast(min); - m_propertyItems.at(propertyName).m_max = static_cast(max); - m_propertyItems.at(propertyName).m_step = static_cast(step); - - err = SetPropertyLimits(propertyName, min, max); + err = SetPropertyLimits(propertyName, static_cast(min), static_cast(max)); break; } case VmbFeatureDataString: @@ -1028,37 +970,30 @@ int AlliedVisionCamera::SnapImage() { frame.bufferSize = m_bufferSize; VmbError_t err = - m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); - if (err == VmbErrorSuccess) { - err = m_sdk->VmbCaptureStart_t(m_handle); - if (err == VmbErrorSuccess) { - err = m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); - if (err == VmbErrorSuccess) { - err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); - m_isAcquisitionRunning = true; - if (err == VmbErrorSuccess) { - err = m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); - } - } - } - } + m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)) || + m_sdk->VmbCaptureStart_t(m_handle) || + m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr) || + m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart) || + m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); + + m_isAcquisitionRunning = true; (void)StopSequenceAcquisition(); + return err; } int AlliedVisionCamera::StartSequenceAcquisition(long numImages, double interval_ms, bool stopOnOverflow) { + (void)stopOnOverflow; + (void)interval_ms; + (void)numImages; + if (IsCapturing()) { return DEVICE_CAMERA_BUSY_ACQUIRING; } - int err = GetCoreCallback()->PrepareForAcq(this); - if (err != DEVICE_OK) { - return err; - } - - err = resizeImageBuffer(); + int err = GetCoreCallback()->PrepareForAcq(this) || resizeImageBuffer(); if (err != VmbErrorSuccess) { return err; } @@ -1071,36 +1006,31 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, m_frames[i].context[1] = reinterpret_cast(i); //VmbFrameAnnounce_t(m_handle, &(m_frames[i]), sizeof(VmbFrame_t)); - if (err != VmbErrorSuccess) { - return err; - } + auto frameCallback = [](const VmbHandle_t cameraHandle, + const VmbHandle_t streamHandle, VmbFrame_t* frame) { + (void)cameraHandle; + (void)streamHandle; + reinterpret_cast(frame->context[0]) + ->insertFrame(frame); + }; - err = m_sdk->VmbCaptureFrameQueue_t( - m_handle, &(m_frames[i]), - [](const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, - VmbFrame_t* frame) { - reinterpret_cast(frame->context[0]) - ->insertFrame(frame); - }); + err = + m_sdk->VmbFrameAnnounce_t(m_handle, &(m_frames[i]), + sizeof(VmbFrame_t)) || + m_sdk->VmbCaptureFrameQueue_t(m_handle, &(m_frames[i]), frameCallback); if (err != VmbErrorSuccess) { return err; } } - err = m_sdk->VmbCaptureStart_t(m_handle); - if (err != VmbErrorSuccess) { - return err; - } + err = m_sdk->VmbCaptureStart_t(m_handle) || + m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); + m_isAcquisitionRunning = true; - err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); if (err != VmbErrorSuccess) { return err; } - m_isAcquisitionRunning = true; - return err; } @@ -1108,6 +1038,7 @@ int AlliedVisionCamera::StartSequenceAcquisition(double interval_ms) { return StartSequenceAcquisition(LONG_MAX, interval_ms, true); } int AlliedVisionCamera::StopSequenceAcquisition() { + // This method shall never return any error if (IsCapturing()) { auto err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStop); m_isAcquisitionRunning = false; @@ -1116,20 +1047,9 @@ int AlliedVisionCamera::StopSequenceAcquisition() { } } - auto err = m_sdk->VmbCaptureEnd_t(m_handle); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbCaptureQueueFlush_t(m_handle); - if (err != VmbErrorSuccess) { - return err; - } - - err = m_sdk->VmbFrameRevokeAll_t(m_handle); - if (err != VmbErrorSuccess) { - return err; - } + auto err = m_sdk->VmbCaptureEnd_t(m_handle) || + m_sdk->VmbCaptureQueueFlush_t(m_handle) || + m_sdk->VmbFrameRevokeAll_t(m_handle); return err; } @@ -1160,6 +1080,8 @@ void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { m_handle, frame, [](const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, VmbFrame_t* frame) { + (void)cameraHandle; + (void)streamHandle; reinterpret_cast(frame->context[0]) ->insertFrame(frame); }); diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 146b41ebd..86d4e0a39 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -25,7 +25,6 @@ #include "DeviceBase.h" #include "Loader/LibLoader.h" -#include "PropertyItem.h" /////////////////////////////////////////////////////////////////////////////// // STATIC FEATURE NAMES (FROM VIMBA) @@ -138,11 +137,9 @@ class AlliedVisionCamera : public CCameraBase { /** * @brief Helper method to create single uManager property from Vimba feature * @param[in] feature Pointer to the Vimba feature - * @param[in] callback uManager callback for given property * @return VmbError_t */ - VmbError_t createPropertyFromFeature(const VmbFeatureInfo_t* feature, - MM::ActionFunctor* callback); + VmbError_t createPropertyFromFeature(const VmbFeatureInfo_t* feature); /** * @brief Helper method to set allowed values for given property, based on @@ -220,13 +217,22 @@ class AlliedVisionCamera : public CCameraBase { /** * @brief In case trying to set invalid value, adjust it to the closest with * inceremntal step - * @param[in] min Minimum for given property - * @param[in] max Maximum for given property + * @param[in] step Incremental step + + * @return Adjusted value resresented as a string + */ + + /** + * @brief In case trying to set invalid value, adjust it to the closest with + * inceremntal step + * @param[in] featureInfo Feature info object + * @param[in] min Minimum for given property + * @param[in] max Maximum for given property * @param[in] propertyValue Value that was tried to be set * @return Adjusted value resresented as a string */ - std::string adjustValue(double min, double max, double step, + std::string adjustValue(VmbFeatureInfo_t& featureInfo, double min, double max, double propertyValue) const; /////////////////////////////////////////////////////////////////////////////// @@ -241,8 +247,6 @@ class AlliedVisionCamera : public CCameraBase { VmbUint32_t m_bufferSize; // - m_propertyItems; //!< Internal map of properties static const std::unordered_map m_featureToProperty; //!< Map of features name into uManager properties static const std::unordered_multimap diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj index ead62b67c..7174e830c 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj @@ -21,7 +21,6 @@ - @@ -34,7 +33,6 @@ - diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters index 2d7e866f7..15ee3a694 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters @@ -42,9 +42,6 @@ Header Files - - Header Files - Header Files @@ -56,9 +53,6 @@ Source Files - - Source Files - Source Files diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp index b800b5c8c..47f400949 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp @@ -5,6 +5,7 @@ AlliedVisionHub::AlliedVisionHub() : m_sdk(std::make_shared()) {} int AlliedVisionHub::DetectInstalledDevices() { + LogMessage("Detecting installed cameras..."); VmbUint32_t camNum; // Get the number of connected cameras first VmbError_t err = m_sdk->VmbCamerasList_t(nullptr, 0, &camNum, 0); diff --git a/DeviceAdapters/AlliedVisionCamera/PropertyItem.cpp b/DeviceAdapters/AlliedVisionCamera/PropertyItem.cpp deleted file mode 100644 index 4b0865174..000000000 --- a/DeviceAdapters/AlliedVisionCamera/PropertyItem.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "PropertyItem.h" - -PropertyItem::PropertyItem(const std::string& name, double min, double max, - double step) - : m_name(name), m_min(min), m_max(max), m_step(step) {} \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/PropertyItem.h b/DeviceAdapters/AlliedVisionCamera/PropertyItem.h deleted file mode 100644 index ed1232dd7..000000000 --- a/DeviceAdapters/AlliedVisionCamera/PropertyItem.h +++ /dev/null @@ -1,43 +0,0 @@ -/*============================================================================= - Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. - - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, - NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=============================================================================*/ -#ifndef PROPERTYITEM_H -#define PROPERTYITEM_H - -#include - -/** - * @brief - */ -struct PropertyItem { - PropertyItem(const std::string& name, double min = 0.0, double max = 0.0, double step = 1.0); - ~PropertyItem() = default; - - bool operator==(PropertyItem& rhs) { - return rhs.m_name == m_name && rhs.m_min == m_min && rhs.m_max == m_max && - rhs.m_step == m_step; - } - bool operator!=(PropertyItem& rhs) { return !(rhs == *this); } - - std::string m_name; - double m_min; - double m_max; - double m_step; -}; - -#endif // PROPERTYITEM_H From 9edd88274aca9e135ab94f96b94365f7584e0bc6 Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Mon, 17 Jul 2023 11:21:20 +0200 Subject: [PATCH 016/141] Error handling Changes: - Error handling for issues related to the loading SDK Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 36 +++++-------------- .../AlliedVisionCamera/AlliedVisionCamera.h | 5 --- .../AlliedVisionCamera/AlliedVisionHub.cpp | 33 +++++++++++++++-- .../AlliedVisionCamera/AlliedVisionHub.h | 7 +++- .../SDK/Loader/LibLoader.cpp | 6 ++-- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index f386d0614..37731e7bc 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -90,8 +90,6 @@ AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) m_bufferSize{0}, m_isAcquisitionRunning{false} { CreateHubIDProperty(); - InitializeDefaultErrorMessages(); - setApiErrorMessages(); } int AlliedVisionCamera::Initialize() { @@ -119,36 +117,16 @@ int AlliedVisionCamera::Initialize() { int AlliedVisionCamera::Shutdown() { LogMessage("Shutting down camera: " + m_cameraName); VmbError_t err = VmbErrorSuccess; - - (void)StopSequenceAcquisition(); - if (m_handle != nullptr) { - err = m_sdk->VmbCameraClose_t(m_handle); + if (m_sdk != nullptr && m_sdk->isInitialized()) { + (void)StopSequenceAcquisition(); + if (m_handle != nullptr) { + err = m_sdk->VmbCameraClose_t(m_handle); + } } return err; } -void AlliedVisionCamera::setApiErrorMessages() { - SetErrorText(VmbErrorApiNotStarted, "Vimba X API not started"); - SetErrorText(VmbErrorNotFound, "Device cannot be found"); - SetErrorText(VmbErrorDeviceNotOpen, "Device cannot be opened"); - SetErrorText(VmbErrorBadParameter, - "Invalid parameter passed to the function"); - SetErrorText(VmbErrorNotImplemented, "Feature not implemented"); - SetErrorText(VmbErrorNotSupported, "Feature not supported"); - SetErrorText(VmbErrorUnknown, "Unknown error"); - SetErrorText(VmbErrorInvalidValue, - "The value is not valid: either out of bounds or not an " - "increment of the minimum"); - SetErrorText(VmbErrorBadHandle, "Given device handle is not valid"); - SetErrorText(VmbErrorInvalidAccess, - "Operation is invalid with the current access mode"); - SetErrorText(VmbErrorTimeout, "Timeout occured"); - SetErrorText(VmbErrorNotAvailable, "Something is not available"); - SetErrorText(VmbErrorNotInitialized, "Something is not initialized"); - SetErrorText(VmbErrorAlready, "The operation has been already done"); -} - VmbError_t AlliedVisionCamera::setupProperties() { VmbUint32_t featureCount = 0; VmbError_t err = m_sdk->VmbFeaturesList_t(m_handle, NULL, 0, &featureCount, @@ -541,6 +519,7 @@ int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, &featureInfoVertical, g_BinningVerticalFeature, propertyValue); if (VmbErrorSuccess != errHor || VmbErrorSuccess != errVer) { //[IMPORTANT] For binning, adjust value is ignored + err = errHor | errVer; } } break; @@ -945,7 +924,8 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, return err; } - err = SetPropertyLimits(propertyName, static_cast(min), static_cast(max)); + err = SetPropertyLimits(propertyName, static_cast(min), + static_cast(max)); break; } case VmbFeatureDataString: diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 86d4e0a39..b530525a1 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -117,11 +117,6 @@ class AlliedVisionCamera : public CCameraBase { static constexpr const VmbUint8_t MAX_FRAMES = 7; //!<< Max frame number in the buffer - /** - * @brief Setup error messages for Vimba API - */ - void setApiErrorMessages(); - /** * @brief Resize all buffers for image frames * @return VmbError_t diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp index 47f400949..729962443 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp @@ -2,7 +2,10 @@ #include "AlliedVisionCamera.h" -AlliedVisionHub::AlliedVisionHub() : m_sdk(std::make_shared()) {} +AlliedVisionHub::AlliedVisionHub() : m_sdk(std::make_shared()) { + InitializeDefaultErrorMessages(); + setApiErrorMessages(); +} int AlliedVisionHub::DetectInstalledDevices() { LogMessage("Detecting installed cameras..."); @@ -32,7 +35,12 @@ int AlliedVisionHub::DetectInstalledDevices() { int AlliedVisionHub::Initialize() { LogMessage("Init HUB"); - return DEVICE_OK; + if (m_sdk->isInitialized()) { + return DEVICE_OK; + } else { + LogMessage("SDK not initialized!"); + return VmbErrorApiNotStarted; + } } int AlliedVisionHub::Shutdown() { @@ -47,3 +55,24 @@ void AlliedVisionHub::GetName(char* name) const { bool AlliedVisionHub::Busy() { return false; } std::shared_ptr& AlliedVisionHub::getSDK() { return m_sdk; } + +void AlliedVisionHub::setApiErrorMessages() { + SetErrorText(VmbErrorApiNotStarted, "Vimba X API not started"); + SetErrorText(VmbErrorNotFound, "Device cannot be found"); + SetErrorText(VmbErrorDeviceNotOpen, "Device cannot be opened"); + SetErrorText(VmbErrorBadParameter, + "Invalid parameter passed to the function"); + SetErrorText(VmbErrorNotImplemented, "Feature not implemented"); + SetErrorText(VmbErrorNotSupported, "Feature not supported"); + SetErrorText(VmbErrorUnknown, "Unknown error"); + SetErrorText(VmbErrorInvalidValue, + "The value is not valid: either out of bounds or not an " + "increment of the minimum"); + SetErrorText(VmbErrorBadHandle, "Given device handle is not valid"); + SetErrorText(VmbErrorInvalidAccess, + "Operation is invalid with the current access mode"); + SetErrorText(VmbErrorTimeout, "Timeout occured"); + SetErrorText(VmbErrorNotAvailable, "Something is not available"); + SetErrorText(VmbErrorNotInitialized, "Something is not initialized"); + SetErrorText(VmbErrorAlready, "The operation has been already done"); +} diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h index a18629d94..09c77aeba 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h @@ -48,9 +48,14 @@ class AlliedVisionHub : public HubBase { /** * @brief SDK getter * @return Pointer to SDK - */ + */ std::shared_ptr& getSDK(); + /** + * @brief Setup error messages for Vimba API + */ + void setApiErrorMessages(); + /////////////////////////////////////////////////////////////////////////////// // uMANAGER API METHODS /////////////////////////////////////////////////////////////////////////////// diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp index 95c43266c..44b6a5336 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp @@ -98,8 +98,10 @@ VimbaXApi::VimbaXApi() bool VimbaXApi::isInitialized() const { return m_initialized; } VimbaXApi::~VimbaXApi() { - m_initialized = false; - VmbShutdown_t(); + if (m_initialized) { + m_initialized = false; + VmbShutdown_t(); + } } LibLoader::LibLoader(const char* lib, const char* libPath) From 432e4eec120df9bcc6822e191239a6a4500c88f2 Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Tue, 18 Jul 2023 10:14:30 +0200 Subject: [PATCH 017/141] Few fixes Changes: - Removed RAW features from the list - Reduced GetProperty calls that generates a lot of calls - Added calling OnPropertyChanged callback for Property Browser Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 41 +++++++++++-------- .../AlliedVisionCamera/AlliedVisionCamera.h | 4 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 37731e7bc..65839f70c 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -176,10 +176,11 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( } VmbError_t err = VmbErrorSuccess; - // Skip Event and Chunk features + // Skip Event, Chunk, RAW features std::string featureCategory = feature->category; if (featureCategory.find(g_EventCategory) != std::string::npos || - featureCategory.find(g_ChunkCategory) != std::string::npos) { + featureCategory.find(g_ChunkCategory) != std::string::npos || + feature->featureDataType == VmbFeatureDataRaw) { return err; } @@ -259,23 +260,23 @@ const unsigned char* AlliedVisionCamera::GetImageBuffer() { } unsigned AlliedVisionCamera::GetImageWidth() const { - char value[MM::MaxStrLength]; - int ret = GetProperty(g_Width, value); + std::string value{}; + auto ret = getFeatureValue(g_Width, value); if (ret != VmbErrorSuccess) { return 0; } - return atoi(value); + return atoi(value.c_str()); } unsigned AlliedVisionCamera::GetImageHeight() const { - char value[MM::MaxStrLength]; - int ret = GetProperty(g_Height, value); + std::string value{}; + auto ret = getFeatureValue(g_Height, value); if (ret != VmbErrorSuccess) { return 0; } - return atoi(value); + return atoi(value.c_str()); } unsigned AlliedVisionCamera::GetImageBytesPerPixel() const { @@ -306,13 +307,13 @@ int AlliedVisionCamera::SetBinning(int binSize) { } double AlliedVisionCamera::GetExposure() const { - char strExposure[MM::MaxStrLength]; - int ret = GetProperty(MM::g_Keyword_Exposure, strExposure); + std::string value{}; + auto ret = getFeatureValue(g_ExposureFeature, value); if (ret != VmbErrorSuccess) { - return 0.0; + return 0; } - return strtod(strExposure, nullptr); + return strtod(value.c_str(), nullptr); } void AlliedVisionCamera::SetExposure(double exp_ms) { @@ -371,11 +372,10 @@ int AlliedVisionCamera::GetROI(unsigned& x, unsigned& y, unsigned& xSize, std::unordered_map fields = { {g_OffsetX, x}, {g_OffsetY, y}, {g_Width, xSize}, {g_Height, ySize}}; - std::array value; VmbError_t err = VmbErrorSuccess; for (auto& field : fields) { - value.fill(0x00); // Clean the buffer - err = GetProperty(field.first, value.data()); + std::string value{}; + err = getFeatureValue(field.first, value); if (err != VmbErrorSuccess) { break; } @@ -584,11 +584,17 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Update property if (propertyValue != featureValue) { pProp->Set(featureValue.c_str()); + err = GetCoreCallback()->OnPropertyChanged( + this, propertyName.c_str(), featureValue.c_str()); + if (VmbErrorSuccess != err) { + return err; + } } } // Update property's access mode pChildProperty->SetReadOnly(readOnly); + // Update property's limits and allowed values if (wMode) { err = setAllowedValues(&featureInfo, propertyName.c_str()); @@ -614,7 +620,6 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, } std::string adjustedValue = adjustValue(featureInfo, min, max, std::stod(propertyValue)); - pProp->Set(adjustedValue.c_str()); err = setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); } @@ -632,7 +637,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, const char* featureName, - std::string& value) { + std::string& value) const { VmbError_t err = VmbErrorSuccess; switch (featureInfo->featureDataType) { case VmbFeatureDataBool: { @@ -700,7 +705,7 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, } VmbError_t AlliedVisionCamera::getFeatureValue(const char* featureName, - std::string& value) { + std::string& value) const { VmbFeatureInfo_t featureInfo; VmbError_t err = m_sdk->VmbFeatureInfoQuery_t( m_handle, featureName, &featureInfo, sizeof(featureInfo)); diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index b530525a1..8e5db5a8f 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -162,7 +162,7 @@ class AlliedVisionCamera : public CCameraBase { * @return VmbError_t */ VmbError_t getFeatureValue(VmbFeatureInfo_t* featureInfo, - const char* featureName, std::string& value); + const char* featureName, std::string& value) const; /** * @brief Method to get feature value, based on its type. Feature value is * always a string type. @@ -170,7 +170,7 @@ class AlliedVisionCamera : public CCameraBase { * @param[out] value Value of feature, read from device * @return VmbError_t */ - VmbError_t getFeatureValue(const char* featureName, std::string& value); + VmbError_t getFeatureValue(const char* featureName, std::string& value) const; /** * @brief Method to set a feature value, bases on its type. Feature value is From bad9b851b05509b922e8ba0b1a9d02beb2f4b8b6 Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Wed, 19 Jul 2023 11:49:58 +0200 Subject: [PATCH 018/141] Binning and ROI changes Changes: - Removed generic Binning to have Vertical/Horizontal only - Changes to ROI Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 290 ++++++------------ .../AlliedVisionCamera/AlliedVisionCamera.h | 2 - 2 files changed, 87 insertions(+), 205 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 65839f70c..7e29d6ef4 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -37,16 +37,12 @@ const std::unordered_map AlliedVisionCamera::m_featureToProperty = { {g_PixelFormatFeature, MM::g_Keyword_PixelType}, - {g_ExposureFeature, MM::g_Keyword_Exposure}, - {g_BinningHorizontalFeature, MM::g_Keyword_Binning}, - {g_BinningVerticalFeature, MM::g_Keyword_Binning}}; + {g_ExposureFeature, MM::g_Keyword_Exposure}}; const std::unordered_multimap AlliedVisionCamera::m_propertyToFeature = { {MM::g_Keyword_PixelType, g_PixelFormatFeature}, - {MM::g_Keyword_Exposure, g_ExposureFeature}, - {MM::g_Keyword_Binning, g_BinningHorizontalFeature}, - {MM::g_Keyword_Binning, g_BinningVerticalFeature}}; + {MM::g_Keyword_Exposure, g_ExposureFeature}}; /////////////////////////////////////////////////////////////////////////////// // Exported MMDevice API @@ -292,18 +288,13 @@ unsigned AlliedVisionCamera::GetBitDepth() const { } int AlliedVisionCamera::GetBinning() const { - char value[MM::MaxStrLength]; - int ret = GetProperty(MM::g_Keyword_Binning, value); - if (ret != VmbErrorSuccess) { - return 0; - } - - return atoi(value); + // Binning not supported. We support BinningVertical/Horizontal + return 1; } int AlliedVisionCamera::SetBinning(int binSize) { - return SetProperty(MM::g_Keyword_Binning, - CDeviceUtils::ConvertToString(binSize)); + // Binning not supported. We support BinningVertical/Horizontal + return DEVICE_OK; } double AlliedVisionCamera::GetExposure() const { @@ -369,7 +360,7 @@ int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, int AlliedVisionCamera::GetROI(unsigned& x, unsigned& y, unsigned& xSize, unsigned& ySize) { - std::unordered_map fields = { + std::map fields = { {g_OffsetX, x}, {g_OffsetY, y}, {g_Width, xSize}, {g_Height, ySize}}; VmbError_t err = VmbErrorSuccess; @@ -393,10 +384,12 @@ int AlliedVisionCamera::ClearROI() { return err; } - std::unordered_map fields = {{g_OffsetX, "0"}, - {g_OffsetY, "0"}, - {g_Width, maxWidth}, - {g_Height, maxHeight}}; + // Keep the order of the fields + std::vector> fields = { + {g_OffsetX, "0"}, + {g_OffsetY, "0"}, + {g_Width, maxWidth}, + {g_Height, maxHeight}}; for (auto& field : fields) { err = setFeatureValue(field.first, field.second); @@ -419,118 +412,6 @@ void AlliedVisionCamera::GetName(char* name) const { bool AlliedVisionCamera::IsCapturing() { return m_isAcquisitionRunning; } -int AlliedVisionCamera::OnBinning(MM::PropertyBase* pProp, - MM::ActionType eAct) { - // Get horizonal binning - VmbFeatureInfo_t featureInfoHorizontal; - VmbError_t err = m_sdk->VmbFeatureInfoQuery_t( - m_handle, g_BinningHorizontalFeature, &featureInfoHorizontal, - sizeof(featureInfoHorizontal)); - if (VmbErrorSuccess != err) { - return err; - } - - // Get vertical binning - VmbFeatureInfo_t featureInfoVertical; - err = m_sdk->VmbFeatureInfoQuery_t(m_handle, g_BinningVerticalFeature, - &featureInfoVertical, - sizeof(featureInfoVertical)); - if (VmbErrorSuccess != err) { - return err; - } - - // Get read/write mode - assume binning horizontal and vertical has the same - // mode - bool rMode, wMode, readOnly; - err = m_sdk->VmbFeatureAccessQuery_t(m_handle, g_BinningVerticalFeature, - &rMode, &wMode); - if (VmbErrorSuccess != err) { - return err; - } - - readOnly = (rMode && !wMode); - - // Get values of property and features - std::string propertyValue, featureHorizontalValue, featureVerticalValue; - pProp->Get(propertyValue); - - MM::Property* pChildProperty = (MM::Property*)pProp; - std::vector strValues; - VmbInt64_t minHorizontal, maxHorizontal, minVertical, maxVertical, min, max; - - switch (eAct) { - case MM::ActionType::BeforeGet: - if (rMode) { - err = - getFeatureValue(&featureInfoHorizontal, g_BinningHorizontalFeature, - featureHorizontalValue); - if (VmbErrorSuccess != err) { - return err; - } - err = getFeatureValue(&featureInfoVertical, g_BinningVerticalFeature, - featureVerticalValue); - if (VmbErrorSuccess != err) { - return err; - } - - std::string featureValue = - std::to_string(std::min(atoi(featureHorizontalValue.c_str()), - atoi(featureVerticalValue.c_str()))); - // Update property - if (propertyValue != featureValue) { - pProp->Set(featureValue.c_str()); - } - } - - // Update property's access mode - pChildProperty->SetReadOnly(readOnly); - // Update property's limits and allowed values - if (wMode) { - // Update binning limits - err = m_sdk->VmbFeatureIntRangeQuery_t(m_handle, - g_BinningHorizontalFeature, - &minHorizontal, &maxHorizontal); - if (VmbErrorSuccess != err) { - return err; - } - - err = m_sdk->VmbFeatureIntRangeQuery_t( - m_handle, g_BinningVerticalFeature, &minVertical, &maxVertical); - if (VmbErrorSuccess != err) { - return err; - } - - //[IMPORTANT] For binning, increment step is ignored - - min = std::max(minHorizontal, minVertical); - max = std::min(maxHorizontal, maxVertical); - - for (VmbInt64_t i = min; i <= max; i++) { - strValues.push_back(std::to_string(i)); - } - err = SetAllowedValues(pProp->GetName().c_str(), strValues); - } - break; - case MM::ActionType::AfterSet: - if (wMode) { - VmbError_t errHor = setFeatureValue( - &featureInfoHorizontal, g_BinningHorizontalFeature, propertyValue); - VmbError_t errVer = setFeatureValue( - &featureInfoVertical, g_BinningVerticalFeature, propertyValue); - if (VmbErrorSuccess != errHor || VmbErrorSuccess != errVer) { - //[IMPORTANT] For binning, adjust value is ignored - err = errHor | errVer; - } - } - break; - default: - // nothing - break; - } - - return err; -} - int AlliedVisionCamera::OnPixelType(MM::PropertyBase* pProp, MM::ActionType eAct) { // TODO implement @@ -548,87 +429,85 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Check property mapping mapPropertyNameToFeatureNames(propertyName.c_str(), featureNames); - if (propertyName == std::string(MM::g_Keyword_Binning)) { - // Binning requires special handling and combining two features into one - // property - OnBinning(pProp, eAct); - } else { - // Retrive each feature - for (const auto& featureName : featureNames) { - // Get Feature Info and Access Mode - VmbFeatureInfo_t featureInfo; - bool rMode{}, wMode{}, readOnly{}; - err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), - &featureInfo, sizeof(featureInfo)) || - m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), - &rMode, &wMode); - if (VmbErrorSuccess != err) { - return err; - } + // Retrive each feature + for (const auto& featureName : featureNames) { + // Get Feature Info and Access Mode + VmbFeatureInfo_t featureInfo; + bool rMode{}, wMode{}, readOnly{}; + err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), + &featureInfo, sizeof(featureInfo)) || + m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, + &wMode); + if (VmbErrorSuccess != err) { + return err; + } + + readOnly = (rMode && !wMode); + //TODO + //Set somehow property to be unavailable when there is no r and w modes - readOnly = (rMode && !wMode); + // Get values + std::string propertyValue{}, featureValue{}; + pProp->Get(propertyValue); + + // Handle property value change + switch (eAct) { + case MM::ActionType::BeforeGet: //!< Update property from feature + if (rMode) { + err = + getFeatureValue(&featureInfo, featureName.c_str(), featureValue); + if (VmbErrorSuccess != err) { + return err; + } - // Get values - std::string propertyValue, featureValue; - pProp->Get(propertyValue); + // Update property + if (propertyValue != featureValue) { + pProp->Set(featureValue.c_str()); + err = GetCoreCallback()->OnPropertyChanged( + this, propertyName.c_str(), featureValue.c_str()); - // Handle property value change - switch (eAct) { - case MM::ActionType::BeforeGet: //!< Update property from feature - if (rMode) { - err = getFeatureValue(&featureInfo, featureName.c_str(), - featureValue); if (VmbErrorSuccess != err) { return err; } - // Update property - if (propertyValue != featureValue) { - pProp->Set(featureValue.c_str()); - err = GetCoreCallback()->OnPropertyChanged( - this, propertyName.c_str(), featureValue.c_str()); - if (VmbErrorSuccess != err) { - return err; - } - } } + } - // Update property's access mode - pChildProperty->SetReadOnly(readOnly); + // Update property's access mode + pChildProperty->SetReadOnly(readOnly); - // Update property's limits and allowed values - if (wMode) { + // Update property's limits and allowed values + if (wMode) { + err = setAllowedValues(&featureInfo, propertyName.c_str()); + } + break; + case MM::ActionType::AfterSet: //!< Update feature from property + if (wMode) { + err = + setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); + if (err == VmbErrorInvalidValue) { + // Update limits first to have latest min and max err = setAllowedValues(&featureInfo, propertyName.c_str()); - } - break; - case MM::ActionType::AfterSet: //!< Update feature from property - if (wMode) { - err = setFeatureValue(&featureInfo, featureName.c_str(), - propertyValue); - if (err == VmbErrorInvalidValue) { - // Update limits first to have latest min and max - err = setAllowedValues(&featureInfo, propertyName.c_str()); - if (VmbErrorSuccess != err) { - return err; - } - - // Adjust value - double min{}, max{}; - err = GetPropertyLowerLimit(propertyName.c_str(), min) || - GetPropertyUpperLimit(propertyName.c_str(), max); - if (VmbErrorSuccess != err) { - return err; - } - std::string adjustedValue = - adjustValue(featureInfo, min, max, std::stod(propertyValue)); - err = setFeatureValue(&featureInfo, featureName.c_str(), - adjustedValue); + if (VmbErrorSuccess != err) { + return err; + } + + // Adjust value + double min{}, max{}; + err = GetPropertyLowerLimit(propertyName.c_str(), min) || + GetPropertyUpperLimit(propertyName.c_str(), max); + if (VmbErrorSuccess != err) { + return err; } + std::string adjustedValue = + adjustValue(featureInfo, min, max, std::stod(propertyValue)); + err = setFeatureValue(&featureInfo, featureName.c_str(), + adjustedValue); } - break; - default: - // nothing - break; - } + } + break; + default: + // nothing + break; } } @@ -780,6 +659,8 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, } // Set back property to "Command" err = SetProperty(property.c_str(), g_Command); + GetCoreCallback()->OnPropertyChanged(this, property.c_str(), + g_Command); } else { err = VmbErrorInvalidValue; } @@ -1016,7 +897,7 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, return err; } - return err; + return UpdateStatus(); } int AlliedVisionCamera::StartSequenceAcquisition(double interval_ms) { @@ -1035,8 +916,11 @@ int AlliedVisionCamera::StopSequenceAcquisition() { auto err = m_sdk->VmbCaptureEnd_t(m_handle) || m_sdk->VmbCaptureQueueFlush_t(m_handle) || m_sdk->VmbFrameRevokeAll_t(m_handle); + if (err != VmbErrorSuccess) { + return err; + } - return err; + return UpdateStatus(); } void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 8e5db5a8f..bea9b8e2e 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -104,8 +104,6 @@ class AlliedVisionCamera : public CCameraBase { /////////////////////////////////////////////////////////////////////////////// int OnPixelType(MM::PropertyBase* pProp, MM::ActionType eAct); //!<< PixelType property callback - int OnBinning(MM::PropertyBase* pProp, - MM::ActionType eAct); //!<< Binning property callback int onProperty(MM::PropertyBase* pProp, MM::ActionType eAct); //!<< General property callback From 3bd5b99ba841e3a11d693b34c48716e173ff1fe1 Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Fri, 21 Jul 2023 14:25:50 +0200 Subject: [PATCH 019/141] Error handling Changes: - Error handling improvements - Created a base class for devices to handle the same stuff everywhere - Created logging macro Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 138 ++++++++++++++---- .../AlliedVisionCamera/AlliedVisionCamera.h | 9 +- .../AlliedVisionCamera.vcxproj | 2 + .../AlliedVisionCamera.vcxproj.filters | 6 + .../AlliedVisionDeviceBase.cpp | 3 + .../AlliedVisionDeviceBase.h | 97 ++++++++++++ .../AlliedVisionCamera/AlliedVisionHub.cpp | 30 +--- .../AlliedVisionCamera/AlliedVisionHub.h | 14 +- 8 files changed, 229 insertions(+), 70 deletions(-) create mode 100644 DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.cpp create mode 100644 DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 7e29d6ef4..67e0b8a6c 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -77,8 +77,7 @@ AlliedVisionCamera::~AlliedVisionCamera() { } AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) - : CCameraBase(), - m_sdk(nullptr), + : m_sdk(nullptr), m_handle{nullptr}, m_cameraName{deviceName}, m_frames{}, @@ -91,8 +90,8 @@ AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) int AlliedVisionCamera::Initialize() { auto parentHub = dynamic_cast(GetParentHub()); if (parentHub == nullptr) { - LogMessage("Parent HUB not found!"); - return DEVICE_ERR; + LOG_ERROR(VmbErrorBadParameter, "Parent HUB not found!"); + return VmbErrorBadParameter; } m_sdk = parentHub->getSDK(); @@ -100,6 +99,7 @@ int AlliedVisionCamera::Initialize() { VmbError_t err = m_sdk->VmbCameraOpen_t( m_cameraName.c_str(), VmbAccessModeType::VmbAccessModeFull, &m_handle); if (err != VmbErrorSuccess || m_handle == nullptr) { + LOG_ERROR(err, "Error while opening camera or handle is NULL!"); return err; } @@ -127,7 +127,8 @@ VmbError_t AlliedVisionCamera::setupProperties() { VmbUint32_t featureCount = 0; VmbError_t err = m_sdk->VmbFeaturesList_t(m_handle, NULL, 0, &featureCount, sizeof(VmbFeatureInfo_t)); - if (err != VmbErrorSuccess || !featureCount) { + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error occurred when obtaining features count!"); return err; } @@ -136,6 +137,7 @@ VmbError_t AlliedVisionCamera::setupProperties() { err = m_sdk->VmbFeaturesList_t(m_handle, features.get(), featureCount, &featureCount, sizeof(VmbFeatureInfo_t)); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error occurred when obtaining features!"); return err; } @@ -143,7 +145,8 @@ VmbError_t AlliedVisionCamera::setupProperties() { for (VmbFeatureInfo_t* feature = features.get(); feature != end; ++feature) { err = createPropertyFromFeature(feature); if (err != VmbErrorSuccess) { - LogMessageCode(err); + LOG_ERROR(err, + "Error while creating property" + std::string(feature->name)); continue; } } @@ -154,6 +157,7 @@ VmbError_t AlliedVisionCamera::setupProperties() { VmbError_t AlliedVisionCamera::resizeImageBuffer() { VmbError_t err = m_sdk->VmbPayloadSizeGet_t(m_handle, &m_bufferSize); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while reading payload size"); return err; } @@ -168,6 +172,7 @@ VmbError_t AlliedVisionCamera::resizeImageBuffer() { VmbError_t AlliedVisionCamera::createPropertyFromFeature( const VmbFeatureInfo_t* feature) { if (feature == nullptr) { + LogMessage("Cannot create feature. It is NULL"); return VmbErrorInvalidValue; } @@ -177,6 +182,7 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( if (featureCategory.find(g_EventCategory) != std::string::npos || featureCategory.find(g_ChunkCategory) != std::string::npos || feature->featureDataType == VmbFeatureDataRaw) { + // Skip return err; } @@ -198,7 +204,7 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( camera->mapFeatureNameToPropertyName(name, propertyName); auto err = camera->UpdateProperty(propertyName.c_str()); if (err != VmbErrorSuccess) { - camera->LogMessage("Property: " + propertyName + " update failed"); + camera->LOG_ERROR(err, "Property: " + propertyName + " update failed"); } }; @@ -206,6 +212,8 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( err = m_sdk->VmbFeatureInvalidationRegister_t(m_handle, feature->name, vmbCallback, this); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while registering invalidation callback for " + + std::string(feature->name)); return err; } @@ -248,6 +256,11 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( break; } + if (err != VmbErrorSuccess) { + LOG_ERROR(err, + "Error while creating property " + std::string(feature->name)); + } + return err; } @@ -259,6 +272,7 @@ unsigned AlliedVisionCamera::GetImageWidth() const { std::string value{}; auto ret = getFeatureValue(g_Width, value); if (ret != VmbErrorSuccess) { + LOG_ERROR(ret, "Error while getting image width!"); return 0; } @@ -269,6 +283,7 @@ unsigned AlliedVisionCamera::GetImageHeight() const { std::string value{}; auto ret = getFeatureValue(g_Height, value); if (ret != VmbErrorSuccess) { + LOG_ERROR(ret, "Error while getting image height!"); return 0; } @@ -301,6 +316,7 @@ double AlliedVisionCamera::GetExposure() const { std::string value{}; auto ret = getFeatureValue(g_ExposureFeature, value); if (ret != VmbErrorSuccess) { + LOG_ERROR(ret, "Error while getting exposure!"); return 0; } @@ -332,25 +348,29 @@ int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, }; if (xSize > width) { - err = setOffsetXProperty(x) || setWidthProperty(xSize); + err = setOffsetXProperty(x) | setWidthProperty(xSize); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while ROI X!"); return err; } } else { - err = setWidthProperty(xSize) || setOffsetXProperty(x); + err = setWidthProperty(xSize) | setOffsetXProperty(x); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while ROI X!"); return err; } } if (ySize > height) { - err = setOffsetYProperty(y) || setHeightProperty(ySize); + err = setOffsetYProperty(y) | setHeightProperty(ySize); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while ROI Y!"); return err; } } else { - err = setHeightProperty(ySize) || setOffsetYProperty(y); + err = setHeightProperty(ySize) | setOffsetYProperty(y); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while ROI Y!"); return err; } } @@ -368,6 +388,7 @@ int AlliedVisionCamera::GetROI(unsigned& x, unsigned& y, unsigned& xSize, std::string value{}; err = getFeatureValue(field.first, value); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while getting ROI!"); break; } field.second = atoi(value.data()); @@ -378,9 +399,10 @@ int AlliedVisionCamera::GetROI(unsigned& x, unsigned& y, unsigned& xSize, int AlliedVisionCamera::ClearROI() { std::string maxWidth, maxHeight; - VmbError_t err = getFeatureValue(g_WidthMax, maxWidth) || + VmbError_t err = getFeatureValue(g_WidthMax, maxWidth) | getFeatureValue(g_HeightMax, maxHeight); if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while clearing ROI!"); return err; } @@ -394,6 +416,7 @@ int AlliedVisionCamera::ClearROI() { for (auto& field : fields) { err = setFeatureValue(field.first, field.second); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while clearing ROI!"); break; } } @@ -433,18 +456,25 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, for (const auto& featureName : featureNames) { // Get Feature Info and Access Mode VmbFeatureInfo_t featureInfo; - bool rMode{}, wMode{}, readOnly{}; + bool rMode{}, wMode{}, readOnly{}, featureAvailable{}; err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), - &featureInfo, sizeof(featureInfo)) || + &featureInfo, sizeof(featureInfo)) | m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, &wMode); if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting info or access query!"); return err; } readOnly = (rMode && !wMode); - //TODO - //Set somehow property to be unavailable when there is no r and w modes + featureAvailable = rMode || wMode; + if (!featureAvailable) { + LOG_ERROR(VmbErrorFeaturesUnavailable, + "Feature is currently not available! Feature: " + featureName); + return err; + } + // TODO + // Set somehow property to be unavailable when there is no r and w modes // Get values std::string propertyValue{}, featureValue{}; @@ -457,6 +487,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, err = getFeatureValue(&featureInfo, featureName.c_str(), featureValue); if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting feature value " + featureName); return err; } @@ -467,6 +498,9 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, this, propertyName.c_str(), featureValue.c_str()); if (VmbErrorSuccess != err) { + LOG_ERROR(err, + "Error while calling OnPropertyChanged callback for " + + featureName); return err; } } @@ -488,14 +522,17 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Update limits first to have latest min and max err = setAllowedValues(&featureInfo, propertyName.c_str()); if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while setting allowed values for feature " + + featureName); return err; } // Adjust value double min{}, max{}; - err = GetPropertyLowerLimit(propertyName.c_str(), min) || + err = GetPropertyLowerLimit(propertyName.c_str(), min) | GetPropertyUpperLimit(propertyName.c_str(), max); if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting limits for " + propertyName); return err; } std::string adjustedValue = @@ -511,6 +548,10 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, } } + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while updating property " + propertyName); + } + return err; } @@ -580,6 +621,12 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, // nothing break; } + + if (VmbErrorSuccess != err) { + LOG_ERROR(err, + "Error while getting feature value " + std::string(featureName)); + } + return err; } @@ -589,6 +636,8 @@ VmbError_t AlliedVisionCamera::getFeatureValue(const char* featureName, VmbError_t err = m_sdk->VmbFeatureInfoQuery_t( m_handle, featureName, &featureInfo, sizeof(featureInfo)); if (VmbErrorSuccess != err) { + LOG_ERROR(err, + "Error while getting feature value " + std::string(featureName)); return err; } @@ -630,7 +679,6 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, err = m_sdk->VmbFeatureStringMaxlengthQuery_t(m_handle, featureName, &maxLen); if (err != VmbErrorSuccess) { - LogMessageCode(err); break; } if (value.size() > maxLen) { @@ -653,7 +701,6 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, err = m_sdk->VmbFeatureCommandIsDone_t(m_handle, featureName, &isDone); if (err != VmbErrorSuccess) { - LogMessageCode(err); break; } } @@ -661,6 +708,9 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, err = SetProperty(property.c_str(), g_Command); GetCoreCallback()->OnPropertyChanged(this, property.c_str(), g_Command); + if (err != VmbErrorSuccess) { + break; + } } else { err = VmbErrorInvalidValue; } @@ -673,6 +723,12 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, // nothing break; } + + if (VmbErrorSuccess != err) { + LOG_ERROR(err, + "Error while setting feature value " + std::string(featureName)); + } + return err; } @@ -682,6 +738,8 @@ VmbError_t AlliedVisionCamera::setFeatureValue(const char* featureName, VmbError_t err = m_sdk->VmbFeatureInfoQuery_t( m_handle, featureName, &featureInfo, sizeof(featureInfo)); if (VmbErrorSuccess != err) { + LOG_ERROR(err, + "Error while setting feature value " + std::string(featureName)); return err; } @@ -733,7 +791,8 @@ std::string AlliedVisionCamera::adjustValue(VmbFeatureInfo_t& featureInfo, break; } if (VmbErrorSuccess != err) { - LogMessage("Cannot get feature incremental step to adjust a value"); + LOG_ERROR(err, "Error while getting increment query for feature " + + std::string(featureInfo.name)); return std::to_string(propertyValue); } @@ -777,7 +836,7 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, err = m_sdk->VmbFeatureFloatRangeQuery_t(m_handle, feature->name, &min, &max); if (VmbErrorSuccess != err || min == max) { - return err; + break; } err = SetPropertyLimits(propertyName, min, max); @@ -790,7 +849,7 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, err = m_sdk->VmbFeatureEnumRangeQuery_t( m_handle, feature->name, values.data(), MM::MaxStrLength, &valuesNum); if (VmbErrorSuccess != err) { - return err; + break; } for (size_t i = 0; i < valuesNum; i++) { @@ -807,7 +866,7 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, err = m_sdk->VmbFeatureIntRangeQuery_t(m_handle, feature->name, &min, &max); if (VmbErrorSuccess != err || min == max) { - return err; + break; } err = SetPropertyLimits(propertyName, static_cast(min), @@ -822,6 +881,11 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, break; } + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while setting allowed values for feature " + + std::string(feature->name)); + } + return err; } @@ -836,15 +900,19 @@ int AlliedVisionCamera::SnapImage() { frame.bufferSize = m_bufferSize; VmbError_t err = - m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)) || - m_sdk->VmbCaptureStart_t(m_handle) || - m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr) || - m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart) || + m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)) | + m_sdk->VmbCaptureStart_t(m_handle) | + m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr) | + m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart) | m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); m_isAcquisitionRunning = true; (void)StopSequenceAcquisition(); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while snapping image!"); + } + return err; } @@ -859,8 +927,9 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, return DEVICE_CAMERA_BUSY_ACQUIRING; } - int err = GetCoreCallback()->PrepareForAcq(this) || resizeImageBuffer(); + int err = GetCoreCallback()->PrepareForAcq(this) | resizeImageBuffer(); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while preparing for acquisition!"); return err; } @@ -882,18 +951,21 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, err = m_sdk->VmbFrameAnnounce_t(m_handle, &(m_frames[i]), - sizeof(VmbFrame_t)) || + sizeof(VmbFrame_t)) | m_sdk->VmbCaptureFrameQueue_t(m_handle, &(m_frames[i]), frameCallback); if (err != VmbErrorSuccess) { + LOG_ERROR(err, + "Error during frame praparation for continous acquisition!"); return err; } } - err = m_sdk->VmbCaptureStart_t(m_handle) || + err = m_sdk->VmbCaptureStart_t(m_handle) | m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); m_isAcquisitionRunning = true; if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error during start acquisition!"); return err; } @@ -909,14 +981,16 @@ int AlliedVisionCamera::StopSequenceAcquisition() { auto err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStop); m_isAcquisitionRunning = false; if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error during stopping acquisition command!"); return err; } } - auto err = m_sdk->VmbCaptureEnd_t(m_handle) || - m_sdk->VmbCaptureQueueFlush_t(m_handle) || + auto err = m_sdk->VmbCaptureEnd_t(m_handle) | + m_sdk->VmbCaptureQueueFlush_t(m_handle) | m_sdk->VmbFrameRevokeAll_t(m_handle); if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error in acquisition stop!"); return err; } diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index bea9b8e2e..5668286d9 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -16,13 +16,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =============================================================================*/ -#ifndef AlliedVisionCamera_H -#define AlliedVisionCamera_H +#ifndef ALLIEDVISIONCAMERA_H +#define ALLIEDVISIONCAMERA_H #include #include #include +#include "AlliedVisionDeviceBase.h" #include "DeviceBase.h" #include "Loader/LibLoader.h" @@ -56,7 +57,9 @@ static constexpr const char* g_AcqusitionStatus = "AcqusitionStatus"; /** * @brief Main Allied Vision Camera class */ -class AlliedVisionCamera : public CCameraBase { +class AlliedVisionCamera + : public AlliedVisionDeviceBase, + AlliedVisionCamera> { /////////////////////////////////////////////////////////////////////////////// // PUBLIC /////////////////////////////////////////////////////////////////////////////// diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj index 7174e830c..7106ac0ca 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj @@ -20,6 +20,7 @@ + @@ -32,6 +33,7 @@ + diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters index 15ee3a694..bdb569ec1 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters @@ -45,6 +45,9 @@ Header Files + + Header Files + @@ -56,5 +59,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.cpp new file mode 100644 index 000000000..64e692022 --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.cpp @@ -0,0 +1,3 @@ +#include "AlliedVisionDeviceBase.h" + + diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h new file mode 100644 index 000000000..fa7d6c30a --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h @@ -0,0 +1,97 @@ +/*============================================================================= + Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ +#ifndef ALLIEDVISIONDEVICEBASE_H +#define ALLIEDVISIONDEVICEBASE_H + +#include "DeviceBase.h" +#include "Loader/LibLoader.h" + +#define LOG_ERROR(err, message) logError(err, message, __FUNCTION__, __LINE__) + +/** + * @brief Base class for Allied Vision devices + */ +template +class AlliedVisionDeviceBase : public CDeviceBase { + /////////////////////////////////////////////////////////////////////////////// + // PUBLIC + /////////////////////////////////////////////////////////////////////////////// + public: + /** + * @brief Constructor + */ + AlliedVisionDeviceBase() { + CDeviceBase::InitializeDefaultErrorMessages(); + setApiErrorMessages(); + }; + + /** + * @brief Destructor + */ + virtual ~AlliedVisionDeviceBase() = default; + + void logError(int error, std::string message, std::string function = "", + int line = 0) const { + std::string prefix = "[" + function + "():" + std::to_string(line) + "] "; + CDeviceBase::LogMessage(prefix + message); + CDeviceBase::LogMessageCode(error); + } + + /////////////////////////////////////////////////////////////////////////////// + // PRIVATE + /////////////////////////////////////////////////////////////////////////////// + private: + /** + * @brief Setup error messages for Vimba API + */ + void setApiErrorMessages() { + CDeviceBase::SetErrorText(VmbErrorApiNotStarted, + "Vimba X API not started"); + CDeviceBase::SetErrorText(VmbErrorNotFound, "Device cannot be found"); + CDeviceBase::SetErrorText(VmbErrorDeviceNotOpen, + "Device cannot be opened"); + CDeviceBase::SetErrorText(VmbErrorBadParameter, + "Invalid parameter passed to the function"); + CDeviceBase::SetErrorText(VmbErrorNotImplemented, + "Feature not implemented"); + CDeviceBase::SetErrorText(VmbErrorNotSupported, + "Feature not supported"); + CDeviceBase::SetErrorText(VmbErrorUnknown, "Unknown error"); + CDeviceBase::SetErrorText( + VmbErrorInvalidValue, + "The value is not valid: either out of bounds or not an " + "increment of the minimum"); + CDeviceBase::SetErrorText(VmbErrorBadHandle, + "Given device handle is not valid"); + CDeviceBase::SetErrorText( + VmbErrorInvalidAccess, + "Operation is invalid with the current access mode"); + CDeviceBase::SetErrorText(VmbErrorTimeout, "Timeout occured"); + CDeviceBase::SetErrorText(VmbErrorNotAvailable, + "Something is not available"); + CDeviceBase::SetErrorText(VmbErrorNotInitialized, + "Something is not initialized"); + CDeviceBase::SetErrorText(VmbErrorAlready, + "The operation has been already done"); + CDeviceBase::SetErrorText(VmbErrorFeaturesUnavailable, + "Feature is currently unavailable"); + } +}; + +#endif diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp index 729962443..74b3bee5e 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp @@ -2,10 +2,7 @@ #include "AlliedVisionCamera.h" -AlliedVisionHub::AlliedVisionHub() : m_sdk(std::make_shared()) { - InitializeDefaultErrorMessages(); - setApiErrorMessages(); -} +AlliedVisionHub::AlliedVisionHub() : m_sdk(std::make_shared()) {} int AlliedVisionHub::DetectInstalledDevices() { LogMessage("Detecting installed cameras..."); @@ -28,6 +25,8 @@ int AlliedVisionHub::DetectInstalledDevices() { } delete[] camInfo; + } else { + LOG_ERROR(err, "Cannot get installed devices!"); } return err; @@ -38,7 +37,7 @@ int AlliedVisionHub::Initialize() { if (m_sdk->isInitialized()) { return DEVICE_OK; } else { - LogMessage("SDK not initialized!"); + LOG_ERROR(VmbErrorApiNotStarted, "SDK not initialized!"); return VmbErrorApiNotStarted; } } @@ -55,24 +54,3 @@ void AlliedVisionHub::GetName(char* name) const { bool AlliedVisionHub::Busy() { return false; } std::shared_ptr& AlliedVisionHub::getSDK() { return m_sdk; } - -void AlliedVisionHub::setApiErrorMessages() { - SetErrorText(VmbErrorApiNotStarted, "Vimba X API not started"); - SetErrorText(VmbErrorNotFound, "Device cannot be found"); - SetErrorText(VmbErrorDeviceNotOpen, "Device cannot be opened"); - SetErrorText(VmbErrorBadParameter, - "Invalid parameter passed to the function"); - SetErrorText(VmbErrorNotImplemented, "Feature not implemented"); - SetErrorText(VmbErrorNotSupported, "Feature not supported"); - SetErrorText(VmbErrorUnknown, "Unknown error"); - SetErrorText(VmbErrorInvalidValue, - "The value is not valid: either out of bounds or not an " - "increment of the minimum"); - SetErrorText(VmbErrorBadHandle, "Given device handle is not valid"); - SetErrorText(VmbErrorInvalidAccess, - "Operation is invalid with the current access mode"); - SetErrorText(VmbErrorTimeout, "Timeout occured"); - SetErrorText(VmbErrorNotAvailable, "Something is not available"); - SetErrorText(VmbErrorNotInitialized, "Something is not initialized"); - SetErrorText(VmbErrorAlready, "The operation has been already done"); -} diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h index 09c77aeba..dceb66dc5 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h @@ -16,12 +16,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =============================================================================*/ -#ifndef AlliedVisionHub_H -#define AlliedVisionHub_H +#ifndef ALLIEDVISIONHUB_H +#define ALLIEDVISIONHUB_H +#include "AlliedVisionDeviceBase.h" #include "DeviceBase.h" #include "SDK/Loader/LibLoader.h" - /////////////////////////////////////////////////////////////////////////////// // STATIC VARIABLES /////////////////////////////////////////////////////////////////////////////// @@ -30,7 +30,8 @@ static constexpr const char* g_hubName = "AlliedVisionHub"; /** * @brief Class that represents a HUB of supported devices */ -class AlliedVisionHub : public HubBase { +class AlliedVisionHub + : public AlliedVisionDeviceBase, AlliedVisionHub> { /////////////////////////////////////////////////////////////////////////////// // PUBLIC /////////////////////////////////////////////////////////////////////////////// @@ -51,11 +52,6 @@ class AlliedVisionHub : public HubBase { */ std::shared_ptr& getSDK(); - /** - * @brief Setup error messages for Vimba API - */ - void setApiErrorMessages(); - /////////////////////////////////////////////////////////////////////////////// // uMANAGER API METHODS /////////////////////////////////////////////////////////////////////////////// From f126db07dccd009eab50c82dc6490c039ff52be4 Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Mon, 24 Jul 2023 10:15:48 +0200 Subject: [PATCH 020/141] Property read-only handling and fixes Changes: - Changes to the handling of read-only property - Fixes for errors for snap/live mode in release mode - Added additional error checks Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 203 +++++++++++------- 1 file changed, 124 insertions(+), 79 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 67e0b8a6c..6b7071a5e 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -114,7 +114,6 @@ int AlliedVisionCamera::Shutdown() { LogMessage("Shutting down camera: " + m_cameraName); VmbError_t err = VmbErrorSuccess; if (m_sdk != nullptr && m_sdk->isInitialized()) { - (void)StopSequenceAcquisition(); if (m_handle != nullptr) { err = m_sdk->VmbCameraClose_t(m_handle); } @@ -219,19 +218,19 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( switch (feature->featureDataType) { case VmbFeatureDataInt: { - err = CreateIntegerProperty(propertyName.c_str(), 0, true, + err = CreateIntegerProperty(propertyName.c_str(), 0, false, uManagerCallback); break; } case VmbFeatureDataBool: { - err = CreateStringProperty(propertyName.c_str(), g_False, true, + err = CreateStringProperty(propertyName.c_str(), g_False, false, uManagerCallback); AddAllowedValue(propertyName.c_str(), g_False); AddAllowedValue(propertyName.c_str(), g_True); break; } case VmbFeatureDataCommand: { - err = CreateStringProperty(propertyName.c_str(), g_Command, true, + err = CreateStringProperty(propertyName.c_str(), g_Command, false, uManagerCallback); AddAllowedValue(propertyName.c_str(), g_Command); AddAllowedValue(propertyName.c_str(), g_Execute); @@ -239,12 +238,12 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( } case VmbFeatureDataEnum: case VmbFeatureDataString: { - err = CreateStringProperty(propertyName.c_str(), "", true, + err = CreateStringProperty(propertyName.c_str(), "", false, uManagerCallback); break; } case VmbFeatureDataFloat: { - err = CreateFloatProperty(propertyName.c_str(), 0.0, true, + err = CreateFloatProperty(propertyName.c_str(), 0.0, false, uManagerCallback); break; } @@ -335,44 +334,48 @@ int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, VmbError_t err = VmbErrorSuccess; std::function setOffsetXProperty = [this](long x) { - return SetProperty(g_OffsetX, CDeviceUtils::ConvertToString(x)); + auto err = SetProperty(g_OffsetX, CDeviceUtils::ConvertToString(x)); + if (err) { + LOG_ERROR(err, "Error while ROI Offset X!"); + } + return err; }; std::function setOffsetYProperty = [this](long y) { - return SetProperty(g_OffsetY, CDeviceUtils::ConvertToString(y)); + auto err = SetProperty(g_OffsetY, CDeviceUtils::ConvertToString(y)); + if (err) { + LOG_ERROR(err, "Error while ROI Offset Y!"); + } + return err; }; std::function setWidthProperty = [this](long xSize) { - return SetProperty(g_Width, CDeviceUtils::ConvertToString(xSize)); + auto err = SetProperty(g_Width, CDeviceUtils::ConvertToString(xSize)); + if (err) { + LOG_ERROR(err, "Error while ROI X!"); + } + return err; }; std::function setHeightProperty = [this](long ySize) { - return SetProperty(g_Height, CDeviceUtils::ConvertToString(ySize)); + auto err = SetProperty(g_Height, CDeviceUtils::ConvertToString(ySize)); + if (err) { + LOG_ERROR(err, "Error while ROI Y!"); + } + return err; }; if (xSize > width) { - err = setOffsetXProperty(x) | setWidthProperty(xSize); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while ROI X!"); - return err; - } + err = setOffsetXProperty(x) || setWidthProperty(xSize); } else { - err = setWidthProperty(xSize) | setOffsetXProperty(x); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while ROI X!"); - return err; - } + err = setWidthProperty(xSize) || setOffsetXProperty(x); } if (ySize > height) { - err = setOffsetYProperty(y) | setHeightProperty(ySize); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while ROI Y!"); - return err; - } + err = setOffsetYProperty(y) || setHeightProperty(ySize); } else { - err = setHeightProperty(ySize) | setOffsetYProperty(y); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while ROI Y!"); - return err; - } + err = setHeightProperty(ySize) || setOffsetYProperty(y); + } + + if (err != VmbErrorSuccess) { + return err; } return resizeImageBuffer(); @@ -446,7 +449,6 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Init std::vector featureNames = {}; VmbError_t err = VmbErrorSuccess; - MM::Property* pChildProperty = (MM::Property*)pProp; const auto propertyName = pProp->GetName(); // Check property mapping @@ -456,7 +458,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, for (const auto& featureName : featureNames) { // Get Feature Info and Access Mode VmbFeatureInfo_t featureInfo; - bool rMode{}, wMode{}, readOnly{}, featureAvailable{}; + bool rMode{}, wMode{}, featureAvailable{}; err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), &featureInfo, sizeof(featureInfo)) | m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, @@ -466,7 +468,6 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, return err; } - readOnly = (rMode && !wMode); featureAvailable = rMode || wMode; if (!featureAvailable) { LOG_ERROR(VmbErrorFeaturesUnavailable, @@ -506,40 +507,32 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, } } - // Update property's access mode - pChildProperty->SetReadOnly(readOnly); + err = setAllowedValues(&featureInfo, propertyName.c_str()); - // Update property's limits and allowed values - if (wMode) { - err = setAllowedValues(&featureInfo, propertyName.c_str()); - } break; case MM::ActionType::AfterSet: //!< Update feature from property - if (wMode) { - err = - setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); - if (err == VmbErrorInvalidValue) { - // Update limits first to have latest min and max - err = setAllowedValues(&featureInfo, propertyName.c_str()); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while setting allowed values for feature " + - featureName); - return err; - } + err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); + if (err == VmbErrorInvalidValue) { + // Update limits first to have latest min and max + err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while setting allowed values for feature " + + featureName); + return err; + } - // Adjust value - double min{}, max{}; - err = GetPropertyLowerLimit(propertyName.c_str(), min) | - GetPropertyUpperLimit(propertyName.c_str(), max); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting limits for " + propertyName); - return err; - } - std::string adjustedValue = - adjustValue(featureInfo, min, max, std::stod(propertyValue)); - err = setFeatureValue(&featureInfo, featureName.c_str(), - adjustedValue); + // Adjust value + double min{}, max{}; + err = GetPropertyLowerLimit(propertyName.c_str(), min) | + GetPropertyUpperLimit(propertyName.c_str(), max); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting limits for " + propertyName); + return err; } + std::string adjustedValue = + adjustValue(featureInfo, min, max, std::stod(propertyValue)); + err = + setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); } break; default: @@ -899,14 +892,39 @@ int AlliedVisionCamera::SnapImage() { frame.buffer = m_buffer[0]; frame.bufferSize = m_bufferSize; - VmbError_t err = - m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)) | - m_sdk->VmbCaptureStart_t(m_handle) | - m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr) | - m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart) | - m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); - + VmbError_t err = VmbErrorSuccess; + err = m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } + err = m_sdk->VmbCaptureStart_t(m_handle); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } + err = m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } + err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } m_isAcquisitionRunning = true; + err = m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } + (void)StopSequenceAcquisition(); if (VmbErrorSuccess != err) { @@ -927,12 +945,18 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, return DEVICE_CAMERA_BUSY_ACQUIRING; } - int err = GetCoreCallback()->PrepareForAcq(this) | resizeImageBuffer(); + int err = GetCoreCallback()->PrepareForAcq(this); if (err != VmbErrorSuccess) { LOG_ERROR(err, "Error while preparing for acquisition!"); return err; } + err = resizeImageBuffer(); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error during frame praparation for continous acquisition!"); + return err; + } + for (size_t i = 0; i < MAX_FRAMES; i++) { // Setup frame with buffer m_frames[i].buffer = new uint8_t[m_bufferSize]; @@ -950,8 +974,14 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, }; err = - m_sdk->VmbFrameAnnounce_t(m_handle, &(m_frames[i]), - sizeof(VmbFrame_t)) | + m_sdk->VmbFrameAnnounce_t(m_handle, &(m_frames[i]), sizeof(VmbFrame_t)); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, + "Error during frame praparation for continous acquisition!"); + return err; + } + + err = m_sdk->VmbCaptureFrameQueue_t(m_handle, &(m_frames[i]), frameCallback); if (err != VmbErrorSuccess) { LOG_ERROR(err, @@ -960,8 +990,12 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, } } - err = m_sdk->VmbCaptureStart_t(m_handle) | - m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); + err = m_sdk->VmbCaptureStart_t(m_handle); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error during frame praparation for continous acquisition!"); + return err; + } + err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); m_isAcquisitionRunning = true; if (err != VmbErrorSuccess) { @@ -977,8 +1011,9 @@ int AlliedVisionCamera::StartSequenceAcquisition(double interval_ms) { } int AlliedVisionCamera::StopSequenceAcquisition() { // This method shall never return any error + VmbError_t err = VmbErrorSuccess; if (IsCapturing()) { - auto err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStop); + err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStop); m_isAcquisitionRunning = false; if (err != VmbErrorSuccess) { LOG_ERROR(err, "Error during stopping acquisition command!"); @@ -986,11 +1021,21 @@ int AlliedVisionCamera::StopSequenceAcquisition() { } } - auto err = m_sdk->VmbCaptureEnd_t(m_handle) | - m_sdk->VmbCaptureQueueFlush_t(m_handle) | - m_sdk->VmbFrameRevokeAll_t(m_handle); + err = m_sdk->VmbCaptureEnd_t(m_handle); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error during stop acquisition!"); + return err; + } + + err = m_sdk->VmbCaptureQueueFlush_t(m_handle); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error during stop acquisition!"); + return err; + } + + err = m_sdk->VmbFrameRevokeAll_t(m_handle); if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error in acquisition stop!"); + LOG_ERROR(err, "Error during stop acquisition!"); return err; } From 6922c32b7f15775636e89a11e1ec22e6bbb75ad1 Mon Sep 17 00:00:00 2001 From: "Florian Klostermann [Allied Vision]" Date: Thu, 27 Jul 2023 14:15:34 +0200 Subject: [PATCH 021/141] Pull request #1: Feature/UNISDK-3114 create jenkins jobs for building micro manager avt device adapter Merge in HSW_SDK/mmcoreanddevices from feature/UNISDK-3114-create-jenkins-jobs-for-building-micro-manager-avt-device-adapter to allied-vision-camera-device-adapter Squashed commit of the following: commit 3ef56cc7e2efbdda688d732476055bfdd318a234 Author: Florian Klostermann Date: Thu Jul 27 14:09:14 2023 +0200 Jenkinsfile fix commit f74b0b65cf5fce2277b17433b612915c51d52eaa Author: Florian Klostermann Date: Thu Jul 27 14:01:23 2023 +0200 fixed Windows Jenkinsfile commit bf1304f3b1afb11a19f32bbdaffd5ec0d80e2a59 Author: Florian Klostermann Date: Thu Jul 27 11:58:32 2023 +0200 Added Windows Jenkinsfile --- .../jenkins/Windows/Jenkinsfile | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 DeviceAdapters/AlliedVisionCamera/jenkins/Windows/Jenkinsfile diff --git a/DeviceAdapters/AlliedVisionCamera/jenkins/Windows/Jenkinsfile b/DeviceAdapters/AlliedVisionCamera/jenkins/Windows/Jenkinsfile new file mode 100644 index 000000000..1e904b62e --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/jenkins/Windows/Jenkinsfile @@ -0,0 +1,59 @@ +properties([ + buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '14')), + [$class: 'RebuildSettings', autoRebuild: false, rebuildDisabled: false], + [$class: 'JobLocalConfiguration', changeReasonComment: ''] +]) + +pipeline { + + agent { label 'Host_Software_Windows_Build_Machine2' } + + stages { + + stage("Build") { + steps { + bat label: 'cmake build Release', script: '''CALL %VS2019_VCVARSALL_BAT% amd64 + msbuild micromanager.sln /p:Configuration=Release /p:Platform=x64 -t:AlliedVisionCamera''' + } + } + + stage("Deploy") { + steps { + script { + def buildDir = "build\\Release\\x64" + def deployDir = "MicroManager_AlliedVisionCamera" + + bat label: 'Create Deploy folder', script: """rd /s /q ${deployDir} + mkdir ${deployDir} + copy /Y /V ${buildDir}\\mmgr_dal_AlliedVisionCamera.dll ${deployDir}""" + + withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-user', usernameVariable: 'NEXUS_USER', passwordVariable: 'NEXUS_USER_PW')]) { + bat label: 'Upload to Nexus', script:"""python -m nexus_handler upload --repository hsw_test --group-id avt.hsw.vimba.Win64 --artifact-id MicroManager_AlliedVisionCamera --auto-version --input ${deployDir} --type zip --user %NEXUS_USER% --password "%NEXUS_USER_PW%" """ + } + } + } + } + } + post { + always { + notifyBitbucket(projectKey: "${currentBuild.fullDisplayName}") + } + unstable { + emailext ( + subject: "UNSTABLE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'", + body: """The build for the job ${env.JOB_NAME} > #${env.BUILD_NUMBER} failed: +Check console output at ${env.BUILD_URL}""", + recipientProviders: [[$class: 'DevelopersRecipientProvider']] + ) + } + failure { + emailext ( + subject: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'", + body: """The Job ${env.JOB_NAME} resulted in an unexpected error during build ${env.BUILD_NUMBER}. + +Check console output at ${env.BUILD_URL}""", + recipientProviders: [[$class: 'DevelopersRecipientProvider']] + ) + } + } +} \ No newline at end of file From dafcc7da010596a5249dcb3fb2fa9fedbe64c1a2 Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Mon, 31 Jul 2023 10:36:01 +0200 Subject: [PATCH 022/141] Pixel Format support Changes: - Added helper class for pixel type converting - Added draft pixel type support Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 138 ++++++++++--- .../AlliedVisionCamera/AlliedVisionCamera.h | 194 +++++++++++++++++- .../AlliedVisionCamera.vcxproj | 2 +- .../AlliedVisionCamera/SDK/Loader/Constants.h | 12 +- .../SDK/Loader/LibLoader.cpp | 10 +- .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 9 +- 6 files changed, 323 insertions(+), 42 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 6b7071a5e..abfeaa4a4 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -83,8 +83,12 @@ AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) m_frames{}, m_buffer{}, m_bufferSize{0}, - m_isAcquisitionRunning{false} { + m_payloadSize{0}, + m_isAcquisitionRunning{false}, + m_currentPixelFormat{} { CreateHubIDProperty(); + // Binning property is a Core Property, we will have a dummy one + CreateProperty(MM::g_Keyword_Binning, "1", MM::Integer, false, nullptr); } int AlliedVisionCamera::Initialize() { @@ -154,18 +158,21 @@ VmbError_t AlliedVisionCamera::setupProperties() { } VmbError_t AlliedVisionCamera::resizeImageBuffer() { - VmbError_t err = m_sdk->VmbPayloadSizeGet_t(m_handle, &m_bufferSize); + VmbError_t err = m_sdk->VmbPayloadSizeGet_t(m_handle, &m_payloadSize); if (err != VmbErrorSuccess) { LOG_ERROR(err, "Error while reading payload size"); return err; } + m_bufferSize = GetImageWidth() * GetImageHeight() * + m_currentPixelFormat.getBytesPerPixel(); + for (size_t i = 0; i < MAX_FRAMES; i++) { delete[] m_buffer[i]; m_buffer[i] = new VmbUint8_t[m_bufferSize]; } - return err; + return VmbErrorSuccess; } VmbError_t AlliedVisionCamera::createPropertyFromFeature( @@ -290,15 +297,17 @@ unsigned AlliedVisionCamera::GetImageHeight() const { } unsigned AlliedVisionCamera::GetImageBytesPerPixel() const { - // TODO implement - return 1; + return m_currentPixelFormat.getBytesPerPixel(); } long AlliedVisionCamera::GetImageBufferSize() const { return m_bufferSize; } unsigned AlliedVisionCamera::GetBitDepth() const { - // TODO implement - return 8; + return m_currentPixelFormat.getBitDepth(); +} + +unsigned AlliedVisionCamera::GetNumberOfComponents() const { + return m_currentPixelFormat.getNumberOfComponents(); } int AlliedVisionCamera::GetBinning() const { @@ -438,12 +447,6 @@ void AlliedVisionCamera::GetName(char* name) const { bool AlliedVisionCamera::IsCapturing() { return m_isAcquisitionRunning; } -int AlliedVisionCamera::OnPixelType(MM::PropertyBase* pProp, - MM::ActionType eAct) { - // TODO implement - return 0; -} - int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, MM::ActionType eAct) { // Init @@ -497,6 +500,9 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, pProp->Set(featureValue.c_str()); err = GetCoreCallback()->OnPropertyChanged( this, propertyName.c_str(), featureValue.c_str()); + if (propertyName == MM::g_Keyword_PixelType) { + handlePixelFormatChange(featureValue); + } if (VmbErrorSuccess != err) { LOG_ERROR(err, @@ -534,6 +540,10 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, err = setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); } + + if (propertyName == MM::g_Keyword_PixelType) { + handlePixelFormatChange(propertyValue); + } break; default: // nothing @@ -548,6 +558,11 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, return err; } +void AlliedVisionCamera::handlePixelFormatChange(const std::string& pixelType) { + m_currentPixelFormat.setPixelType(pixelType); + resizeImageBuffer(); +} + VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, const char* featureName, std::string& value) const { @@ -889,8 +904,15 @@ int AlliedVisionCamera::SnapImage() { resizeImageBuffer(); VmbFrame_t frame; - frame.buffer = m_buffer[0]; - frame.bufferSize = m_bufferSize; + std::shared_ptr buffer{nullptr}; + if (m_currentPixelFormat.getNumberOfComponents() > 1) { + buffer = std::shared_ptr(new VmbUint8_t[m_payloadSize]); + frame.buffer = buffer.get(); + frame.bufferSize = m_payloadSize; + } else { + frame.buffer = m_buffer[0]; + frame.bufferSize = m_bufferSize; + } VmbError_t err = VmbErrorSuccess; err = m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); @@ -925,6 +947,15 @@ int AlliedVisionCamera::SnapImage() { return err; } + if (m_currentPixelFormat.getNumberOfComponents() > 1) { + err = transformImage(&frame, 0); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while snapping image - cannot transform image!"); + (void)StopSequenceAcquisition(); + return err; + } + } + (void)StopSequenceAcquisition(); if (VmbErrorSuccess != err) { @@ -959,8 +990,13 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, for (size_t i = 0; i < MAX_FRAMES; i++) { // Setup frame with buffer - m_frames[i].buffer = new uint8_t[m_bufferSize]; - m_frames[i].bufferSize = m_bufferSize; + if (m_currentPixelFormat.getNumberOfComponents() > 1) { + m_frames[i].buffer = new VmbUint8_t[m_payloadSize]; + m_frames[i].bufferSize = m_payloadSize; + } else { + m_frames[i].buffer = m_buffer[i]; + m_frames[i].bufferSize = m_bufferSize; + } m_frames[i].context[0] = this; //(i); //buffer; + src.Size = sizeof(src); + + dest.Data = m_buffer[frameIndex]; + dest.Size = sizeof(dest); + + err = m_sdk->VmbSetImageInfoFromPixelFormat_t( + frame->pixelFormat, frame->width, frame->height, &src); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Cannot set image info from pixel format!"); + return err; + } + + err = m_sdk->VmbSetImageInfoFromPixelFormat_t( + m_currentPixelFormat.getVmbFormat(), frame->width, frame->height, &dest); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Cannot set image info from pixel format!"); + return err; + } + + err = m_sdk->VmbImageTransform_t(&src, &dest, &info, 0); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Cannot transform image!"); + return err; + } + + return err; +} + void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { if (frame != nullptr && frame->receiveStatus == VmbFrameStatusComplete) { - VmbUint8_t* buffer = reinterpret_cast(frame->imageData); + VmbUint8_t* buffer = nullptr; + size_t frameIndex = reinterpret_cast(frame->context[1]); + VmbError_t err = VmbErrorSuccess; + + if (m_currentPixelFormat.getNumberOfComponents() > 1) { + err = transformImage(frame, frameIndex); + buffer = m_buffer[frameIndex]; + if (err != VmbErrorSuccess) { + // Error logged in transformImage + return; + } + } else { + // For GRAY_SCALE copy as it is without any transform + buffer = reinterpret_cast(frame->buffer); + } // TODO implement metadata Metadata md; md.put("Camera", m_cameraName); - // TODO implement parameters - auto err = GetCoreCallback()->InsertImage(this, buffer, GetImageWidth(), - GetImageHeight(), 1, 1, - md.Serialize().c_str()); + err = GetCoreCallback()->InsertImage( + this, buffer, GetImageWidth(), GetImageHeight(), + m_currentPixelFormat.getBytesPerPixel(), + m_currentPixelFormat.getNumberOfComponents(), md.Serialize().c_str()); if (err == DEVICE_BUFFER_OVERFLOW) { GetCoreCallback()->ClearImageBuffer(this); - // TODO implement parameters - err = GetCoreCallback()->InsertImage(this, buffer, frame->width, - frame->height, 1, 1, - md.Serialize().c_str(), false); + err = GetCoreCallback()->InsertImage( + this, buffer, GetImageWidth(), GetImageHeight(), + m_currentPixelFormat.getBytesPerPixel(), + m_currentPixelFormat.getNumberOfComponents(), md.Serialize().c_str(), + false); } if (IsCapturing()) { diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 5668286d9..f1ff962b0 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -21,6 +21,7 @@ #include #include +#include #include #include "AlliedVisionDeviceBase.h" @@ -54,6 +55,172 @@ static constexpr const char* g_AcquisitionStart = "AcquisitionStart"; static constexpr const char* g_AcquisitionStop = "AcquisitionStop"; static constexpr const char* g_AcqusitionStatus = "AcqusitionStatus"; +/** + * @brief Pixel Format class that contains VMB Pixel Format info and contains + * helper methods to convert these information to the one that uManager + * supports. + * + * [IMPORTANT] uManager supports formats: + * 8bit GRAY [no. component = 1] + * 16bit GRAY [no. component = 1] + * 32bit RGB [no. component = 4] + */ +class PixelFormatConverter { + /////////////////////////////////////////////////////////////////////////////// + // PUBLIC + /////////////////////////////////////////////////////////////////////////////// + public: + /** + * @brief Constructor + */ + PixelFormatConverter() + : m_pixelType{"Mono8"}, + m_components{1}, + m_bitDepth{8}, + m_bytesPerPixel{1}, + m_isMono{true}, + m_vmbFormat{VmbPixelFormatMono8} {} + + /** + * @brief Destructor + */ + virtual ~PixelFormatConverter() = default; + + /** + * @brief Setter for pixel type + * @param[in] type New pixel type (string value from VMB) + */ + void setPixelType(const std::string& type) { + m_pixelType = type; + updateFields(); + } + + /** + * @brief Getter to check if given pixel type is Mono or Color + * @return True if Mono, otherwise false + */ + bool isMono() const { return m_isMono; } + + /** + * @brief Getter for number of components for given pixel type + * uManager supports only 1 or 4 components + * @return Number of components + */ + unsigned getNumberOfComponents() const { return m_components; } + + /** + * @brief Getter for bit depth for given pixel type + * @return Number of bits for one pixel + */ + unsigned getBitDepth() const { return m_bitDepth; } + + /** + * @brief Getter for bytes per pixel + * @return Bytes per pixel + */ + unsigned getBytesPerPixel() const { return m_bytesPerPixel; } + + /** + * @brief Getter of destination VmbPixelFormat that fits into the one, that + * uManager supports. In general uManager supports three pixelFormats: + * 1. Mono8 + * 2. Mono16 + * 3. RGB32 + * These types fits into following VmbPixelTypes: + * 1. VmbPixelFormatMono8 + * 2. VmbPixelFormatMono16 + * 3. VmbPixelFormatBgra8 + * @return Destination VmbPixelFormat + */ + VmbPixelFormatType getVmbFormat() const { return m_vmbFormat; } + + /////////////////////////////////////////////////////////////////////////////// + // PRIVATE + /////////////////////////////////////////////////////////////////////////////// + private: + /** + * @brief Helper method to update info if given format is Mono or Color + */ + void updateMono() { + std::regex re("Mono(\\d+)"); + std::smatch m; + m_isMono = std::regex_search(m_pixelType, m, re); + } + + /** + * @brief Helper method to update number of components for given pixel type + * uManager supports only 1 or 4 components + */ + void updateNumberOfComponents() { m_components = m_isMono ? 1 : 4; } + + /** + * @brief Helper method to update bit depth for given pixel type + */ + void updateBitDepth() { + m_bitDepth = 8; + if (isMono()) { + std::regex re("Mono(\\d+)"); + std::smatch m; + std::regex_search(m_pixelType, m, re); + if (m.size() > 0) { + if (std::atoi(m[1].str().c_str()) == 8) { // TODO check this condition + m_bitDepth = 8; + } else { + m_bitDepth = 16; + } + } else { + // ERROR + } + } else { + m_bitDepth = 32; + } + } + + /** + * @brief Helper method to update bytes per pixel + */ + void updateBytesPerPixel() { m_bytesPerPixel = m_bitDepth / 8; } + + /** + * @brief Helper method to update destination VmbPixelFormatType + */ + void updateVmbFormat() { + switch (m_bytesPerPixel) { + case 1: + m_vmbFormat = VmbPixelFormatMono8; + break; + case 2: + m_vmbFormat = VmbPixelFormatMono16; + break; + case 4: + m_vmbFormat = + VmbPixelFormatBgra8; // TODO check if this is a valid format + break; + default: + break; + } + } + + /** + * @brief Helper method to update all required fields + */ + void updateFields() { + // [IMPORTANT] Keep order of the calls + updateMono(); + updateNumberOfComponents(); + updateBitDepth(); + updateBytesPerPixel(); + updateVmbFormat(); + } + + std::string m_pixelType; //!< Pixel type (in string) - value from VMB + unsigned m_components; //!< Number of components + unsigned m_bitDepth; // m_sdk; // m_frames; // m_buffer; // m_sdk; // m_frames; // + m_buffer; // m_featureToProperty; //!< Map of features name into uManager properties static const std::unordered_multimap diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj index 7106ac0ca..6b1087e6d 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj @@ -110,7 +110,7 @@ true - C:\Program Files\Micro-Manager-2.0 + C:\Program Files\Micro-Manager-2.0\ false diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h index 929d01061..fa73ac5b4 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h @@ -19,9 +19,15 @@ #ifndef CONSTANTS_H #define CONSTANTS_H -static constexpr const char* VIMBA_X_ENV_VAR = "VIMBA_X_HOME"; // #include "VmbC/VmbC.h" +#include "VmbImageTransform/VmbTransform.h" /** * @brief Wrapper for single Windows process @@ -171,12 +172,16 @@ class VimbaXApi { decltype(VmbFeatureFloatIncrementQuery)* VmbFeatureFloatIncrementQuery_t = nullptr; decltype(VmbFeatureCommandIsDone)* VmbFeatureCommandIsDone_t = nullptr; + decltype(VmbSetImageInfoFromPixelFormat)* + VmbSetImageInfoFromPixelFormat_t = nullptr; + decltype(VmbImageTransform)* VmbImageTransform_t = nullptr; /////////////////////////////////////////////////////////////////////////////// // PRIVATE /////////////////////////////////////////////////////////////////////////////// private: - bool m_initialized; // Date: Mon, 31 Jul 2023 17:19:52 +0200 Subject: [PATCH 023/141] Pixel format support Changes: - Fixed some memory leaks - Refactored usage of buffer and its clearing - Refactored image transformation - Refactored buffer preparation and resizing Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 79 +++++++------------ .../AlliedVisionCamera/AlliedVisionCamera.h | 19 +++-- 2 files changed, 41 insertions(+), 57 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index abfeaa4a4..028bf0c2f 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -107,10 +107,8 @@ int AlliedVisionCamera::Initialize() { return err; } - // Init properties and buffer - // TODO handle error - setupProperties(); - + // Ignore errors from setting up properties + (void)setupProperties(); return resizeImageBuffer(); } @@ -164,8 +162,9 @@ VmbError_t AlliedVisionCamera::resizeImageBuffer() { return err; } - m_bufferSize = GetImageWidth() * GetImageHeight() * - m_currentPixelFormat.getBytesPerPixel(); + m_bufferSize = std::max(GetImageWidth() * GetImageHeight() * + m_currentPixelFormat.getBytesPerPixel(), + m_payloadSize); for (size_t i = 0; i < MAX_FRAMES; i++) { delete[] m_buffer[i]; @@ -560,7 +559,6 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, void AlliedVisionCamera::handlePixelFormatChange(const std::string& pixelType) { m_currentPixelFormat.setPixelType(pixelType); - resizeImageBuffer(); } VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, @@ -904,15 +902,8 @@ int AlliedVisionCamera::SnapImage() { resizeImageBuffer(); VmbFrame_t frame; - std::shared_ptr buffer{nullptr}; - if (m_currentPixelFormat.getNumberOfComponents() > 1) { - buffer = std::shared_ptr(new VmbUint8_t[m_payloadSize]); - frame.buffer = buffer.get(); - frame.bufferSize = m_payloadSize; - } else { - frame.buffer = m_buffer[0]; - frame.bufferSize = m_bufferSize; - } + frame.buffer = m_buffer[0]; + frame.bufferSize = m_payloadSize; VmbError_t err = VmbErrorSuccess; err = m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); @@ -947,21 +938,14 @@ int AlliedVisionCamera::SnapImage() { return err; } - if (m_currentPixelFormat.getNumberOfComponents() > 1) { - err = transformImage(&frame, 0); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while snapping image - cannot transform image!"); - (void)StopSequenceAcquisition(); - return err; - } + err = transformImage(&frame); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while snapping image - cannot transform image!"); + (void)StopSequenceAcquisition(); + return err; } (void)StopSequenceAcquisition(); - - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while snapping image!"); - } - return err; } @@ -989,14 +973,8 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, } for (size_t i = 0; i < MAX_FRAMES; i++) { - // Setup frame with buffer - if (m_currentPixelFormat.getNumberOfComponents() > 1) { - m_frames[i].buffer = new VmbUint8_t[m_payloadSize]; - m_frames[i].bufferSize = m_payloadSize; - } else { - m_frames[i].buffer = m_buffer[i]; - m_frames[i].bufferSize = m_bufferSize; - } + m_frames[i].buffer = m_buffer[i]; + m_frames[i].bufferSize = m_payloadSize; m_frames[i].context[0] = this; //(i); //VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); m_isAcquisitionRunning = true; @@ -1078,16 +1057,18 @@ int AlliedVisionCamera::StopSequenceAcquisition() { return UpdateStatus(); } -VmbError_t AlliedVisionCamera::transformImage(VmbFrame_t* frame, - size_t frameIndex) { +VmbError_t AlliedVisionCamera::transformImage(VmbFrame_t* frame) { VmbError_t err = VmbErrorSuccess; VmbImage src{}, dest{}; VmbTransformInfo info{}; + std::shared_ptr tempBuff = + std::shared_ptr(new VmbUint8_t[m_bufferSize]); + auto srcBuff = reinterpret_cast(frame->buffer); - src.Data = frame->buffer; + src.Data = srcBuff; src.Size = sizeof(src); - dest.Data = m_buffer[frameIndex]; + dest.Data = tempBuff.get(); dest.Size = sizeof(dest); err = m_sdk->VmbSetImageInfoFromPixelFormat_t( @@ -1110,31 +1091,25 @@ VmbError_t AlliedVisionCamera::transformImage(VmbFrame_t* frame, return err; } + memcpy(srcBuff, tempBuff.get(), m_bufferSize); return err; } void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { if (frame != nullptr && frame->receiveStatus == VmbFrameStatusComplete) { - VmbUint8_t* buffer = nullptr; - size_t frameIndex = reinterpret_cast(frame->context[1]); VmbError_t err = VmbErrorSuccess; - if (m_currentPixelFormat.getNumberOfComponents() > 1) { - err = transformImage(frame, frameIndex); - buffer = m_buffer[frameIndex]; - if (err != VmbErrorSuccess) { - // Error logged in transformImage - return; - } - } else { - // For GRAY_SCALE copy as it is without any transform - buffer = reinterpret_cast(frame->buffer); + err = transformImage(frame); + if (err != VmbErrorSuccess) { + // Error logged in transformImage + return; } // TODO implement metadata Metadata md; md.put("Camera", m_cameraName); + VmbUint8_t* buffer = reinterpret_cast(frame->buffer); err = GetCoreCallback()->InsertImage( this, buffer, GetImageWidth(), GetImageHeight(), m_currentPixelFormat.getBytesPerPixel(), diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index f1ff962b0..d8eb56439 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -163,10 +163,12 @@ class PixelFormatConverter { std::smatch m; std::regex_search(m_pixelType, m, re); if (m.size() > 0) { - if (std::atoi(m[1].str().c_str()) == 8) { // TODO check this condition - m_bitDepth = 8; - } else { + if (std::atoi(m[1].str().c_str()) == 16) { + // We do transformation to Mono16 only for Mono16, otherwise + // it will always be Mono8 m_bitDepth = 16; + } else { + m_bitDepth = 8; } } else { // ERROR @@ -287,7 +289,7 @@ class AlliedVisionCamera /** * @brief Helper method to handle change of pixel type * @param[in] pixelType New pixel type (as string) - */ + */ void handlePixelFormatChange(const std::string& pixelType); /** @@ -403,7 +405,14 @@ class AlliedVisionCamera std::string adjustValue(VmbFeatureInfo_t& featureInfo, double min, double max, double propertyValue) const; - VmbError_t transformImage(VmbFrame_t* frame, size_t frameIndex); + /** + * @brief Internal method to transform image to the destination format that + * uManager supports (see \ref PixelFormatConverter) and replaces input frame + * with output (transformed) frame + * @param[in] frame Frame with image to transform from and into + * @return Error in case of failure + */ + VmbError_t transformImage(VmbFrame_t* frame); /////////////////////////////////////////////////////////////////////////////// // MEMBERS From f3d288b88c72d7a6fac3170ac554e3e0098465ae Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Mon, 7 Aug 2023 15:11:58 +0200 Subject: [PATCH 024/141] Linux support changes Changes: - Base for Linux support Author(s): - Maciej Scecelek --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 2 -- .../AlliedVisionDeviceBase.h | 2 +- .../SDK/Loader/LibLoader.cpp | 10 +++---- .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 26 +++++++++---------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 028bf0c2f..05e827c84 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -20,8 +20,6 @@ #include "AlliedVisionCamera.h" -#include - #include #include #include diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h index fa7d6c30a..deb6c17bf 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h @@ -20,7 +20,7 @@ #define ALLIEDVISIONDEVICEBASE_H #include "DeviceBase.h" -#include "Loader/LibLoader.h" +#include "SDK/Loader/LibLoader.h" #define LOG_ERROR(err, message) logError(err, message, __FUNCTION__, __LINE__) diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp index 9e85a5f2a..f600b4638 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp @@ -121,7 +121,7 @@ LibLoader::LibLoader(const char* lib, const char* libPath) LibLoader::~LibLoader() { if (m_loaded) { - FreeModule(m_module); + FreeModule(static_cast(m_module)); m_module = nullptr; m_loaded = false; m_libName = nullptr; @@ -130,12 +130,12 @@ LibLoader::~LibLoader() { bool LibLoader::isLoaded() const { return m_loaded; } -ProcWrapper LibLoader::resolveFunction(const char* functionName, +SymbolWrapper LibLoader::resolveFunction(const char* functionName, bool& allResolved) const { - FARPROC funPtr = nullptr; + void* funPtr = nullptr; if (allResolved) { - funPtr = GetProcAddress((HMODULE)m_module, functionName); + funPtr = GetProcAddress(static_cast(m_module), functionName); allResolved = allResolved && (funPtr != nullptr); } - return ProcWrapper(funPtr); + return SymbolWrapper(funPtr); } \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h index 583f6b56e..b9221a025 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h @@ -29,16 +29,16 @@ /** * @brief Wrapper for single Windows process */ -class ProcWrapper { +class SymbolWrapper { /////////////////////////////////////////////////////////////////////////////// // PUBLIC /////////////////////////////////////////////////////////////////////////////// public: /** * @brief Constructor of wrapper - * @param[in] funPtr Pointer to the process + * @param[in] funPtr Pointer to the resolved symbol */ - explicit ProcWrapper(FARPROC funPtr) : m_funPtr(funPtr) {} + explicit SymbolWrapper(void* funPtr) : m_funPtr(funPtr) {} /** * @brief Operator () overload to call function @@ -51,7 +51,7 @@ class ProcWrapper { // PRIVATE /////////////////////////////////////////////////////////////////////////////// private: - FARPROC m_funPtr; // Date: Mon, 7 Aug 2023 15:11:58 +0200 Subject: [PATCH 025/141] Linux support changes Changes: - Base for Linux support Author(s): - Maciej Scecelek --- DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h | 4 +++- DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp | 6 ++++-- DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h | 2 -- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h index fa73ac5b4..95f723607 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h @@ -21,8 +21,10 @@ static constexpr const char* VIMBA_X_ENV_VAR = "VIMBA_X_HOME"; // + #include "Constants.h" VimbaXApi::VimbaXApi() @@ -131,11 +133,11 @@ LibLoader::~LibLoader() { bool LibLoader::isLoaded() const { return m_loaded; } SymbolWrapper LibLoader::resolveFunction(const char* functionName, - bool& allResolved) const { + bool& allResolved) const { void* funPtr = nullptr; if (allResolved) { funPtr = GetProcAddress(static_cast(m_module), functionName); allResolved = allResolved && (funPtr != nullptr); } return SymbolWrapper(funPtr); -} \ No newline at end of file +} diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h index b9221a025..7307eedeb 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h @@ -19,8 +19,6 @@ #ifndef LIBLOADER_H #define LIBLOADER_H -#include - #include #include "VmbC/VmbC.h" From e9637bf233dccc9cf8a860e81f2a3f5426b9bc2e Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Mon, 7 Aug 2023 15:09:00 +0200 Subject: [PATCH 026/141] Linux support Changes: - Added changes required for linux build Authros(s): - Maciej Scecelek --- DeviceAdapters/AlliedVisionCamera/Makefile.am | 8 ++++++++ DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h | 7 +++++++ DeviceAdapters/Makefile.am | 1 + DeviceAdapters/configure.ac | 1 + 4 files changed, 17 insertions(+) create mode 100644 DeviceAdapters/AlliedVisionCamera/Makefile.am diff --git a/DeviceAdapters/AlliedVisionCamera/Makefile.am b/DeviceAdapters/AlliedVisionCamera/Makefile.am new file mode 100644 index 000000000..f2229117a --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/Makefile.am @@ -0,0 +1,8 @@ + +AM_CXXFLAGS = $(MMDEVAPI_CXXFLAGS) +deviceadapter_LTLIBRARIES = libmmgr_dal_AlliedVisionCamera.la +libmmgr_dal_AlliedVisionCamera_la_SOURCES = AlliedVisionHub.h AlliedVisionHub.cpp AlliedVisionDeviceBase.h AlliedVisionDeviceBase.cpp AlliedVisionCamera.h AlliedVisionCamera.cpp SDK/Loader/Constants.h SDK/Loader/LibLoader.h SDK/Loader/LibLoader.cpp SDK/VmbC/VmbC.h SDK/VmbC/VmbCommonTypes.h SDK/VmbC/VmbConstants.h SDK/Vmbc/VmbCTypeDefinitions.h SDK/VmbImageTransform/VmbTransform.h SDK/VmbImageTransform/VmbTransformTypes.h +libmmgr_dal_AlliedVisionCamera_la_LIBADD = $(MMDEVAPI_LIBADD) +libmmgr_dal_AlliedVisionCamera_la_LDFLAGS = $(MMDEVAPI_LDFLAGS) + +EXTRA_DIST = AlliedVisionCamera.vcproj AlliedVisionCamera.vcproj.filters AlliedVisionCamera.vcproj.user diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h index 7307eedeb..c8c2a74bd 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h @@ -19,6 +19,13 @@ #ifndef LIBLOADER_H #define LIBLOADER_H +#ifdef __linux__ + #include +#elif _WIN32 + #include +#else + +#endif #include #include "VmbC/VmbC.h" diff --git a/DeviceAdapters/Makefile.am b/DeviceAdapters/Makefile.am index 1fa43090c..225421f0f 100644 --- a/DeviceAdapters/Makefile.am +++ b/DeviceAdapters/Makefile.am @@ -91,6 +91,7 @@ SUBDIRS = \ $(USBMANAGER) \ $(V4L) \ AAAOTF \ + AlliedVisionCamera \ AOTF \ ASIFW1000 \ ASIStage \ diff --git a/DeviceAdapters/configure.ac b/DeviceAdapters/configure.ac index cac90eae8..49596a48f 100644 --- a/DeviceAdapters/configure.ac +++ b/DeviceAdapters/configure.ac @@ -463,6 +463,7 @@ m4_define([device_adapter_dirs], [m4_strip([ ASITiger ASIWPTR Aladdin + AlliedVisionCamera Andor AndorLaserCombiner AndorSDK3 From 1b86a418a54d6ef5157d8192027d885180868f7c Mon Sep 17 00:00:00 2001 From: Maciej Scecelek Date: Wed, 9 Aug 2023 09:40:40 +0200 Subject: [PATCH 027/141] Linux support Changes: - Linux support changes Author(s): - Maciej Scecelek --- .../AlliedVisionDeviceBase.h | 6 +-- .../AlliedVisionCamera/AlliedVisionHub.cpp | 8 +-- .../AlliedVisionCamera/AlliedVisionHub.h | 16 +++--- DeviceAdapters/AlliedVisionCamera/Makefile.am | 3 +- .../AlliedVisionCamera/SDK/Loader/Constants.h | 25 ++++++--- .../SDK/Loader/LibLoader.cpp | 54 ++++++++++++++++--- .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 10 +--- 7 files changed, 84 insertions(+), 38 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h index deb6c17bf..825d65118 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h @@ -20,7 +20,7 @@ #define ALLIEDVISIONDEVICEBASE_H #include "DeviceBase.h" -#include "SDK/Loader/LibLoader.h" +#include "Loader/LibLoader.h" #define LOG_ERROR(err, message) logError(err, message, __FUNCTION__, __LINE__) @@ -32,7 +32,7 @@ class AlliedVisionDeviceBase : public CDeviceBase { /////////////////////////////////////////////////////////////////////////////// // PUBLIC /////////////////////////////////////////////////////////////////////////////// - public: +public: /** * @brief Constructor */ @@ -56,7 +56,7 @@ class AlliedVisionDeviceBase : public CDeviceBase { /////////////////////////////////////////////////////////////////////////////// // PRIVATE /////////////////////////////////////////////////////////////////////////////// - private: +private: /** * @brief Setup error messages for Vimba API */ diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp index 74b3bee5e..9d5a11173 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp @@ -10,7 +10,7 @@ int AlliedVisionHub::DetectInstalledDevices() { // Get the number of connected cameras first VmbError_t err = m_sdk->VmbCamerasList_t(nullptr, 0, &camNum, 0); if (VmbErrorSuccess == err) { - VmbCameraInfo_t* camInfo = new VmbCameraInfo_t[camNum]; + VmbCameraInfo_t *camInfo = new VmbCameraInfo_t[camNum]; // Get the cameras err = m_sdk->VmbCamerasList_t(camInfo, camNum, &camNum, sizeof *camInfo); @@ -18,7 +18,7 @@ int AlliedVisionHub::DetectInstalledDevices() { if (err == VmbErrorSuccess) { for (VmbUint32_t i = 0; i < camNum; ++i) { if (camInfo[i].permittedAccess & VmbAccessModeFull) { - MM::Device* pDev = new AlliedVisionCamera(camInfo[i].cameraIdString); + MM::Device *pDev = new AlliedVisionCamera(camInfo[i].cameraIdString); AddInstalledDevice(pDev); } } @@ -47,10 +47,10 @@ int AlliedVisionHub::Shutdown() { return DEVICE_OK; } -void AlliedVisionHub::GetName(char* name) const { +void AlliedVisionHub::GetName(char *name) const { CDeviceUtils::CopyLimitedString(name, g_hubName); } bool AlliedVisionHub::Busy() { return false; } -std::shared_ptr& AlliedVisionHub::getSDK() { return m_sdk; } +std::shared_ptr &AlliedVisionHub::getSDK() { return m_sdk; } diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h index dceb66dc5..00e50b55a 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h @@ -21,11 +21,13 @@ #include "AlliedVisionDeviceBase.h" #include "DeviceBase.h" -#include "SDK/Loader/LibLoader.h" +#include "Loader/LibLoader.h" + +#include /////////////////////////////////////////////////////////////////////////////// // STATIC VARIABLES /////////////////////////////////////////////////////////////////////////////// -static constexpr const char* g_hubName = "AlliedVisionHub"; +static constexpr const char *g_hubName = "AlliedVisionHub"; /** * @brief Class that represents a HUB of supported devices @@ -35,7 +37,7 @@ class AlliedVisionHub /////////////////////////////////////////////////////////////////////////////// // PUBLIC /////////////////////////////////////////////////////////////////////////////// - public: +public: /** * @brief Contructor of a HUB */ @@ -50,7 +52,7 @@ class AlliedVisionHub * @brief SDK getter * @return Pointer to SDK */ - std::shared_ptr& getSDK(); + std::shared_ptr &getSDK(); /////////////////////////////////////////////////////////////////////////////// // uMANAGER API METHODS @@ -58,14 +60,14 @@ class AlliedVisionHub int DetectInstalledDevices() override; int Initialize() override; int Shutdown() override; - void GetName(char* name) const override; + void GetName(char *name) const override; bool Busy() override; /////////////////////////////////////////////////////////////////////////////// // PRIVATE /////////////////////////////////////////////////////////////////////////////// - private: - std::shared_ptr m_sdk; // m_sdk; // +#elif _WIN32 #include +#else +// nothing +#endif #include "Constants.h" VimbaXApi::VimbaXApi() - : m_sdk(VIMBA_X_LIB_NAME, VIMBA_X_LIB_DIR.c_str()), - m_initialized(false), + : m_sdk(VIMBA_X_LIB_NAME, VIMBA_X_LIB_DIR.c_str()), m_initialized(false), m_imageTransform(VIMBA_X_IMAGE_TRANSFORM_NAME, VIMBA_X_LIB_DIR.c_str()) { if (m_sdk.isLoaded() && m_imageTransform.isLoaded()) { bool allResolved = true; @@ -112,7 +117,39 @@ VimbaXApi::~VimbaXApi() { } } -LibLoader::LibLoader(const char* lib, const char* libPath) +bool LibLoader::isLoaded() const { return m_loaded; } +#ifdef __linux__ +LibLoader::LibLoader(const char *lib, const char *libPath) + : m_libName(lib), m_libPath(libPath), m_module(nullptr), m_loaded(false) { + std::string path = std::string(m_libPath) + std::string(m_libName); + dlerror(); + m_module = dlopen(path.c_str(), RTLD_NOW); + if (m_module != nullptr) { + m_loaded = true; + } +} + +LibLoader::~LibLoader() { + if (m_loaded) { + dlclose(m_module); + m_module = nullptr; + m_loaded = false; + m_libName = nullptr; + } +} + +SymbolWrapper LibLoader::resolveFunction(const char *functionName, + bool &allResolved) const { + dlerror(); + void *funPtr = nullptr; + if (allResolved) { + funPtr = dlsym(m_module, functionName); + allResolved = allResolved && (funPtr != nullptr); + } + return SymbolWrapper(funPtr); +} +#elif _WIN32 +LibLoader::LibLoader(const char *lib, const char *libPath) : m_libName(lib), m_libPath(libPath), m_module(nullptr), m_loaded(false) { SetDllDirectoryA(m_libPath); m_module = LoadLibraryA(m_libName); @@ -130,14 +167,15 @@ LibLoader::~LibLoader() { } } -bool LibLoader::isLoaded() const { return m_loaded; } - -SymbolWrapper LibLoader::resolveFunction(const char* functionName, - bool& allResolved) const { - void* funPtr = nullptr; +SymbolWrapper LibLoader::resolveFunction(const char *functionName, + bool &allResolved) const { + void *funPtr = nullptr; if (allResolved) { funPtr = GetProcAddress(static_cast(m_module), functionName); allResolved = allResolved && (funPtr != nullptr); } return SymbolWrapper(funPtr); } +#else +// nothing +#endif diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h index c8c2a74bd..a877e0903 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h @@ -19,14 +19,8 @@ #ifndef LIBLOADER_H #define LIBLOADER_H -#ifdef __linux__ - #include -#elif _WIN32 - #include -#else - -#endif #include +#include #include "VmbC/VmbC.h" #include "VmbImageTransform/VmbTransform.h" @@ -48,7 +42,7 @@ class SymbolWrapper { /** * @brief Operator () overload to call function */ - template >> + template ::value>> operator T*() const { return reinterpret_cast(m_funPtr); } From ca4147422da1068644900536a0e289254fa12c2f Mon Sep 17 00:00:00 2001 From: Florian Klostermann Date: Fri, 18 Aug 2023 16:55:26 +0200 Subject: [PATCH 028/141] Deleted Jenkins folder since Jenkinsfile have been moved to the "micro-manager" repo --- .../jenkins/Windows/Jenkinsfile | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 DeviceAdapters/AlliedVisionCamera/jenkins/Windows/Jenkinsfile diff --git a/DeviceAdapters/AlliedVisionCamera/jenkins/Windows/Jenkinsfile b/DeviceAdapters/AlliedVisionCamera/jenkins/Windows/Jenkinsfile deleted file mode 100644 index 1e904b62e..000000000 --- a/DeviceAdapters/AlliedVisionCamera/jenkins/Windows/Jenkinsfile +++ /dev/null @@ -1,59 +0,0 @@ -properties([ - buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '14')), - [$class: 'RebuildSettings', autoRebuild: false, rebuildDisabled: false], - [$class: 'JobLocalConfiguration', changeReasonComment: ''] -]) - -pipeline { - - agent { label 'Host_Software_Windows_Build_Machine2' } - - stages { - - stage("Build") { - steps { - bat label: 'cmake build Release', script: '''CALL %VS2019_VCVARSALL_BAT% amd64 - msbuild micromanager.sln /p:Configuration=Release /p:Platform=x64 -t:AlliedVisionCamera''' - } - } - - stage("Deploy") { - steps { - script { - def buildDir = "build\\Release\\x64" - def deployDir = "MicroManager_AlliedVisionCamera" - - bat label: 'Create Deploy folder', script: """rd /s /q ${deployDir} - mkdir ${deployDir} - copy /Y /V ${buildDir}\\mmgr_dal_AlliedVisionCamera.dll ${deployDir}""" - - withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-user', usernameVariable: 'NEXUS_USER', passwordVariable: 'NEXUS_USER_PW')]) { - bat label: 'Upload to Nexus', script:"""python -m nexus_handler upload --repository hsw_test --group-id avt.hsw.vimba.Win64 --artifact-id MicroManager_AlliedVisionCamera --auto-version --input ${deployDir} --type zip --user %NEXUS_USER% --password "%NEXUS_USER_PW%" """ - } - } - } - } - } - post { - always { - notifyBitbucket(projectKey: "${currentBuild.fullDisplayName}") - } - unstable { - emailext ( - subject: "UNSTABLE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'", - body: """The build for the job ${env.JOB_NAME} > #${env.BUILD_NUMBER} failed: -Check console output at ${env.BUILD_URL}""", - recipientProviders: [[$class: 'DevelopersRecipientProvider']] - ) - } - failure { - emailext ( - subject: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'", - body: """The Job ${env.JOB_NAME} resulted in an unexpected error during build ${env.BUILD_NUMBER}. - -Check console output at ${env.BUILD_URL}""", - recipientProviders: [[$class: 'DevelopersRecipientProvider']] - ) - } - } -} \ No newline at end of file From 8329e00502e6d093b09424a1da94c9fad686f19e Mon Sep 17 00:00:00 2001 From: Dennis Langenkamp Date: Tue, 12 Sep 2023 16:09:13 +0200 Subject: [PATCH 029/141] Make micro manager binning core property read-only and set a single allowed value to make the binning field in the main window unchangeable --- DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 05e827c84..fdb423678 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -86,7 +86,8 @@ AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) m_currentPixelFormat{} { CreateHubIDProperty(); // Binning property is a Core Property, we will have a dummy one - CreateProperty(MM::g_Keyword_Binning, "1", MM::Integer, false, nullptr); + CreateProperty(MM::g_Keyword_Binning, "N/A", MM::String, true, nullptr); + AddAllowedValue(MM::g_Keyword_Binning, "N/A"); } int AlliedVisionCamera::Initialize() { @@ -314,7 +315,7 @@ int AlliedVisionCamera::GetBinning() const { int AlliedVisionCamera::SetBinning(int binSize) { // Binning not supported. We support BinningVertical/Horizontal - return DEVICE_OK; + return DEVICE_ERR; } double AlliedVisionCamera::GetExposure() const { From a9fc9c6b5ecbadda9a5752b755d169aee1cf21e4 Mon Sep 17 00:00:00 2001 From: Florian Klostermann Date: Tue, 12 Sep 2023 17:19:05 +0200 Subject: [PATCH 030/141] Enable readonly properties -> properties will be greyed out in property browser if they are readonly or unavailable --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 60 +++++++++++++++---- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index fdb423678..5023a00c1 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -450,6 +450,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Init std::vector featureNames = {}; VmbError_t err = VmbErrorSuccess; + MM::Property* pChildProperty = (MM::Property*)pProp; const auto propertyName = pProp->GetName(); // Check property mapping @@ -459,7 +460,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, for (const auto& featureName : featureNames) { // Get Feature Info and Access Mode VmbFeatureInfo_t featureInfo; - bool rMode{}, wMode{}, featureAvailable{}; + bool rMode{}, wMode{}, readOnly{}, featureAvailable{}; err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), &featureInfo, sizeof(featureInfo)) | m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, @@ -469,15 +470,9 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, return err; } + readOnly = (rMode && !wMode); featureAvailable = rMode || wMode; - if (!featureAvailable) { - LOG_ERROR(VmbErrorFeaturesUnavailable, - "Feature is currently not available! Feature: " + featureName); - return err; - } - // TODO - // Set somehow property to be unavailable when there is no r and w modes - + // Get values std::string propertyValue{}, featureValue{}; pProp->Get(propertyValue); @@ -485,6 +480,35 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Handle property value change switch (eAct) { case MM::ActionType::BeforeGet: //!< Update property from feature + + // Update feature range + if (featureAvailable) + { + err = setAllowedValues(&featureInfo, propertyName.c_str()); + } + // Feature not available -> clear value and range + else { + switch (featureInfo.featureDataType) { + case VmbFeatureDataInt: + case VmbFeatureDataFloat: + err = SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); + pProp->Set("0"); + break; + case VmbFeatureDataEnum: + case VmbFeatureDataString: + case VmbFeatureDataBool: + case VmbFeatureDataCommand: + ClearAllowedValues(propertyName.c_str()); + pProp->Set(""); + break; + case VmbFeatureDataRaw: + case VmbFeatureDataNone: + default: + // feature type not supported + break; + } + } + if (rMode) { err = getFeatureValue(&featureInfo, featureName.c_str(), featureValue); @@ -511,7 +535,8 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, } } - err = setAllowedValues(&featureInfo, propertyName.c_str()); + // Set property to readonly (grey out in GUI) if it is readonly or unavailable + pChildProperty->SetReadOnly(readOnly || !featureAvailable); break; case MM::ActionType::AfterSet: //!< Update feature from property @@ -831,9 +856,20 @@ VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, VmbError_t err = VmbErrorSuccess; switch (feature->featureDataType) { - case VmbFeatureDataCommand: case VmbFeatureDataBool: { - // Already set in creation + // Already set in creation, but maybe reset when become unavailable + if (GetNumberOfPropertyValues(propertyName) == 0) { + AddAllowedValue(propertyName, g_False); + AddAllowedValue(propertyName, g_True); + } + break; + } + case VmbFeatureDataCommand: { + // Already set in creation, but maybe reset when become unavailable + if (GetNumberOfPropertyValues(propertyName) == 0) { + AddAllowedValue(propertyName, g_Command); + AddAllowedValue(propertyName, g_Execute); + } break; } case VmbFeatureDataFloat: { From 73d2a29db8348cbbe6fc07dd0ef87564a4a19a0e Mon Sep 17 00:00:00 2001 From: Florian Klostermann Date: Wed, 13 Sep 2023 09:09:12 +0200 Subject: [PATCH 031/141] Use dynamic_cast for downcasting; declare const where possible; use Property's GetType() function to determine property type (instead of Vmb feature type) --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 5023a00c1..c7318022e 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -450,17 +450,24 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, // Init std::vector featureNames = {}; VmbError_t err = VmbErrorSuccess; - MM::Property* pChildProperty = (MM::Property*)pProp; + + auto pChildProperty = dynamic_cast(pProp); + if (pChildProperty == nullptr) { + err = VmbErrorBadParameter; + LOG_ERROR(err, "Could not get Property from PropertyBase object"); + return err; + } + const auto propertyName = pProp->GetName(); // Check property mapping mapPropertyNameToFeatureNames(propertyName.c_str(), featureNames); - // Retrive each feature + // Retrieve each feature for (const auto& featureName : featureNames) { // Get Feature Info and Access Mode VmbFeatureInfo_t featureInfo; - bool rMode{}, wMode{}, readOnly{}, featureAvailable{}; + bool rMode{}, wMode{}; err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), &featureInfo, sizeof(featureInfo)) | m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, @@ -470,8 +477,8 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, return err; } - readOnly = (rMode && !wMode); - featureAvailable = rMode || wMode; + const bool readOnly = (rMode && !wMode); + const bool featureAvailable = (rMode || wMode); // Get values std::string propertyValue{}, featureValue{}; @@ -487,22 +494,17 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, err = setAllowedValues(&featureInfo, propertyName.c_str()); } // Feature not available -> clear value and range - else { - switch (featureInfo.featureDataType) { - case VmbFeatureDataInt: - case VmbFeatureDataFloat: + else { + switch (pProp->GetType()) { + case MM::Float: + case MM::Integer: err = SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); pProp->Set("0"); break; - case VmbFeatureDataEnum: - case VmbFeatureDataString: - case VmbFeatureDataBool: - case VmbFeatureDataCommand: + case MM::String: ClearAllowedValues(propertyName.c_str()); pProp->Set(""); break; - case VmbFeatureDataRaw: - case VmbFeatureDataNone: default: // feature type not supported break; From bddc46261986a2b7a562007536f71a690f96d65f Mon Sep 17 00:00:00 2001 From: Dennis Langenkamp Date: Fri, 8 Sep 2023 11:18:16 +0200 Subject: [PATCH 032/141] Fixed UNI-583 VimbaX shared libraries must be copied to "/usr/local/bin" The Allied Vision Camera Adapter was trying to load the VimbaX shared libraries from the fixed path "/usr/local/bin". This was not good, because "/usr/local/bin" is on linux a location for applications and not for libraries. Instead, now the standard linux dynamic library loading mechanism is used, which tries to automatically resolve the path of the shared library by using the system paths, ld.so.conf and the environment variable LD_LIBRARY_PATH. Adjusted the licence headers, because the micro manager adapter development was not started in 2012. --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 2 +- .../AlliedVisionCamera/AlliedVisionCamera.h | 2 +- .../AlliedVisionDeviceBase.h | 2 +- .../AlliedVisionCamera/AlliedVisionHub.cpp | 18 ++++++++++++++++++ .../AlliedVisionCamera/AlliedVisionHub.h | 2 +- .../AlliedVisionCamera/SDK/Loader/Constants.h | 5 ++--- .../SDK/Loader/LibLoader.cpp | 4 ++-- .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 2 +- 8 files changed, 27 insertions(+), 10 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index c7318022e..a3797498f 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -1,5 +1,5 @@ /*============================================================================= - Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. Redistribution of this header file, in original or modified form, without prior written consent of Allied Vision Technologies is prohibited. diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index d8eb56439..6a0bd040b 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -1,5 +1,5 @@ /*============================================================================= - Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. Redistribution of this header file, in original or modified form, without prior written consent of Allied Vision Technologies is prohibited. diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h index 825d65118..cccb06d13 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h @@ -1,5 +1,5 @@ /*============================================================================= - Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. Redistribution of this header file, in original or modified form, without prior written consent of Allied Vision Technologies is prohibited. diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp index 9d5a11173..1400542d9 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp @@ -1,3 +1,21 @@ +/*============================================================================= + Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. + + Redistribution of this header file, in original or modified form, without + prior written consent of Allied Vision Technologies is prohibited. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, + NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================*/ #include "AlliedVisionHub.h" #include "AlliedVisionCamera.h" diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h index 00e50b55a..c0daf74cd 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h @@ -1,5 +1,5 @@ /*============================================================================= - Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. Redistribution of this header file, in original or modified form, without prior written consent of Allied Vision Technologies is prohibited. diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h index 99e9c3c3a..e3c39a896 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h @@ -1,5 +1,5 @@ /*============================================================================= - Copyright (C) 2012 - 2023 Allied Vision Technologies. All Rights Reserved. + Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. Redistribution of this header file, in original or modified form, without prior written consent of Allied Vision Technologies is prohibited. @@ -20,8 +20,7 @@ #define CONSTANTS_H #ifdef __linux__ -static std::string VIMBA_X_LIB_DIR = std::string( - "/usr/local/bin/"); // Date: Wed, 13 Sep 2023 11:37:48 +0200 Subject: [PATCH 033/141] Before a feature is read or written the locale is set to the current user locale. --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 278 +++++++++--------- 1 file changed, 144 insertions(+), 134 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index a3797498f..b29de219c 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -447,140 +447,150 @@ bool AlliedVisionCamera::IsCapturing() { return m_isAcquisitionRunning; } int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, MM::ActionType eAct) { - // Init - std::vector featureNames = {}; - VmbError_t err = VmbErrorSuccess; - - auto pChildProperty = dynamic_cast(pProp); - if (pChildProperty == nullptr) { - err = VmbErrorBadParameter; - LOG_ERROR(err, "Could not get Property from PropertyBase object"); - return err; - } - - const auto propertyName = pProp->GetName(); - - // Check property mapping - mapPropertyNameToFeatureNames(propertyName.c_str(), featureNames); - - // Retrieve each feature - for (const auto& featureName : featureNames) { - // Get Feature Info and Access Mode - VmbFeatureInfo_t featureInfo; - bool rMode{}, wMode{}; - err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), - &featureInfo, sizeof(featureInfo)) | - m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, - &wMode); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting info or access query!"); - return err; - } - - const bool readOnly = (rMode && !wMode); - const bool featureAvailable = (rMode || wMode); - - // Get values - std::string propertyValue{}, featureValue{}; - pProp->Get(propertyValue); - - // Handle property value change - switch (eAct) { - case MM::ActionType::BeforeGet: //!< Update property from feature - - // Update feature range - if (featureAvailable) - { - err = setAllowedValues(&featureInfo, propertyName.c_str()); - } - // Feature not available -> clear value and range - else { - switch (pProp->GetType()) { - case MM::Float: - case MM::Integer: - err = SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); - pProp->Set("0"); - break; - case MM::String: - ClearAllowedValues(propertyName.c_str()); - pProp->Set(""); - break; - default: - // feature type not supported - break; - } - } - - if (rMode) { - err = - getFeatureValue(&featureInfo, featureName.c_str(), featureValue); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting feature value " + featureName); - return err; - } - - // Update property - if (propertyValue != featureValue) { - pProp->Set(featureValue.c_str()); - err = GetCoreCallback()->OnPropertyChanged( - this, propertyName.c_str(), featureValue.c_str()); - if (propertyName == MM::g_Keyword_PixelType) { - handlePixelFormatChange(featureValue); - } - - if (VmbErrorSuccess != err) { - LOG_ERROR(err, - "Error while calling OnPropertyChanged callback for " + - featureName); - return err; - } - } - } - - // Set property to readonly (grey out in GUI) if it is readonly or unavailable - pChildProperty->SetReadOnly(readOnly || !featureAvailable); - - break; - case MM::ActionType::AfterSet: //!< Update feature from property - err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); - if (err == VmbErrorInvalidValue) { - // Update limits first to have latest min and max - err = setAllowedValues(&featureInfo, propertyName.c_str()); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while setting allowed values for feature " + - featureName); - return err; - } - - // Adjust value - double min{}, max{}; - err = GetPropertyLowerLimit(propertyName.c_str(), min) | - GetPropertyUpperLimit(propertyName.c_str(), max); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting limits for " + propertyName); - return err; - } - std::string adjustedValue = - adjustValue(featureInfo, min, max, std::stod(propertyValue)); - err = - setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); - } - - if (propertyName == MM::g_Keyword_PixelType) { - handlePixelFormatChange(propertyValue); - } - break; - default: - // nothing - break; - } - } - - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while updating property " + propertyName); - } - - return err; + //Set locale to current system locale + const auto oldLocale = std::setlocale(LC_ALL, ""); + + const auto res = [&] { + // Init + std::vector featureNames = {}; + VmbError_t err = VmbErrorSuccess; + + auto pChildProperty = dynamic_cast(pProp); + if (pChildProperty == nullptr) { + err = VmbErrorBadParameter; + LOG_ERROR(err, "Could not get Property from PropertyBase object"); + return err; + } + + const auto propertyName = pProp->GetName(); + + // Check property mapping + mapPropertyNameToFeatureNames(propertyName.c_str(), featureNames); + + // Retrieve each feature + for (const auto& featureName : featureNames) { + // Get Feature Info and Access Mode + VmbFeatureInfo_t featureInfo; + bool rMode{}, wMode{}; + err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), + &featureInfo, sizeof(featureInfo)) | + m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, + &wMode); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting info or access query!"); + return err; + } + + const bool readOnly = (rMode && !wMode); + const bool featureAvailable = (rMode || wMode); + + // Get values + std::string propertyValue{}, featureValue{}; + pProp->Get(propertyValue); + + // Handle property value change + switch (eAct) { + case MM::ActionType::BeforeGet: //!< Update property from feature + + // Update feature range + if (featureAvailable) + { + err = setAllowedValues(&featureInfo, propertyName.c_str()); + } + // Feature not available -> clear value and range + else { + switch (pProp->GetType()) { + case MM::Float: + case MM::Integer: + err = SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); + pProp->Set("0"); + break; + case MM::String: + ClearAllowedValues(propertyName.c_str()); + pProp->Set(""); + break; + default: + // feature type not supported + break; + } + } + + if (rMode) { + err = + getFeatureValue(&featureInfo, featureName.c_str(), featureValue); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting feature value " + featureName); + return err; + } + + // Update property + if (propertyValue != featureValue) { + pProp->Set(featureValue.c_str()); + err = GetCoreCallback()->OnPropertyChanged( + this, propertyName.c_str(), featureValue.c_str()); + if (propertyName == MM::g_Keyword_PixelType) { + handlePixelFormatChange(featureValue); + } + + if (VmbErrorSuccess != err) { + LOG_ERROR(err, + "Error while calling OnPropertyChanged callback for " + + featureName); + return err; + } + } + } + + // Set property to readonly (grey out in GUI) if it is readonly or unavailable + pChildProperty->SetReadOnly(readOnly || !featureAvailable); + + break; + case MM::ActionType::AfterSet: //!< Update feature from property + err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); + if (err == VmbErrorInvalidValue) { + // Update limits first to have latest min and max + err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while setting allowed values for feature " + + featureName); + return err; + } + + // Adjust value + double min{}, max{}; + err = GetPropertyLowerLimit(propertyName.c_str(), min) | + GetPropertyUpperLimit(propertyName.c_str(), max); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting limits for " + propertyName); + return err; + } + std::string adjustedValue = + adjustValue(featureInfo, min, max, std::stod(propertyValue)); + err = + setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); + } + + if (propertyName == MM::g_Keyword_PixelType) { + handlePixelFormatChange(propertyValue); + } + break; + default: + // nothing + break; + } + } + + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while updating property " + propertyName); + } + + + return err; + }(); + + std::setlocale(LC_ALL, oldLocale); + + return res; } void AlliedVisionCamera::handlePixelFormatChange(const std::string& pixelType) { From 3143322cfaa8d87439ff5ceeab6e3540c4ca2755 Mon Sep 17 00:00:00 2001 From: Dennis Langenkamp Date: Wed, 13 Sep 2023 17:17:50 +0200 Subject: [PATCH 034/141] Fixed UNI-591 main window exposure setting not working The legacy cameras use the feature "ExposureTimeAbs" instead of "ExposureTime" for setting the expsure time. The adapter was using the "ExposureTime" feature for the main window exposure setting. If the feature "ExposureTime" is available, it is used for the exposure setting. But if it is not available now "ExposureTimeAbs" is used for the exposure setting. The value of the exposure setting now also corrected to milliseconds. --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 352 ++++++++++-------- 1 file changed, 188 insertions(+), 164 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index b29de219c..dcfcb05be 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -32,15 +32,11 @@ /////////////////////////////////////////////////////////////////////////////// // STATIC VALUES /////////////////////////////////////////////////////////////////////////////// -const std::unordered_map - AlliedVisionCamera::m_featureToProperty = { - {g_PixelFormatFeature, MM::g_Keyword_PixelType}, - {g_ExposureFeature, MM::g_Keyword_Exposure}}; -const std::unordered_multimap - AlliedVisionCamera::m_propertyToFeature = { - {MM::g_Keyword_PixelType, g_PixelFormatFeature}, - {MM::g_Keyword_Exposure, g_ExposureFeature}}; +const std::unordered_map AlliedVisionCamera::m_featureToProperty = +{ + {g_PixelFormatFeature, MM::g_Keyword_PixelType } +}; /////////////////////////////////////////////////////////////////////////////// // Exported MMDevice API @@ -83,7 +79,9 @@ AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) m_bufferSize{0}, m_payloadSize{0}, m_isAcquisitionRunning{false}, - m_currentPixelFormat{} { + m_currentPixelFormat{}, + m_propertyToFeature{}, + m_exposureFeatureName{} { CreateHubIDProperty(); // Binning property is a Core Property, we will have a dummy one CreateProperty(MM::g_Keyword_Binning, "N/A", MM::String, true, nullptr); @@ -132,23 +130,47 @@ VmbError_t AlliedVisionCamera::setupProperties() { return err; } - std::shared_ptr features = - std::shared_ptr(new VmbFeatureInfo_t[featureCount]); - err = m_sdk->VmbFeaturesList_t(m_handle, features.get(), featureCount, + std::vector features{featureCount}; + + err = m_sdk->VmbFeaturesList_t(m_handle, features.data(), featureCount, &featureCount, sizeof(VmbFeatureInfo_t)); if (err != VmbErrorSuccess) { LOG_ERROR(err, "Error occurred when obtaining features!"); return err; } - const VmbFeatureInfo_t* end = features.get() + featureCount; - for (VmbFeatureInfo_t* feature = features.get(); feature != end; ++feature) { - err = createPropertyFromFeature(feature); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, - "Error while creating property" + std::string(feature->name)); - continue; - } + const auto exposureTime = std::find_if(features.begin(), features.end(), [](const auto& feat) + { + return std::string(feat.name) == g_ExposureFeature; + }); + + if (exposureTime != features.end()) + { + m_exposureFeatureName = std::string{ exposureTime->name }; + } + else + { + const auto exposureTimeAbs = std::find_if(features.begin(), features.end(), [](const auto& feat) + { + return std::string(feat.name) == g_ExposureAbsFeature; + }); + + if (exposureTimeAbs != features.end()) + { + m_exposureFeatureName = std::string{ exposureTimeAbs->name }; + } + } + + + + for (const auto & feature : features) { + if (feature.visibility != VmbFeatureVisibilityInvisible) + { + err = createPropertyFromFeature(&feature); + if (err != VmbErrorSuccess) { + LOG_ERROR(err, "Error while creating property" + std::string(feature.name)); + } + } } return VmbErrorSuccess; @@ -191,8 +213,20 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( } // Map feature to property name - std::string propertyName = {}; - mapFeatureNameToPropertyName(feature->name, propertyName); + const auto propertyName = [&] { + const auto tempName = mapFeatureNameToPropertyName(feature->name); + + if (tempName != feature->name) + { + if (!m_propertyToFeature.count(tempName)) + { + m_propertyToFeature.emplace(tempName, feature->name); + return tempName; + } + } + + return std::string(feature->name); + }(); // uManager callback CPropertyAction* uManagerCallback = @@ -204,8 +238,7 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature( (void)handle; AlliedVisionCamera* camera = reinterpret_cast(userContext); - std::string propertyName; - camera->mapFeatureNameToPropertyName(name, propertyName); + const auto propertyName = camera->mapFeatureNameToPropertyName(name); auto err = camera->UpdateProperty(propertyName.c_str()); if (err != VmbErrorSuccess) { camera->LOG_ERROR(err, "Property: " + propertyName + " update failed"); @@ -320,17 +353,17 @@ int AlliedVisionCamera::SetBinning(int binSize) { double AlliedVisionCamera::GetExposure() const { std::string value{}; - auto ret = getFeatureValue(g_ExposureFeature, value); + auto ret = getFeatureValue(m_exposureFeatureName.c_str(), value); if (ret != VmbErrorSuccess) { LOG_ERROR(ret, "Error while getting exposure!"); return 0; } - return strtod(value.c_str(), nullptr); + return std::stod(value) / MS_TO_US; } void AlliedVisionCamera::SetExposure(double exp_ms) { - SetProperty(MM::g_Keyword_Exposure, CDeviceUtils::ConvertToString(exp_ms)); + SetProperty(m_exposureFeatureName.c_str(), CDeviceUtils::ConvertToString(exp_ms * MS_TO_US)); GetCoreCallback()->OnExposureChanged(this, exp_ms); } @@ -452,7 +485,6 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, const auto res = [&] { // Init - std::vector featureNames = {}; VmbError_t err = VmbErrorSuccess; auto pChildProperty = dynamic_cast(pProp); @@ -465,120 +497,117 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, const auto propertyName = pProp->GetName(); // Check property mapping - mapPropertyNameToFeatureNames(propertyName.c_str(), featureNames); - - // Retrieve each feature - for (const auto& featureName : featureNames) { - // Get Feature Info and Access Mode - VmbFeatureInfo_t featureInfo; - bool rMode{}, wMode{}; - err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), - &featureInfo, sizeof(featureInfo)) | - m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, - &wMode); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting info or access query!"); - return err; - } - - const bool readOnly = (rMode && !wMode); - const bool featureAvailable = (rMode || wMode); - - // Get values - std::string propertyValue{}, featureValue{}; - pProp->Get(propertyValue); - - // Handle property value change - switch (eAct) { - case MM::ActionType::BeforeGet: //!< Update property from feature - - // Update feature range - if (featureAvailable) - { - err = setAllowedValues(&featureInfo, propertyName.c_str()); - } - // Feature not available -> clear value and range - else { - switch (pProp->GetType()) { - case MM::Float: - case MM::Integer: - err = SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); - pProp->Set("0"); - break; - case MM::String: - ClearAllowedValues(propertyName.c_str()); - pProp->Set(""); - break; - default: - // feature type not supported - break; - } - } - - if (rMode) { - err = - getFeatureValue(&featureInfo, featureName.c_str(), featureValue); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting feature value " + featureName); - return err; - } - - // Update property - if (propertyValue != featureValue) { - pProp->Set(featureValue.c_str()); - err = GetCoreCallback()->OnPropertyChanged( - this, propertyName.c_str(), featureValue.c_str()); - if (propertyName == MM::g_Keyword_PixelType) { - handlePixelFormatChange(featureValue); - } - - if (VmbErrorSuccess != err) { - LOG_ERROR(err, - "Error while calling OnPropertyChanged callback for " + - featureName); - return err; - } - } - } - - // Set property to readonly (grey out in GUI) if it is readonly or unavailable - pChildProperty->SetReadOnly(readOnly || !featureAvailable); - - break; - case MM::ActionType::AfterSet: //!< Update feature from property - err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); - if (err == VmbErrorInvalidValue) { - // Update limits first to have latest min and max - err = setAllowedValues(&featureInfo, propertyName.c_str()); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while setting allowed values for feature " + - featureName); - return err; - } - - // Adjust value - double min{}, max{}; - err = GetPropertyLowerLimit(propertyName.c_str(), min) | - GetPropertyUpperLimit(propertyName.c_str(), max); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting limits for " + propertyName); - return err; - } - std::string adjustedValue = - adjustValue(featureInfo, min, max, std::stod(propertyValue)); - err = - setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); - } - - if (propertyName == MM::g_Keyword_PixelType) { - handlePixelFormatChange(propertyValue); - } - break; - default: - // nothing - break; - } - } + auto const featureName = mapPropertyNameToFeatureName(propertyName.c_str()); + + // Get Feature Info and Access Mode + VmbFeatureInfo_t featureInfo; + bool rMode{}, wMode{}; + err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), + &featureInfo, sizeof(featureInfo)) | + m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, + &wMode); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting info or access query!"); + return err; + } + + const bool readOnly = (rMode && !wMode); + const bool featureAvailable = (rMode || wMode); + + // Get values + std::string propertyValue{}, featureValue{}; + pProp->Get(propertyValue); + + // Handle property value change + switch (eAct) { + case MM::ActionType::BeforeGet: //!< Update property from feature + + // Update feature range + if (featureAvailable) + { + err = setAllowedValues(&featureInfo, propertyName.c_str()); + } + // Feature not available -> clear value and range + else { + switch (pProp->GetType()) { + case MM::Float: + case MM::Integer: + err = SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); + pProp->Set("0"); + break; + case MM::String: + ClearAllowedValues(propertyName.c_str()); + pProp->Set(""); + break; + default: + // feature type not supported + break; + } + } + + if (rMode) { + err = + getFeatureValue(&featureInfo, featureName.c_str(), featureValue); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting feature value " + featureName); + return err; + } + + // Update property + if (propertyValue != featureValue) { + pProp->Set(featureValue.c_str()); + err = GetCoreCallback()->OnPropertyChanged( + this, propertyName.c_str(), featureValue.c_str()); + if (propertyName == MM::g_Keyword_PixelType) { + handlePixelFormatChange(featureValue); + } + + if (VmbErrorSuccess != err) { + LOG_ERROR(err, + "Error while calling OnPropertyChanged callback for " + + featureName); + return err; + } + } + } + + // Set property to readonly (grey out in GUI) if it is readonly or unavailable + pChildProperty->SetReadOnly(readOnly || !featureAvailable); + + break; + case MM::ActionType::AfterSet: //!< Update feature from property + err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); + if (err == VmbErrorInvalidValue) { + // Update limits first to have latest min and max + err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while setting allowed values for feature " + + featureName); + return err; + } + + // Adjust value + double min{}, max{}; + err = GetPropertyLowerLimit(propertyName.c_str(), min) | + GetPropertyUpperLimit(propertyName.c_str(), max); + if (VmbErrorSuccess != err) { + LOG_ERROR(err, "Error while getting limits for " + propertyName); + return err; + } + std::string adjustedValue = + adjustValue(featureInfo, min, max, std::stod(propertyValue)); + err = + setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); + } + + if (propertyName == MM::g_Keyword_PixelType) { + handlePixelFormatChange(propertyValue); + } + break; + default: + // nothing + break; + } if (VmbErrorSuccess != err) { LOG_ERROR(err, "Error while updating property " + propertyName); @@ -693,7 +722,6 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, std::stringstream ss(value); bool isDone = false; VmbUint32_t maxLen = 0; - std::string property{}; switch (featureInfo->featureDataType) { case VmbFeatureDataBool: { @@ -733,8 +761,8 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, } case VmbFeatureDataCommand: if (value == g_Execute) { - mapFeatureNameToPropertyName(featureName, property); - if (!property.empty()) { + const auto propertyName = mapFeatureNameToPropertyName(featureName); + if (!propertyName.empty()) { err = m_sdk->VmbFeatureCommandRun_t(m_handle, featureName); if (err != VmbErrorSuccess) { break; @@ -747,8 +775,8 @@ VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, } } // Set back property to "Command" - err = SetProperty(property.c_str(), g_Command); - GetCoreCallback()->OnPropertyChanged(this, property.c_str(), + err = SetProperty(propertyName.c_str(), g_Command); + GetCoreCallback()->OnPropertyChanged(this, propertyName.c_str(), g_Command); if (err != VmbErrorSuccess) { break; @@ -788,27 +816,23 @@ VmbError_t AlliedVisionCamera::setFeatureValue(const char* featureName, return setFeatureValue(&featureInfo, featureName, value); } -void AlliedVisionCamera::mapFeatureNameToPropertyName( - const char* feature, std::string& property) const { - property = std::string(feature); - auto search = m_featureToProperty.find(property); +std::string AlliedVisionCamera::mapFeatureNameToPropertyName( + const char* feature) const { + + auto search = m_featureToProperty.find(feature); if (search != m_featureToProperty.end()) { - property = search->second; + return search->second; } + + return feature; } -void AlliedVisionCamera::mapPropertyNameToFeatureNames( - const char* property, std::vector& featureNames) const { - // Check property mapping - auto searchRange = m_propertyToFeature.equal_range(property); - if (searchRange.first != m_propertyToFeature.end()) { - // Features that are mapped from property - for (auto it = searchRange.first; it != searchRange.second; ++it) { - featureNames.push_back(it->second); +std::string AlliedVisionCamera::mapPropertyNameToFeatureName(const char* property) const { + auto search = m_propertyToFeature.find(property); + if (search != m_propertyToFeature.end()) { + return search->second; } - } else { - // for rest - featureNames.push_back(property); - } + + return property; } std::string AlliedVisionCamera::adjustValue(VmbFeatureInfo_t& featureInfo, From 486e24fed6e9e4de75ebaa7f15c2b347086c1ee1 Mon Sep 17 00:00:00 2001 From: Dennis Langenkamp Date: Wed, 13 Sep 2023 17:18:41 +0200 Subject: [PATCH 035/141] Updated header --- .../AlliedVisionCamera/AlliedVisionCamera.h | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 6a0bd040b..d34e5ffca 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -28,11 +28,13 @@ #include "DeviceBase.h" #include "Loader/LibLoader.h" + /////////////////////////////////////////////////////////////////////////////// // STATIC FEATURE NAMES (FROM VIMBA) /////////////////////////////////////////////////////////////////////////////// static constexpr const char* g_PixelFormatFeature = "PixelFormat"; static constexpr const char* g_ExposureFeature = "ExposureTime"; +static constexpr const char* g_ExposureAbsFeature = "ExposureTimeAbs"; static constexpr const char* g_BinningHorizontalFeature = "BinningHorizontal"; static constexpr const char* g_BinningVerticalFeature = "BinningVertical"; static constexpr const char* g_Width = "Width"; @@ -55,6 +57,8 @@ static constexpr const char* g_AcquisitionStart = "AcquisitionStart"; static constexpr const char* g_AcquisitionStop = "AcquisitionStop"; static constexpr const char* g_AcqusitionStatus = "AcqusitionStatus"; +static constexpr const double MS_TO_US = 1000.0; + /** * @brief Pixel Format class that contains VMB Pixel Format info and contains * helper methods to convert these information to the one that uManager @@ -370,10 +374,9 @@ class AlliedVisionCamera /** * @brief Helper method to map feature name into property name of uManager * @param[in] feature Vimba Feature name - * @param property uManager property name + * @return uManager property name */ - void mapFeatureNameToPropertyName(const char* feature, - std::string& property) const; + std::string mapFeatureNameToPropertyName(const char* feature) const; /** * @brief Helper method to map uManager property in Vimba feature or features @@ -381,8 +384,7 @@ class AlliedVisionCamera * @param[in] property uManager property name * @param featureNames Vimba feature or features name */ - void mapPropertyNameToFeatureNames( - const char* property, std::vector& featureNames) const; + std::string mapPropertyNameToFeatureName(const char* property) const; /** * @brief In case trying to set invalid value, adjust it to the closest with @@ -428,12 +430,17 @@ class AlliedVisionCamera VmbUint32_t m_payloadSize; // m_featureToProperty; //!< Map of features name into uManager properties - static const std::unordered_multimap + std::unordered_map m_propertyToFeature; //!< Map of uManager properties into Vimba features + }; #endif From 05629546f06e9ca7dfa6b9a065cce3603c9107c2 Mon Sep 17 00:00:00 2001 From: Florian Klostermann Date: Thu, 14 Sep 2023 13:09:33 +0200 Subject: [PATCH 036/141] adjusted Windows VmbC dll search path: use /bin instead of /api/bin --- DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h index e3c39a896..1350187a8 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h @@ -33,7 +33,7 @@ static const char* ENV_VALUE = static std::string VIMBA_X_LIB_DIR = std::string(ENV_VALUE != nullptr ? ENV_VALUE : "") + std::string( - "\\api\\bin"); // Date: Thu, 14 Sep 2023 14:52:08 +0200 Subject: [PATCH 037/141] refactored code with clang-format --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 2060 +++++++++-------- .../AlliedVisionCamera/AlliedVisionCamera.h | 793 ++++--- .../AlliedVisionDeviceBase.cpp | 2 - .../AlliedVisionDeviceBase.h | 108 +- .../AlliedVisionCamera/AlliedVisionHub.cpp | 94 +- .../AlliedVisionCamera/AlliedVisionHub.h | 60 +- .../AlliedVisionCamera/SDK/Loader/Constants.h | 24 +- .../SDK/Loader/LibLoader.cpp | 262 +-- .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 284 +-- 9 files changed, 1909 insertions(+), 1778 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index dcfcb05be..d736fc617 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -16,7 +16,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =============================================================================*/ -#define NOMINMAX // AlliedVisionCamera::m_featureToProperty = -{ - {g_PixelFormatFeature, MM::g_Keyword_PixelType } -}; +const std::unordered_map AlliedVisionCamera::m_featureToProperty = { { g_PixelFormatFeature, + MM::g_Keyword_PixelType } }; /////////////////////////////////////////////////////////////////////////////// // Exported MMDevice API /////////////////////////////////////////////////////////////////////////////// -MODULE_API void InitializeModuleData() { - RegisterDevice(g_hubName, MM::HubDevice, "Allied Vision Hub"); +MODULE_API void InitializeModuleData() +{ + RegisterDevice(g_hubName, MM::HubDevice, "Allied Vision Hub"); } -MODULE_API MM::Device* CreateDevice(const char* deviceName) { - if (deviceName == nullptr) { - return nullptr; - } +MODULE_API MM::Device *CreateDevice(const char *deviceName) +{ + if (deviceName == nullptr) + { + return nullptr; + } - if (std::string(deviceName) == std::string(g_hubName)) { - return new AlliedVisionHub(); - } else { - return new AlliedVisionCamera(deviceName); - } + if (std::string(deviceName) == std::string(g_hubName)) + { + return new AlliedVisionHub(); + } + else + { + return new AlliedVisionCamera(deviceName); + } } -MODULE_API void DeleteDevice(MM::Device* pDevice) { delete pDevice; } +MODULE_API void DeleteDevice(MM::Device *pDevice) +{ + delete pDevice; +} /////////////////////////////////////////////////////////////////////////////// // AlliedVisionCamera /////////////////////////////////////////////////////////////////////////////// -AlliedVisionCamera::~AlliedVisionCamera() { - m_handle = nullptr; +AlliedVisionCamera::~AlliedVisionCamera() +{ + m_handle = nullptr; - for (size_t i = 0; i < MAX_FRAMES; i++) { - delete[] m_buffer[i]; - } + for (size_t i = 0; i < MAX_FRAMES; i++) + { + delete[] m_buffer[i]; + } } -AlliedVisionCamera::AlliedVisionCamera(const char* deviceName) - : m_sdk(nullptr), - m_handle{nullptr}, - m_cameraName{deviceName}, - m_frames{}, - m_buffer{}, - m_bufferSize{0}, - m_payloadSize{0}, - m_isAcquisitionRunning{false}, - m_currentPixelFormat{}, - m_propertyToFeature{}, - m_exposureFeatureName{} { - CreateHubIDProperty(); - // Binning property is a Core Property, we will have a dummy one - CreateProperty(MM::g_Keyword_Binning, "N/A", MM::String, true, nullptr); - AddAllowedValue(MM::g_Keyword_Binning, "N/A"); +AlliedVisionCamera::AlliedVisionCamera(const char *deviceName) : + m_sdk(nullptr), + m_handle{ nullptr }, + m_cameraName{ deviceName }, + m_frames{}, + m_buffer{}, + m_bufferSize{ 0 }, + m_payloadSize{ 0 }, + m_isAcquisitionRunning{ false }, + m_currentPixelFormat{}, + m_propertyToFeature{}, + m_exposureFeatureName{} +{ + CreateHubIDProperty(); + // Binning property is a Core Property, we will have a dummy one + CreateProperty(MM::g_Keyword_Binning, "N/A", MM::String, true, nullptr); + AddAllowedValue(MM::g_Keyword_Binning, "N/A"); } -int AlliedVisionCamera::Initialize() { - auto parentHub = dynamic_cast(GetParentHub()); - if (parentHub == nullptr) { - LOG_ERROR(VmbErrorBadParameter, "Parent HUB not found!"); - return VmbErrorBadParameter; - } - m_sdk = parentHub->getSDK(); - - LogMessage("Opening camera: " + m_cameraName); - VmbError_t err = m_sdk->VmbCameraOpen_t( - m_cameraName.c_str(), VmbAccessModeType::VmbAccessModeFull, &m_handle); - if (err != VmbErrorSuccess || m_handle == nullptr) { - LOG_ERROR(err, "Error while opening camera or handle is NULL!"); - return err; - } +int AlliedVisionCamera::Initialize() +{ + auto parentHub = dynamic_cast(GetParentHub()); + if (parentHub == nullptr) + { + LOG_ERROR(VmbErrorBadParameter, "Parent HUB not found!"); + return VmbErrorBadParameter; + } + m_sdk = parentHub->getSDK(); + + LogMessage("Opening camera: " + m_cameraName); + VmbError_t err = m_sdk->VmbCameraOpen_t(m_cameraName.c_str(), VmbAccessModeType::VmbAccessModeFull, &m_handle); + if (err != VmbErrorSuccess || m_handle == nullptr) + { + LOG_ERROR(err, "Error while opening camera or handle is NULL!"); + return err; + } - // Ignore errors from setting up properties - (void)setupProperties(); - return resizeImageBuffer(); + // Ignore errors from setting up properties + (void)setupProperties(); + return resizeImageBuffer(); } -int AlliedVisionCamera::Shutdown() { - LogMessage("Shutting down camera: " + m_cameraName); - VmbError_t err = VmbErrorSuccess; - if (m_sdk != nullptr && m_sdk->isInitialized()) { - if (m_handle != nullptr) { - err = m_sdk->VmbCameraClose_t(m_handle); +int AlliedVisionCamera::Shutdown() +{ + LogMessage("Shutting down camera: " + m_cameraName); + VmbError_t err = VmbErrorSuccess; + if (m_sdk != nullptr && m_sdk->isInitialized()) + { + if (m_handle != nullptr) + { + err = m_sdk->VmbCameraClose_t(m_handle); + } } - } - return err; + return err; } -VmbError_t AlliedVisionCamera::setupProperties() { - VmbUint32_t featureCount = 0; - VmbError_t err = m_sdk->VmbFeaturesList_t(m_handle, NULL, 0, &featureCount, - sizeof(VmbFeatureInfo_t)); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error occurred when obtaining features count!"); - return err; - } +VmbError_t AlliedVisionCamera::setupProperties() +{ + VmbUint32_t featureCount = 0; + VmbError_t err = m_sdk->VmbFeaturesList_t(m_handle, NULL, 0, &featureCount, sizeof(VmbFeatureInfo_t)); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error occurred when obtaining features count!"); + return err; + } - std::vector features{featureCount}; + std::vector features{ featureCount }; - err = m_sdk->VmbFeaturesList_t(m_handle, features.data(), featureCount, - &featureCount, sizeof(VmbFeatureInfo_t)); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error occurred when obtaining features!"); - return err; - } - - const auto exposureTime = std::find_if(features.begin(), features.end(), [](const auto& feat) - { - return std::string(feat.name) == g_ExposureFeature; - }); - - if (exposureTime != features.end()) - { - m_exposureFeatureName = std::string{ exposureTime->name }; - } - else - { - const auto exposureTimeAbs = std::find_if(features.begin(), features.end(), [](const auto& feat) - { - return std::string(feat.name) == g_ExposureAbsFeature; - }); - - if (exposureTimeAbs != features.end()) - { - m_exposureFeatureName = std::string{ exposureTimeAbs->name }; - } - } - - - - for (const auto & feature : features) { - if (feature.visibility != VmbFeatureVisibilityInvisible) - { - err = createPropertyFromFeature(&feature); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while creating property" + std::string(feature.name)); - } - } - } - - return VmbErrorSuccess; + err = m_sdk->VmbFeaturesList_t(m_handle, features.data(), featureCount, &featureCount, sizeof(VmbFeatureInfo_t)); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error occurred when obtaining features!"); + return err; + } + + const auto exposureTime = + std::find_if(features.begin(), features.end(), [](const auto &feat) { return std::string(feat.name) == g_ExposureFeature; }); + + if (exposureTime != features.end()) + { + m_exposureFeatureName = std::string{ exposureTime->name }; + } + else + { + const auto exposureTimeAbs = + std::find_if(features.begin(), features.end(), [](const auto &feat) { return std::string(feat.name) == g_ExposureAbsFeature; }); + + if (exposureTimeAbs != features.end()) + { + m_exposureFeatureName = std::string{ exposureTimeAbs->name }; + } + } + + for (const auto &feature : features) + { + if (feature.visibility != VmbFeatureVisibilityInvisible) + { + err = createPropertyFromFeature(&feature); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while creating property" + std::string(feature.name)); + } + } + } + + return VmbErrorSuccess; } -VmbError_t AlliedVisionCamera::resizeImageBuffer() { - VmbError_t err = m_sdk->VmbPayloadSizeGet_t(m_handle, &m_payloadSize); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while reading payload size"); - return err; - } +VmbError_t AlliedVisionCamera::resizeImageBuffer() +{ + VmbError_t err = m_sdk->VmbPayloadSizeGet_t(m_handle, &m_payloadSize); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while reading payload size"); + return err; + } - m_bufferSize = std::max(GetImageWidth() * GetImageHeight() * - m_currentPixelFormat.getBytesPerPixel(), - m_payloadSize); + m_bufferSize = std::max(GetImageWidth() * GetImageHeight() * m_currentPixelFormat.getBytesPerPixel(), m_payloadSize); - for (size_t i = 0; i < MAX_FRAMES; i++) { - delete[] m_buffer[i]; - m_buffer[i] = new VmbUint8_t[m_bufferSize]; - } + for (size_t i = 0; i < MAX_FRAMES; i++) + { + delete[] m_buffer[i]; + m_buffer[i] = new VmbUint8_t[m_bufferSize]; + } - return VmbErrorSuccess; + return VmbErrorSuccess; } -VmbError_t AlliedVisionCamera::createPropertyFromFeature( - const VmbFeatureInfo_t* feature) { - if (feature == nullptr) { - LogMessage("Cannot create feature. It is NULL"); - return VmbErrorInvalidValue; - } - - VmbError_t err = VmbErrorSuccess; - // Skip Event, Chunk, RAW features - std::string featureCategory = feature->category; - if (featureCategory.find(g_EventCategory) != std::string::npos || - featureCategory.find(g_ChunkCategory) != std::string::npos || - feature->featureDataType == VmbFeatureDataRaw) { - // Skip - return err; - } - - // Map feature to property name - const auto propertyName = [&] { - const auto tempName = mapFeatureNameToPropertyName(feature->name); - - if (tempName != feature->name) - { - if (!m_propertyToFeature.count(tempName)) - { - m_propertyToFeature.emplace(tempName, feature->name); - return tempName; - } - } - - return std::string(feature->name); - }(); - - // uManager callback - CPropertyAction* uManagerCallback = - new CPropertyAction(this, &AlliedVisionCamera::onProperty); - - // Vimba callback - auto vmbCallback = [](VmbHandle_t handle, const char* name, - void* userContext) { - (void)handle; - AlliedVisionCamera* camera = - reinterpret_cast(userContext); - const auto propertyName = camera->mapFeatureNameToPropertyName(name); - auto err = camera->UpdateProperty(propertyName.c_str()); - if (err != VmbErrorSuccess) { - camera->LOG_ERROR(err, "Property: " + propertyName + " update failed"); - } - }; - - // Register VMB callback for given feature - err = m_sdk->VmbFeatureInvalidationRegister_t(m_handle, feature->name, - vmbCallback, this); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while registering invalidation callback for " + - std::string(feature->name)); - return err; - } - - switch (feature->featureDataType) { - case VmbFeatureDataInt: { - err = CreateIntegerProperty(propertyName.c_str(), 0, false, - uManagerCallback); - break; - } - case VmbFeatureDataBool: { - err = CreateStringProperty(propertyName.c_str(), g_False, false, - uManagerCallback); - AddAllowedValue(propertyName.c_str(), g_False); - AddAllowedValue(propertyName.c_str(), g_True); - break; - } - case VmbFeatureDataCommand: { - err = CreateStringProperty(propertyName.c_str(), g_Command, false, - uManagerCallback); - AddAllowedValue(propertyName.c_str(), g_Command); - AddAllowedValue(propertyName.c_str(), g_Execute); - break; +VmbError_t AlliedVisionCamera::createPropertyFromFeature(const VmbFeatureInfo_t *feature) +{ + if (feature == nullptr) + { + LogMessage("Cannot create feature. It is NULL"); + return VmbErrorInvalidValue; + } + + VmbError_t err = VmbErrorSuccess; + // Skip Event, Chunk, RAW features + std::string featureCategory = feature->category; + if (featureCategory.find(g_EventCategory) != std::string::npos || featureCategory.find(g_ChunkCategory) != std::string::npos || + feature->featureDataType == VmbFeatureDataRaw) + { + // Skip + return err; + } + + // Map feature to property name + const auto propertyName = [&] + { + const auto tempName = mapFeatureNameToPropertyName(feature->name); + + if (tempName != feature->name) + { + if (!m_propertyToFeature.count(tempName)) + { + m_propertyToFeature.emplace(tempName, feature->name); + return tempName; + } + } + + return std::string(feature->name); + }(); + + // uManager callback + CPropertyAction *uManagerCallback = new CPropertyAction(this, &AlliedVisionCamera::onProperty); + + // Vimba callback + auto vmbCallback = [](VmbHandle_t handle, const char *name, void *userContext) + { + (void)handle; + AlliedVisionCamera *camera = reinterpret_cast(userContext); + const auto propertyName = camera->mapFeatureNameToPropertyName(name); + auto err = camera->UpdateProperty(propertyName.c_str()); + if (err != VmbErrorSuccess) + { + camera->LOG_ERROR(err, "Property: " + propertyName + " update failed"); + } + }; + + // Register VMB callback for given feature + err = m_sdk->VmbFeatureInvalidationRegister_t(m_handle, feature->name, vmbCallback, this); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while registering invalidation callback for " + std::string(feature->name)); + return err; + } + + switch (feature->featureDataType) + { + case VmbFeatureDataInt: + { + err = CreateIntegerProperty(propertyName.c_str(), 0, false, uManagerCallback); + break; + } + case VmbFeatureDataBool: + { + err = CreateStringProperty(propertyName.c_str(), g_False, false, uManagerCallback); + AddAllowedValue(propertyName.c_str(), g_False); + AddAllowedValue(propertyName.c_str(), g_True); + break; + } + case VmbFeatureDataCommand: + { + err = CreateStringProperty(propertyName.c_str(), g_Command, false, uManagerCallback); + AddAllowedValue(propertyName.c_str(), g_Command); + AddAllowedValue(propertyName.c_str(), g_Execute); + break; } case VmbFeatureDataEnum: - case VmbFeatureDataString: { - err = CreateStringProperty(propertyName.c_str(), "", false, - uManagerCallback); - break; + case VmbFeatureDataString: + { + err = CreateStringProperty(propertyName.c_str(), "", false, uManagerCallback); + break; } - case VmbFeatureDataFloat: { - err = CreateFloatProperty(propertyName.c_str(), 0.0, false, - uManagerCallback); - break; + case VmbFeatureDataFloat: + { + err = CreateFloatProperty(propertyName.c_str(), 0.0, false, uManagerCallback); + break; } case VmbFeatureDataUnknown: case VmbFeatureDataRaw: case VmbFeatureDataNone: default: - // nothing - break; - } + // nothing + break; + } - if (err != VmbErrorSuccess) { - LOG_ERROR(err, - "Error while creating property " + std::string(feature->name)); - } + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while creating property " + std::string(feature->name)); + } - return err; + return err; } -const unsigned char* AlliedVisionCamera::GetImageBuffer() { - return reinterpret_cast(m_buffer[0]); +const unsigned char *AlliedVisionCamera::GetImageBuffer() +{ + return reinterpret_cast(m_buffer[0]); } -unsigned AlliedVisionCamera::GetImageWidth() const { - std::string value{}; - auto ret = getFeatureValue(g_Width, value); - if (ret != VmbErrorSuccess) { - LOG_ERROR(ret, "Error while getting image width!"); - return 0; - } +unsigned AlliedVisionCamera::GetImageWidth() const +{ + std::string value{}; + auto ret = getFeatureValue(g_Width, value); + if (ret != VmbErrorSuccess) + { + LOG_ERROR(ret, "Error while getting image width!"); + return 0; + } - return atoi(value.c_str()); + return atoi(value.c_str()); } -unsigned AlliedVisionCamera::GetImageHeight() const { - std::string value{}; - auto ret = getFeatureValue(g_Height, value); - if (ret != VmbErrorSuccess) { - LOG_ERROR(ret, "Error while getting image height!"); - return 0; - } +unsigned AlliedVisionCamera::GetImageHeight() const +{ + std::string value{}; + auto ret = getFeatureValue(g_Height, value); + if (ret != VmbErrorSuccess) + { + LOG_ERROR(ret, "Error while getting image height!"); + return 0; + } - return atoi(value.c_str()); + return atoi(value.c_str()); } -unsigned AlliedVisionCamera::GetImageBytesPerPixel() const { - return m_currentPixelFormat.getBytesPerPixel(); +unsigned AlliedVisionCamera::GetImageBytesPerPixel() const +{ + return m_currentPixelFormat.getBytesPerPixel(); } -long AlliedVisionCamera::GetImageBufferSize() const { return m_bufferSize; } +long AlliedVisionCamera::GetImageBufferSize() const +{ + return m_bufferSize; +} -unsigned AlliedVisionCamera::GetBitDepth() const { - return m_currentPixelFormat.getBitDepth(); +unsigned AlliedVisionCamera::GetBitDepth() const +{ + return m_currentPixelFormat.getBitDepth(); } -unsigned AlliedVisionCamera::GetNumberOfComponents() const { - return m_currentPixelFormat.getNumberOfComponents(); +unsigned AlliedVisionCamera::GetNumberOfComponents() const +{ + return m_currentPixelFormat.getNumberOfComponents(); } -int AlliedVisionCamera::GetBinning() const { - // Binning not supported. We support BinningVertical/Horizontal - return 1; +int AlliedVisionCamera::GetBinning() const +{ + // Binning not supported. We support BinningVertical/Horizontal + return 1; } -int AlliedVisionCamera::SetBinning(int binSize) { - // Binning not supported. We support BinningVertical/Horizontal - return DEVICE_ERR; +int AlliedVisionCamera::SetBinning(int binSize) +{ + // Binning not supported. We support BinningVertical/Horizontal + return DEVICE_ERR; } -double AlliedVisionCamera::GetExposure() const { - std::string value{}; - auto ret = getFeatureValue(m_exposureFeatureName.c_str(), value); - if (ret != VmbErrorSuccess) { - LOG_ERROR(ret, "Error while getting exposure!"); - return 0; - } +double AlliedVisionCamera::GetExposure() const +{ + std::string value{}; + auto ret = getFeatureValue(m_exposureFeatureName.c_str(), value); + if (ret != VmbErrorSuccess) + { + LOG_ERROR(ret, "Error while getting exposure!"); + return 0; + } - return std::stod(value) / MS_TO_US; + return std::stod(value) / MS_TO_US; } -void AlliedVisionCamera::SetExposure(double exp_ms) { - SetProperty(m_exposureFeatureName.c_str(), CDeviceUtils::ConvertToString(exp_ms * MS_TO_US)); - GetCoreCallback()->OnExposureChanged(this, exp_ms); +void AlliedVisionCamera::SetExposure(double exp_ms) +{ + SetProperty(m_exposureFeatureName.c_str(), CDeviceUtils::ConvertToString(exp_ms * MS_TO_US)); + GetCoreCallback()->OnExposureChanged(this, exp_ms); } -int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, - unsigned ySize) { - auto width = GetImageWidth(); - auto height = GetImageHeight(); - VmbError_t err = VmbErrorSuccess; +int AlliedVisionCamera::SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) +{ + auto width = GetImageWidth(); + auto height = GetImageHeight(); + VmbError_t err = VmbErrorSuccess; - std::function setOffsetXProperty = [this](long x) { - auto err = SetProperty(g_OffsetX, CDeviceUtils::ConvertToString(x)); - if (err) { - LOG_ERROR(err, "Error while ROI Offset X!"); + std::function setOffsetXProperty = [this](long x) + { + auto err = SetProperty(g_OffsetX, CDeviceUtils::ConvertToString(x)); + if (err) + { + LOG_ERROR(err, "Error while ROI Offset X!"); + } + return err; + }; + std::function setOffsetYProperty = [this](long y) + { + auto err = SetProperty(g_OffsetY, CDeviceUtils::ConvertToString(y)); + if (err) + { + LOG_ERROR(err, "Error while ROI Offset Y!"); + } + return err; + }; + std::function setWidthProperty = [this](long xSize) + { + auto err = SetProperty(g_Width, CDeviceUtils::ConvertToString(xSize)); + if (err) + { + LOG_ERROR(err, "Error while ROI X!"); + } + return err; + }; + std::function setHeightProperty = [this](long ySize) + { + auto err = SetProperty(g_Height, CDeviceUtils::ConvertToString(ySize)); + if (err) + { + LOG_ERROR(err, "Error while ROI Y!"); + } + return err; + }; + + if (xSize > width) + { + err = setOffsetXProperty(x) || setWidthProperty(xSize); } - return err; - }; - std::function setOffsetYProperty = [this](long y) { - auto err = SetProperty(g_OffsetY, CDeviceUtils::ConvertToString(y)); - if (err) { - LOG_ERROR(err, "Error while ROI Offset Y!"); + else + { + err = setWidthProperty(xSize) || setOffsetXProperty(x); } - return err; - }; - std::function setWidthProperty = [this](long xSize) { - auto err = SetProperty(g_Width, CDeviceUtils::ConvertToString(xSize)); - if (err) { - LOG_ERROR(err, "Error while ROI X!"); + + if (ySize > height) + { + err = setOffsetYProperty(y) || setHeightProperty(ySize); } - return err; - }; - std::function setHeightProperty = [this](long ySize) { - auto err = SetProperty(g_Height, CDeviceUtils::ConvertToString(ySize)); - if (err) { - LOG_ERROR(err, "Error while ROI Y!"); + else + { + err = setHeightProperty(ySize) || setOffsetYProperty(y); } - return err; - }; - - if (xSize > width) { - err = setOffsetXProperty(x) || setWidthProperty(xSize); - } else { - err = setWidthProperty(xSize) || setOffsetXProperty(x); - } - if (ySize > height) { - err = setOffsetYProperty(y) || setHeightProperty(ySize); - } else { - err = setHeightProperty(ySize) || setOffsetYProperty(y); - } - - if (err != VmbErrorSuccess) { - return err; - } + if (err != VmbErrorSuccess) + { + return err; + } - return resizeImageBuffer(); + return resizeImageBuffer(); } -int AlliedVisionCamera::GetROI(unsigned& x, unsigned& y, unsigned& xSize, - unsigned& ySize) { - std::map fields = { - {g_OffsetX, x}, {g_OffsetY, y}, {g_Width, xSize}, {g_Height, ySize}}; +int AlliedVisionCamera::GetROI(unsigned &x, unsigned &y, unsigned &xSize, unsigned &ySize) +{ + std::map fields = { { g_OffsetX, x }, { g_OffsetY, y }, { g_Width, xSize }, { g_Height, ySize } }; - VmbError_t err = VmbErrorSuccess; - for (auto& field : fields) { - std::string value{}; - err = getFeatureValue(field.first, value); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while getting ROI!"); - break; + VmbError_t err = VmbErrorSuccess; + for (auto &field : fields) + { + std::string value{}; + err = getFeatureValue(field.first, value); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while getting ROI!"); + break; + } + field.second = atoi(value.data()); } - field.second = atoi(value.data()); - } - return err; + return err; } -int AlliedVisionCamera::ClearROI() { - std::string maxWidth, maxHeight; - VmbError_t err = getFeatureValue(g_WidthMax, maxWidth) | - getFeatureValue(g_HeightMax, maxHeight); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while clearing ROI!"); - return err; - } +int AlliedVisionCamera::ClearROI() +{ + std::string maxWidth, maxHeight; + VmbError_t err = getFeatureValue(g_WidthMax, maxWidth) | getFeatureValue(g_HeightMax, maxHeight); + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while clearing ROI!"); + return err; + } - // Keep the order of the fields - std::vector> fields = { - {g_OffsetX, "0"}, - {g_OffsetY, "0"}, - {g_Width, maxWidth}, - {g_Height, maxHeight}}; + // Keep the order of the fields + std::vector> fields = { + { g_OffsetX, "0" }, { g_OffsetY, "0" }, { g_Width, maxWidth }, { g_Height, maxHeight } + }; - for (auto& field : fields) { - err = setFeatureValue(field.first, field.second); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while clearing ROI!"); - break; + for (auto &field : fields) + { + err = setFeatureValue(field.first, field.second); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while clearing ROI!"); + break; + } } - } - return resizeImageBuffer(); + return resizeImageBuffer(); } -int AlliedVisionCamera::IsExposureSequenceable(bool& isSequenceable) const { - // TODO implement - return VmbErrorSuccess; +int AlliedVisionCamera::IsExposureSequenceable(bool &isSequenceable) const +{ + // TODO implement + return VmbErrorSuccess; } -void AlliedVisionCamera::GetName(char* name) const { - CDeviceUtils::CopyLimitedString(name, m_cameraName.c_str()); +void AlliedVisionCamera::GetName(char *name) const +{ + CDeviceUtils::CopyLimitedString(name, m_cameraName.c_str()); +} + +bool AlliedVisionCamera::IsCapturing() +{ + return m_isAcquisitionRunning; } -bool AlliedVisionCamera::IsCapturing() { return m_isAcquisitionRunning; } - -int AlliedVisionCamera::onProperty(MM::PropertyBase* pProp, - MM::ActionType eAct) { - //Set locale to current system locale - const auto oldLocale = std::setlocale(LC_ALL, ""); - - const auto res = [&] { - // Init - VmbError_t err = VmbErrorSuccess; - - auto pChildProperty = dynamic_cast(pProp); - if (pChildProperty == nullptr) { - err = VmbErrorBadParameter; - LOG_ERROR(err, "Could not get Property from PropertyBase object"); - return err; - } - - const auto propertyName = pProp->GetName(); - - // Check property mapping - auto const featureName = mapPropertyNameToFeatureName(propertyName.c_str()); - - // Get Feature Info and Access Mode - VmbFeatureInfo_t featureInfo; - bool rMode{}, wMode{}; - err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), - &featureInfo, sizeof(featureInfo)) | - m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, - &wMode); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting info or access query!"); - return err; - } - - const bool readOnly = (rMode && !wMode); - const bool featureAvailable = (rMode || wMode); - - // Get values - std::string propertyValue{}, featureValue{}; - pProp->Get(propertyValue); - - // Handle property value change - switch (eAct) { - case MM::ActionType::BeforeGet: //!< Update property from feature - - // Update feature range - if (featureAvailable) - { - err = setAllowedValues(&featureInfo, propertyName.c_str()); - } - // Feature not available -> clear value and range - else { - switch (pProp->GetType()) { - case MM::Float: - case MM::Integer: - err = SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); - pProp->Set("0"); - break; - case MM::String: - ClearAllowedValues(propertyName.c_str()); - pProp->Set(""); - break; - default: - // feature type not supported - break; - } - } - - if (rMode) { - err = - getFeatureValue(&featureInfo, featureName.c_str(), featureValue); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting feature value " + featureName); - return err; - } - - // Update property - if (propertyValue != featureValue) { - pProp->Set(featureValue.c_str()); - err = GetCoreCallback()->OnPropertyChanged( - this, propertyName.c_str(), featureValue.c_str()); - if (propertyName == MM::g_Keyword_PixelType) { - handlePixelFormatChange(featureValue); - } - - if (VmbErrorSuccess != err) { - LOG_ERROR(err, - "Error while calling OnPropertyChanged callback for " + - featureName); - return err; - } - } - } - - // Set property to readonly (grey out in GUI) if it is readonly or unavailable - pChildProperty->SetReadOnly(readOnly || !featureAvailable); - - break; - case MM::ActionType::AfterSet: //!< Update feature from property - err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); - if (err == VmbErrorInvalidValue) { - // Update limits first to have latest min and max - err = setAllowedValues(&featureInfo, propertyName.c_str()); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while setting allowed values for feature " + - featureName); - return err; - } - - // Adjust value - double min{}, max{}; - err = GetPropertyLowerLimit(propertyName.c_str(), min) | - GetPropertyUpperLimit(propertyName.c_str(), max); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting limits for " + propertyName); - return err; - } - std::string adjustedValue = - adjustValue(featureInfo, min, max, std::stod(propertyValue)); - err = - setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); - } - - if (propertyName == MM::g_Keyword_PixelType) { - handlePixelFormatChange(propertyValue); - } - break; - default: - // nothing - break; - } - - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while updating property " + propertyName); - } - - - return err; - }(); - - std::setlocale(LC_ALL, oldLocale); - - return res; +int AlliedVisionCamera::onProperty(MM::PropertyBase *pProp, MM::ActionType eAct) +{ + // Set locale to current system locale + const auto oldLocale = std::setlocale(LC_ALL, ""); + + const auto res = [&] + { + // Init + VmbError_t err = VmbErrorSuccess; + + auto pChildProperty = dynamic_cast(pProp); + if (pChildProperty == nullptr) + { + err = VmbErrorBadParameter; + LOG_ERROR(err, "Could not get Property from PropertyBase object"); + return err; + } + + const auto propertyName = pProp->GetName(); + + // Check property mapping + auto const featureName = mapPropertyNameToFeatureName(propertyName.c_str()); + + // Get Feature Info and Access Mode + VmbFeatureInfo_t featureInfo; + bool rMode{}, wMode{}; + err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), &featureInfo, sizeof(featureInfo)) | + m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, &wMode); + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while getting info or access query!"); + return err; + } + + const bool readOnly = (rMode && !wMode); + const bool featureAvailable = (rMode || wMode); + + // Get values + std::string propertyValue{}, featureValue{}; + pProp->Get(propertyValue); + + // Handle property value change + switch (eAct) + { + case MM::ActionType::BeforeGet: //!< Update property from feature + + // Update feature range + if (featureAvailable) + { + err = setAllowedValues(&featureInfo, propertyName.c_str()); + } + // Feature not available -> clear value and range + else + { + switch (pProp->GetType()) + { + case MM::Float: + case MM::Integer: + err = SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); + pProp->Set("0"); + break; + case MM::String: + ClearAllowedValues(propertyName.c_str()); + pProp->Set(""); + break; + default: + // feature type not supported + break; + } + } + + if (rMode) + { + err = getFeatureValue(&featureInfo, featureName.c_str(), featureValue); + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while getting feature value " + featureName); + return err; + } + + // Update property + if (propertyValue != featureValue) + { + pProp->Set(featureValue.c_str()); + err = GetCoreCallback()->OnPropertyChanged(this, propertyName.c_str(), featureValue.c_str()); + if (propertyName == MM::g_Keyword_PixelType) + { + handlePixelFormatChange(featureValue); + } + + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while calling OnPropertyChanged callback for " + featureName); + return err; + } + } + } + + // Set property to readonly (grey out in GUI) if it is readonly or unavailable + pChildProperty->SetReadOnly(readOnly || !featureAvailable); + + break; + case MM::ActionType::AfterSet: //!< Update feature from property + err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); + if (err == VmbErrorInvalidValue) + { + // Update limits first to have latest min and max + err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while setting allowed values for feature " + featureName); + return err; + } + + // Adjust value + double min{}, max{}; + err = GetPropertyLowerLimit(propertyName.c_str(), min) | GetPropertyUpperLimit(propertyName.c_str(), max); + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while getting limits for " + propertyName); + return err; + } + std::string adjustedValue = adjustValue(featureInfo, min, max, std::stod(propertyValue)); + err = setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); + } + + if (propertyName == MM::g_Keyword_PixelType) + { + handlePixelFormatChange(propertyValue); + } + break; + default: + // nothing + break; + } + + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while updating property " + propertyName); + } + + return err; + }(); + + std::setlocale(LC_ALL, oldLocale); + + return res; } -void AlliedVisionCamera::handlePixelFormatChange(const std::string& pixelType) { - m_currentPixelFormat.setPixelType(pixelType); +void AlliedVisionCamera::handlePixelFormatChange(const std::string &pixelType) +{ + m_currentPixelFormat.setPixelType(pixelType); } -VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t* featureInfo, - const char* featureName, - std::string& value) const { - VmbError_t err = VmbErrorSuccess; - switch (featureInfo->featureDataType) { - case VmbFeatureDataBool: { - VmbBool_t out; - err = m_sdk->VmbFeatureBoolGet_t(m_handle, featureName, &out); - if (err != VmbErrorSuccess) { +VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t *featureInfo, const char *featureName, std::string &value) const +{ + VmbError_t err = VmbErrorSuccess; + switch (featureInfo->featureDataType) + { + case VmbFeatureDataBool: + { + VmbBool_t out; + err = m_sdk->VmbFeatureBoolGet_t(m_handle, featureName, &out); + if (err != VmbErrorSuccess) + { + break; + } + value = (out ? g_True : g_False); break; - } - value = (out ? g_True : g_False); - break; - } - case VmbFeatureDataEnum: { - const char* out = nullptr; - err = m_sdk->VmbFeatureEnumGet_t(m_handle, featureName, &out); - if (err != VmbErrorSuccess) { + } + case VmbFeatureDataEnum: + { + const char *out = nullptr; + err = m_sdk->VmbFeatureEnumGet_t(m_handle, featureName, &out); + if (err != VmbErrorSuccess) + { + break; + } + value = std::string(out); break; - } - value = std::string(out); - break; - } - case VmbFeatureDataFloat: { - double out; - err = m_sdk->VmbFeatureFloatGet_t(m_handle, featureName, &out); - if (err != VmbErrorSuccess) { + } + case VmbFeatureDataFloat: + { + double out; + err = m_sdk->VmbFeatureFloatGet_t(m_handle, featureName, &out); + if (err != VmbErrorSuccess) + { + break; + } + value = std::to_string(out); break; - } - value = std::to_string(out); - break; - } - case VmbFeatureDataInt: { - VmbInt64_t out; - err = m_sdk->VmbFeatureIntGet_t(m_handle, featureName, &out); - if (err != VmbErrorSuccess) { + } + case VmbFeatureDataInt: + { + VmbInt64_t out; + err = m_sdk->VmbFeatureIntGet_t(m_handle, featureName, &out); + if (err != VmbErrorSuccess) + { + break; + } + value = std::to_string(out); break; - } - value = std::to_string(out); - break; - } - case VmbFeatureDataString: { - VmbUint32_t size = 0; - err = m_sdk->VmbFeatureStringGet_t(m_handle, featureName, nullptr, 0, - &size); - if (VmbErrorSuccess == err && size > 0) { - std::shared_ptr buff = std::shared_ptr(new char[size]); - err = m_sdk->VmbFeatureStringGet_t(m_handle, featureName, buff.get(), - size, &size); - if (err != VmbErrorSuccess) { - break; + } + case VmbFeatureDataString: + { + VmbUint32_t size = 0; + err = m_sdk->VmbFeatureStringGet_t(m_handle, featureName, nullptr, 0, &size); + if (VmbErrorSuccess == err && size > 0) + { + std::shared_ptr buff = std::shared_ptr(new char[size]); + err = m_sdk->VmbFeatureStringGet_t(m_handle, featureName, buff.get(), size, &size); + if (err != VmbErrorSuccess) + { + break; + } + value = std::string(buff.get()); } - value = std::string(buff.get()); - } - break; + break; } case VmbFeatureDataCommand: - value = std::string(g_Command); - break; + value = std::string(g_Command); + break; case VmbFeatureDataUnknown: case VmbFeatureDataRaw: case VmbFeatureDataNone: default: - // nothing - break; - } + // nothing + break; + } - if (VmbErrorSuccess != err) { - LOG_ERROR(err, - "Error while getting feature value " + std::string(featureName)); - } + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while getting feature value " + std::string(featureName)); + } - return err; + return err; } -VmbError_t AlliedVisionCamera::getFeatureValue(const char* featureName, - std::string& value) const { - VmbFeatureInfo_t featureInfo; - VmbError_t err = m_sdk->VmbFeatureInfoQuery_t( - m_handle, featureName, &featureInfo, sizeof(featureInfo)); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, - "Error while getting feature value " + std::string(featureName)); - return err; - } +VmbError_t AlliedVisionCamera::getFeatureValue(const char *featureName, std::string &value) const +{ + VmbFeatureInfo_t featureInfo; + VmbError_t err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName, &featureInfo, sizeof(featureInfo)); + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while getting feature value " + std::string(featureName)); + return err; + } - return getFeatureValue(&featureInfo, featureName, value); + return getFeatureValue(&featureInfo, featureName, value); } -VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t* featureInfo, - const char* featureName, - std::string& value) { - VmbError_t err = VmbErrorSuccess; - std::stringstream ss(value); - bool isDone = false; - VmbUint32_t maxLen = 0; - - switch (featureInfo->featureDataType) { - case VmbFeatureDataBool: { - VmbBool_t out = (value == g_True ? true : false); - err = m_sdk->VmbFeatureBoolSet_t(m_handle, featureName, out); - break; - } - case VmbFeatureDataEnum: { - err = m_sdk->VmbFeatureEnumSet_t(m_handle, featureName, value.c_str()); - break; - } - case VmbFeatureDataFloat: { - double out; - ss >> out; - err = m_sdk->VmbFeatureFloatSet_t(m_handle, featureName, out); - break; - } - case VmbFeatureDataInt: { - VmbInt64_t out; - ss >> out; - err = m_sdk->VmbFeatureIntSet_t(m_handle, featureName, out); - break; - } - case VmbFeatureDataString: { - err = m_sdk->VmbFeatureStringMaxlengthQuery_t(m_handle, featureName, - &maxLen); - if (err != VmbErrorSuccess) { +VmbError_t AlliedVisionCamera::setFeatureValue(VmbFeatureInfo_t *featureInfo, const char *featureName, std::string &value) +{ + VmbError_t err = VmbErrorSuccess; + std::stringstream ss(value); + bool isDone = false; + VmbUint32_t maxLen = 0; + + switch (featureInfo->featureDataType) + { + case VmbFeatureDataBool: + { + VmbBool_t out = (value == g_True ? true : false); + err = m_sdk->VmbFeatureBoolSet_t(m_handle, featureName, out); break; - } - if (value.size() > maxLen) { - err = VmbErrorInvalidValue; - } else { - err = - m_sdk->VmbFeatureStringSet_t(m_handle, featureName, value.c_str()); - } - break; } - case VmbFeatureDataCommand: - if (value == g_Execute) { - const auto propertyName = mapFeatureNameToPropertyName(featureName); - if (!propertyName.empty()) { - err = m_sdk->VmbFeatureCommandRun_t(m_handle, featureName); - if (err != VmbErrorSuccess) { + case VmbFeatureDataEnum: + { + err = m_sdk->VmbFeatureEnumSet_t(m_handle, featureName, value.c_str()); + break; + } + case VmbFeatureDataFloat: + { + double out; + ss >> out; + err = m_sdk->VmbFeatureFloatSet_t(m_handle, featureName, out); + break; + } + case VmbFeatureDataInt: + { + VmbInt64_t out; + ss >> out; + err = m_sdk->VmbFeatureIntSet_t(m_handle, featureName, out); + break; + } + case VmbFeatureDataString: + { + err = m_sdk->VmbFeatureStringMaxlengthQuery_t(m_handle, featureName, &maxLen); + if (err != VmbErrorSuccess) + { break; - } - while (!isDone) { - err = m_sdk->VmbFeatureCommandIsDone_t(m_handle, featureName, - &isDone); - if (err != VmbErrorSuccess) { - break; + } + if (value.size() > maxLen) + { + err = VmbErrorInvalidValue; + } + else + { + err = m_sdk->VmbFeatureStringSet_t(m_handle, featureName, value.c_str()); + } + break; + } + case VmbFeatureDataCommand: + if (value == g_Execute) + { + const auto propertyName = mapFeatureNameToPropertyName(featureName); + if (!propertyName.empty()) + { + err = m_sdk->VmbFeatureCommandRun_t(m_handle, featureName); + if (err != VmbErrorSuccess) + { + break; + } + while (!isDone) + { + err = m_sdk->VmbFeatureCommandIsDone_t(m_handle, featureName, &isDone); + if (err != VmbErrorSuccess) + { + break; + } + } + // Set back property to "Command" + err = SetProperty(propertyName.c_str(), g_Command); + GetCoreCallback()->OnPropertyChanged(this, propertyName.c_str(), g_Command); + if (err != VmbErrorSuccess) + { + break; + } + } + else + { + err = VmbErrorInvalidValue; } - } - // Set back property to "Command" - err = SetProperty(propertyName.c_str(), g_Command); - GetCoreCallback()->OnPropertyChanged(this, propertyName.c_str(), - g_Command); - if (err != VmbErrorSuccess) { - break; - } - } else { - err = VmbErrorInvalidValue; } - } - break; + break; case VmbFeatureDataUnknown: case VmbFeatureDataRaw: case VmbFeatureDataNone: default: - // nothing - break; - } + // nothing + break; + } - if (VmbErrorSuccess != err) { - LOG_ERROR(err, - "Error while setting feature value " + std::string(featureName)); - } + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while setting feature value " + std::string(featureName)); + } - return err; + return err; } -VmbError_t AlliedVisionCamera::setFeatureValue(const char* featureName, - std::string& value) { - VmbFeatureInfo_t featureInfo; - VmbError_t err = m_sdk->VmbFeatureInfoQuery_t( - m_handle, featureName, &featureInfo, sizeof(featureInfo)); - if (VmbErrorSuccess != err) { - LOG_ERROR(err, - "Error while setting feature value " + std::string(featureName)); - return err; - } +VmbError_t AlliedVisionCamera::setFeatureValue(const char *featureName, std::string &value) +{ + VmbFeatureInfo_t featureInfo; + VmbError_t err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName, &featureInfo, sizeof(featureInfo)); + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while setting feature value " + std::string(featureName)); + return err; + } - return setFeatureValue(&featureInfo, featureName, value); + return setFeatureValue(&featureInfo, featureName, value); } -std::string AlliedVisionCamera::mapFeatureNameToPropertyName( - const char* feature) const { - - auto search = m_featureToProperty.find(feature); - if (search != m_featureToProperty.end()) { - return search->second; - } +std::string AlliedVisionCamera::mapFeatureNameToPropertyName(const char *feature) const +{ - return feature; + auto search = m_featureToProperty.find(feature); + if (search != m_featureToProperty.end()) + { + return search->second; + } + + return feature; } -std::string AlliedVisionCamera::mapPropertyNameToFeatureName(const char* property) const { +std::string AlliedVisionCamera::mapPropertyNameToFeatureName(const char *property) const +{ auto search = m_propertyToFeature.find(property); - if (search != m_propertyToFeature.end()) { + if (search != m_propertyToFeature.end()) + { return search->second; } return property; } -std::string AlliedVisionCamera::adjustValue(VmbFeatureInfo_t& featureInfo, - double min, double max, - double propertyValue) const { - VmbError_t err = VmbErrorSuccess; - double step = 1.0; - VmbInt64_t stepI = 1; - bool isIncremental = true; - switch (featureInfo.featureDataType) { +std::string AlliedVisionCamera::adjustValue(VmbFeatureInfo_t &featureInfo, double min, double max, double propertyValue) const +{ + VmbError_t err = VmbErrorSuccess; + double step = 1.0; + VmbInt64_t stepI = 1; + bool isIncremental = true; + switch (featureInfo.featureDataType) + { case VmbFeatureDataFloat: - err = m_sdk->VmbFeatureFloatIncrementQuery_t(m_handle, featureInfo.name, - &isIncremental, &step); - break; + err = m_sdk->VmbFeatureFloatIncrementQuery_t(m_handle, featureInfo.name, &isIncremental, &step); + break; case VmbFeatureDataInt: - err = m_sdk->VmbFeatureIntIncrementQuery_t(m_handle, featureInfo.name, - &stepI); - step = static_cast(stepI); - break; + err = m_sdk->VmbFeatureIntIncrementQuery_t(m_handle, featureInfo.name, &stepI); + step = static_cast(stepI); + break; default: - // nothing - break; - } - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting increment query for feature " + - std::string(featureInfo.name)); - return std::to_string(propertyValue); - } - - if (!isIncremental) { - return std::to_string(propertyValue); - } - - if (propertyValue > max) { - return std::to_string(max); - } - if (propertyValue < min) { - return std::to_string(min); - } - - VmbInt64_t factor = static_cast((propertyValue - min) / step); - double prev = min + factor * step; - double next = min + (factor + 1) * step; - - double prevDiff = abs(propertyValue - prev); - double nextDiff = abs(next - propertyValue); - - return (nextDiff < prevDiff) ? std::to_string(next) : std::to_string(prev); + // nothing + break; + } + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while getting increment query for feature " + std::string(featureInfo.name)); + return std::to_string(propertyValue); + } + + if (!isIncremental) + { + return std::to_string(propertyValue); + } + + if (propertyValue > max) + { + return std::to_string(max); + } + if (propertyValue < min) + { + return std::to_string(min); + } + + VmbInt64_t factor = static_cast((propertyValue - min) / step); + double prev = min + factor * step; + double next = min + (factor + 1) * step; + + double prevDiff = abs(propertyValue - prev); + double nextDiff = abs(next - propertyValue); + + return (nextDiff < prevDiff) ? std::to_string(next) : std::to_string(prev); } -VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t* feature, - const char* propertyName) { - if (feature == nullptr || propertyName == nullptr) { - return VmbErrorInvalidValue; - } - - VmbError_t err = VmbErrorSuccess; - - switch (feature->featureDataType) { - case VmbFeatureDataBool: { - // Already set in creation, but maybe reset when become unavailable - if (GetNumberOfPropertyValues(propertyName) == 0) { - AddAllowedValue(propertyName, g_False); - AddAllowedValue(propertyName, g_True); - } - break; - } - case VmbFeatureDataCommand: { - // Already set in creation, but maybe reset when become unavailable - if (GetNumberOfPropertyValues(propertyName) == 0) { - AddAllowedValue(propertyName, g_Command); - AddAllowedValue(propertyName, g_Execute); - } - break; - } - case VmbFeatureDataFloat: { - double min, max; - err = m_sdk->VmbFeatureFloatRangeQuery_t(m_handle, feature->name, &min, - &max); - if (VmbErrorSuccess != err || min == max) { +VmbError_t AlliedVisionCamera::setAllowedValues(const VmbFeatureInfo_t *feature, const char *propertyName) +{ + if (feature == nullptr || propertyName == nullptr) + { + return VmbErrorInvalidValue; + } + + VmbError_t err = VmbErrorSuccess; + + switch (feature->featureDataType) + { + case VmbFeatureDataBool: + { + // Already set in creation, but maybe reset when become unavailable + if (GetNumberOfPropertyValues(propertyName) == 0) + { + AddAllowedValue(propertyName, g_False); + AddAllowedValue(propertyName, g_True); + } break; - } - - err = SetPropertyLimits(propertyName, min, max); - break; - } - case VmbFeatureDataEnum: { - std::array values; - std::vector strValues; - VmbUint32_t valuesNum = 0; - err = m_sdk->VmbFeatureEnumRangeQuery_t( - m_handle, feature->name, values.data(), MM::MaxStrLength, &valuesNum); - if (VmbErrorSuccess != err) { + } + case VmbFeatureDataCommand: + { + // Already set in creation, but maybe reset when become unavailable + if (GetNumberOfPropertyValues(propertyName) == 0) + { + AddAllowedValue(propertyName, g_Command); + AddAllowedValue(propertyName, g_Execute); + } break; - } - - for (size_t i = 0; i < valuesNum; i++) { - strValues.push_back(values[i]); - } - err = SetAllowedValues(propertyName, strValues); + } + case VmbFeatureDataFloat: + { + double min, max; + err = m_sdk->VmbFeatureFloatRangeQuery_t(m_handle, feature->name, &min, &max); + if (VmbErrorSuccess != err || min == max) + { + break; + } - break; + err = SetPropertyLimits(propertyName, min, max); + break; } - case VmbFeatureDataInt: { - VmbInt64_t min, max; - std::vector strValues; + case VmbFeatureDataEnum: + { + std::array values; + std::vector strValues; + VmbUint32_t valuesNum = 0; + err = m_sdk->VmbFeatureEnumRangeQuery_t(m_handle, feature->name, values.data(), MM::MaxStrLength, &valuesNum); + if (VmbErrorSuccess != err) + { + break; + } + + for (size_t i = 0; i < valuesNum; i++) + { + strValues.push_back(values[i]); + } + err = SetAllowedValues(propertyName, strValues); - err = - m_sdk->VmbFeatureIntRangeQuery_t(m_handle, feature->name, &min, &max); - if (VmbErrorSuccess != err || min == max) { break; - } + } + case VmbFeatureDataInt: + { + VmbInt64_t min, max; + std::vector strValues; - err = SetPropertyLimits(propertyName, static_cast(min), - static_cast(max)); - break; + err = m_sdk->VmbFeatureIntRangeQuery_t(m_handle, feature->name, &min, &max); + if (VmbErrorSuccess != err || min == max) + { + break; + } + + err = SetPropertyLimits(propertyName, static_cast(min), static_cast(max)); + break; } case VmbFeatureDataString: case VmbFeatureDataRaw: case VmbFeatureDataNone: default: - // nothing - break; - } + // nothing + break; + } - if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while setting allowed values for feature " + - std::string(feature->name)); - } + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while setting allowed values for feature " + std::string(feature->name)); + } - return err; + return err; } -int AlliedVisionCamera::SnapImage() { - if (IsCapturing()) { - return DEVICE_CAMERA_BUSY_ACQUIRING; - } - resizeImageBuffer(); +int AlliedVisionCamera::SnapImage() +{ + if (IsCapturing()) + { + return DEVICE_CAMERA_BUSY_ACQUIRING; + } + resizeImageBuffer(); - VmbFrame_t frame; - frame.buffer = m_buffer[0]; - frame.bufferSize = m_payloadSize; + VmbFrame_t frame; + frame.buffer = m_buffer[0]; + frame.bufferSize = m_payloadSize; - VmbError_t err = VmbErrorSuccess; - err = m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while snapping image!"); - (void)StopSequenceAcquisition(); - return err; - } - err = m_sdk->VmbCaptureStart_t(m_handle); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while snapping image!"); - (void)StopSequenceAcquisition(); - return err; - } - err = m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while snapping image!"); - (void)StopSequenceAcquisition(); - return err; - } - err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while snapping image!"); - (void)StopSequenceAcquisition(); - return err; - } - m_isAcquisitionRunning = true; - err = m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while snapping image!"); - (void)StopSequenceAcquisition(); - return err; - } + VmbError_t err = VmbErrorSuccess; + err = m_sdk->VmbFrameAnnounce_t(m_handle, &frame, sizeof(VmbFrame_t)); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } + err = m_sdk->VmbCaptureStart_t(m_handle); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } + err = m_sdk->VmbCaptureFrameQueue_t(m_handle, &frame, nullptr); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } + err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } + m_isAcquisitionRunning = true; + err = m_sdk->VmbCaptureFrameWait_t(m_handle, &frame, 3000); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while snapping image!"); + (void)StopSequenceAcquisition(); + return err; + } + + err = transformImage(&frame); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while snapping image - cannot transform image!"); + (void)StopSequenceAcquisition(); + return err; + } - err = transformImage(&frame); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while snapping image - cannot transform image!"); (void)StopSequenceAcquisition(); return err; - } - - (void)StopSequenceAcquisition(); - return err; } -int AlliedVisionCamera::StartSequenceAcquisition(long numImages, - double interval_ms, - bool stopOnOverflow) { - (void)stopOnOverflow; - (void)interval_ms; - (void)numImages; - - if (IsCapturing()) { - return DEVICE_CAMERA_BUSY_ACQUIRING; - } +int AlliedVisionCamera::StartSequenceAcquisition(long numImages, double interval_ms, bool stopOnOverflow) +{ + (void)stopOnOverflow; + (void)interval_ms; + (void)numImages; - int err = GetCoreCallback()->PrepareForAcq(this); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error while preparing for acquisition!"); - return err; - } + if (IsCapturing()) + { + return DEVICE_CAMERA_BUSY_ACQUIRING; + } - err = resizeImageBuffer(); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error during frame praparation for continous acquisition!"); - return err; - } - - for (size_t i = 0; i < MAX_FRAMES; i++) { - m_frames[i].buffer = m_buffer[i]; - m_frames[i].bufferSize = m_payloadSize; - m_frames[i].context[0] = this; //(i); //(frame->context[0]) - ->insertFrame(frame); - }; + int err = GetCoreCallback()->PrepareForAcq(this); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error while preparing for acquisition!"); + return err; + } - err = - m_sdk->VmbFrameAnnounce_t(m_handle, &(m_frames[i]), sizeof(VmbFrame_t)); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, - "Error during frame praparation for continous acquisition!"); - return err; + err = resizeImageBuffer(); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error during frame praparation for continous acquisition!"); + return err; } - err = - m_sdk->VmbCaptureFrameQueue_t(m_handle, &(m_frames[i]), frameCallback); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, - "Error during frame praparation for continous acquisition!"); - return err; + for (size_t i = 0; i < MAX_FRAMES; i++) + { + m_frames[i].buffer = m_buffer[i]; + m_frames[i].bufferSize = m_payloadSize; + m_frames[i].context[0] = this; //(i); //(frame->context[0])->insertFrame(frame); + }; + + err = m_sdk->VmbFrameAnnounce_t(m_handle, &(m_frames[i]), sizeof(VmbFrame_t)); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error during frame praparation for continous acquisition!"); + return err; + } + + err = m_sdk->VmbCaptureFrameQueue_t(m_handle, &(m_frames[i]), frameCallback); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error during frame praparation for continous acquisition!"); + return err; + } } - } - err = m_sdk->VmbCaptureStart_t(m_handle); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error during frame praparation for continous acquisition!"); - return err; - } + err = m_sdk->VmbCaptureStart_t(m_handle); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error during frame praparation for continous acquisition!"); + return err; + } - err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); - m_isAcquisitionRunning = true; + err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStart); + m_isAcquisitionRunning = true; - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error during start acquisition!"); - return err; - } + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error during start acquisition!"); + return err; + } - return UpdateStatus(); + return UpdateStatus(); } -int AlliedVisionCamera::StartSequenceAcquisition(double interval_ms) { - return StartSequenceAcquisition(LONG_MAX, interval_ms, true); +int AlliedVisionCamera::StartSequenceAcquisition(double interval_ms) +{ + return StartSequenceAcquisition(LONG_MAX, interval_ms, true); } -int AlliedVisionCamera::StopSequenceAcquisition() { - // This method shall never return any error - VmbError_t err = VmbErrorSuccess; - if (IsCapturing()) { - err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStop); - m_isAcquisitionRunning = false; - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error during stopping acquisition command!"); - return err; - } - } - - err = m_sdk->VmbCaptureEnd_t(m_handle); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error during stop acquisition!"); - return err; - } - - err = m_sdk->VmbCaptureQueueFlush_t(m_handle); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error during stop acquisition!"); - return err; - } +int AlliedVisionCamera::StopSequenceAcquisition() +{ + // This method shall never return any error + VmbError_t err = VmbErrorSuccess; + if (IsCapturing()) + { + err = m_sdk->VmbFeatureCommandRun_t(m_handle, g_AcquisitionStop); + m_isAcquisitionRunning = false; + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error during stopping acquisition command!"); + return err; + } + } - err = m_sdk->VmbFrameRevokeAll_t(m_handle); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Error during stop acquisition!"); - return err; - } + err = m_sdk->VmbCaptureEnd_t(m_handle); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error during stop acquisition!"); + return err; + } - return UpdateStatus(); -} + err = m_sdk->VmbCaptureQueueFlush_t(m_handle); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error during stop acquisition!"); + return err; + } -VmbError_t AlliedVisionCamera::transformImage(VmbFrame_t* frame) { - VmbError_t err = VmbErrorSuccess; - VmbImage src{}, dest{}; - VmbTransformInfo info{}; - std::shared_ptr tempBuff = - std::shared_ptr(new VmbUint8_t[m_bufferSize]); - auto srcBuff = reinterpret_cast(frame->buffer); + err = m_sdk->VmbFrameRevokeAll_t(m_handle); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Error during stop acquisition!"); + return err; + } - src.Data = srcBuff; - src.Size = sizeof(src); + return UpdateStatus(); +} - dest.Data = tempBuff.get(); - dest.Size = sizeof(dest); +VmbError_t AlliedVisionCamera::transformImage(VmbFrame_t *frame) +{ + VmbError_t err = VmbErrorSuccess; + VmbImage src{}, dest{}; + VmbTransformInfo info{}; + std::shared_ptr tempBuff = std::shared_ptr(new VmbUint8_t[m_bufferSize]); + auto srcBuff = reinterpret_cast(frame->buffer); + + src.Data = srcBuff; + src.Size = sizeof(src); + + dest.Data = tempBuff.get(); + dest.Size = sizeof(dest); + + err = m_sdk->VmbSetImageInfoFromPixelFormat_t(frame->pixelFormat, frame->width, frame->height, &src); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Cannot set image info from pixel format!"); + return err; + } - err = m_sdk->VmbSetImageInfoFromPixelFormat_t( - frame->pixelFormat, frame->width, frame->height, &src); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Cannot set image info from pixel format!"); - return err; - } + err = m_sdk->VmbSetImageInfoFromPixelFormat_t(m_currentPixelFormat.getVmbFormat(), frame->width, frame->height, &dest); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Cannot set image info from pixel format!"); + return err; + } - err = m_sdk->VmbSetImageInfoFromPixelFormat_t( - m_currentPixelFormat.getVmbFormat(), frame->width, frame->height, &dest); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Cannot set image info from pixel format!"); - return err; - } + err = m_sdk->VmbImageTransform_t(&src, &dest, &info, 0); + if (err != VmbErrorSuccess) + { + LOG_ERROR(err, "Cannot transform image!"); + return err; + } - err = m_sdk->VmbImageTransform_t(&src, &dest, &info, 0); - if (err != VmbErrorSuccess) { - LOG_ERROR(err, "Cannot transform image!"); + memcpy(srcBuff, tempBuff.get(), m_bufferSize); return err; - } - - memcpy(srcBuff, tempBuff.get(), m_bufferSize); - return err; } -void AlliedVisionCamera::insertFrame(VmbFrame_t* frame) { - if (frame != nullptr && frame->receiveStatus == VmbFrameStatusComplete) { - VmbError_t err = VmbErrorSuccess; - - err = transformImage(frame); - if (err != VmbErrorSuccess) { - // Error logged in transformImage - return; - } +void AlliedVisionCamera::insertFrame(VmbFrame_t *frame) +{ + if (frame != nullptr && frame->receiveStatus == VmbFrameStatusComplete) + { + VmbError_t err = VmbErrorSuccess; + + err = transformImage(frame); + if (err != VmbErrorSuccess) + { + // Error logged in transformImage + return; + } - // TODO implement metadata - Metadata md; - md.put("Camera", m_cameraName); + // TODO implement metadata + Metadata md; + md.put("Camera", m_cameraName); - VmbUint8_t* buffer = reinterpret_cast(frame->buffer); - err = GetCoreCallback()->InsertImage( - this, buffer, GetImageWidth(), GetImageHeight(), - m_currentPixelFormat.getBytesPerPixel(), - m_currentPixelFormat.getNumberOfComponents(), md.Serialize().c_str()); + VmbUint8_t *buffer = reinterpret_cast(frame->buffer); + err = GetCoreCallback()->InsertImage(this, buffer, GetImageWidth(), GetImageHeight(), m_currentPixelFormat.getBytesPerPixel(), + m_currentPixelFormat.getNumberOfComponents(), md.Serialize().c_str()); - if (err == DEVICE_BUFFER_OVERFLOW) { - GetCoreCallback()->ClearImageBuffer(this); - err = GetCoreCallback()->InsertImage( - this, buffer, GetImageWidth(), GetImageHeight(), - m_currentPixelFormat.getBytesPerPixel(), - m_currentPixelFormat.getNumberOfComponents(), md.Serialize().c_str(), - false); - } + if (err == DEVICE_BUFFER_OVERFLOW) + { + GetCoreCallback()->ClearImageBuffer(this); + err = GetCoreCallback()->InsertImage(this, buffer, GetImageWidth(), GetImageHeight(), m_currentPixelFormat.getBytesPerPixel(), + m_currentPixelFormat.getNumberOfComponents(), md.Serialize().c_str(), false); + } - if (IsCapturing()) { - m_sdk->VmbCaptureFrameQueue_t( - m_handle, frame, - [](const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, - VmbFrame_t* frame) { - (void)cameraHandle; - (void)streamHandle; - reinterpret_cast(frame->context[0]) - ->insertFrame(frame); - }); + if (IsCapturing()) + { + m_sdk->VmbCaptureFrameQueue_t(m_handle, frame, + [](const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, VmbFrame_t *frame) + { + (void)cameraHandle; + (void)streamHandle; + reinterpret_cast(frame->context[0])->insertFrame(frame); + }); + } } - } } \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index d34e5ffca..017042cd5 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -28,34 +28,33 @@ #include "DeviceBase.h" #include "Loader/LibLoader.h" - /////////////////////////////////////////////////////////////////////////////// // STATIC FEATURE NAMES (FROM VIMBA) /////////////////////////////////////////////////////////////////////////////// -static constexpr const char* g_PixelFormatFeature = "PixelFormat"; -static constexpr const char* g_ExposureFeature = "ExposureTime"; -static constexpr const char* g_ExposureAbsFeature = "ExposureTimeAbs"; -static constexpr const char* g_BinningHorizontalFeature = "BinningHorizontal"; -static constexpr const char* g_BinningVerticalFeature = "BinningVertical"; -static constexpr const char* g_Width = "Width"; -static constexpr const char* g_Height = "Height"; -static constexpr const char* g_OffsetX = "OffsetX"; -static constexpr const char* g_OffsetY = "OffsetY"; -static constexpr const char* g_WidthMax = "WidthMax"; -static constexpr const char* g_HeightMax = "HeightMax"; +static constexpr const char *g_PixelFormatFeature = "PixelFormat"; +static constexpr const char *g_ExposureFeature = "ExposureTime"; +static constexpr const char *g_ExposureAbsFeature = "ExposureTimeAbs"; +static constexpr const char *g_BinningHorizontalFeature = "BinningHorizontal"; +static constexpr const char *g_BinningVerticalFeature = "BinningVertical"; +static constexpr const char *g_Width = "Width"; +static constexpr const char *g_Height = "Height"; +static constexpr const char *g_OffsetX = "OffsetX"; +static constexpr const char *g_OffsetY = "OffsetY"; +static constexpr const char *g_WidthMax = "WidthMax"; +static constexpr const char *g_HeightMax = "HeightMax"; /////////////////////////////////////////////////////////////////////////////// // STATIC VARIABLES /////////////////////////////////////////////////////////////////////////////// -static constexpr const char* g_True = "True"; -static constexpr const char* g_False = "False"; -static constexpr const char* g_Execute = "Execute"; -static constexpr const char* g_Command = "Command"; -static constexpr const char* g_ChunkCategory = "ChunkDataControl"; -static constexpr const char* g_EventCategory = "EventControl"; -static constexpr const char* g_AcquisitionStart = "AcquisitionStart"; -static constexpr const char* g_AcquisitionStop = "AcquisitionStop"; -static constexpr const char* g_AcqusitionStatus = "AcqusitionStatus"; +static constexpr const char *g_True = "True"; +static constexpr const char *g_False = "False"; +static constexpr const char *g_Execute = "Execute"; +static constexpr const char *g_Command = "Command"; +static constexpr const char *g_ChunkCategory = "ChunkDataControl"; +static constexpr const char *g_EventCategory = "EventControl"; +static constexpr const char *g_AcquisitionStart = "AcquisitionStart"; +static constexpr const char *g_AcquisitionStop = "AcquisitionStop"; +static constexpr const char *g_AcqusitionStatus = "AcqusitionStatus"; static constexpr const double MS_TO_US = 1000.0; @@ -69,378 +68,402 @@ static constexpr const double MS_TO_US = 1000.0; * 16bit GRAY [no. component = 1] * 32bit RGB [no. component = 4] */ -class PixelFormatConverter { - /////////////////////////////////////////////////////////////////////////////// - // PUBLIC - /////////////////////////////////////////////////////////////////////////////// - public: - /** - * @brief Constructor - */ - PixelFormatConverter() - : m_pixelType{"Mono8"}, - m_components{1}, - m_bitDepth{8}, - m_bytesPerPixel{1}, - m_isMono{true}, - m_vmbFormat{VmbPixelFormatMono8} {} - - /** - * @brief Destructor - */ - virtual ~PixelFormatConverter() = default; - - /** - * @brief Setter for pixel type - * @param[in] type New pixel type (string value from VMB) - */ - void setPixelType(const std::string& type) { - m_pixelType = type; - updateFields(); - } - - /** - * @brief Getter to check if given pixel type is Mono or Color - * @return True if Mono, otherwise false - */ - bool isMono() const { return m_isMono; } - - /** - * @brief Getter for number of components for given pixel type - * uManager supports only 1 or 4 components - * @return Number of components - */ - unsigned getNumberOfComponents() const { return m_components; } - - /** - * @brief Getter for bit depth for given pixel type - * @return Number of bits for one pixel - */ - unsigned getBitDepth() const { return m_bitDepth; } - - /** - * @brief Getter for bytes per pixel - * @return Bytes per pixel - */ - unsigned getBytesPerPixel() const { return m_bytesPerPixel; } - - /** - * @brief Getter of destination VmbPixelFormat that fits into the one, that - * uManager supports. In general uManager supports three pixelFormats: - * 1. Mono8 - * 2. Mono16 - * 3. RGB32 - * These types fits into following VmbPixelTypes: - * 1. VmbPixelFormatMono8 - * 2. VmbPixelFormatMono16 - * 3. VmbPixelFormatBgra8 - * @return Destination VmbPixelFormat - */ - VmbPixelFormatType getVmbFormat() const { return m_vmbFormat; } - - /////////////////////////////////////////////////////////////////////////////// - // PRIVATE - /////////////////////////////////////////////////////////////////////////////// - private: - /** - * @brief Helper method to update info if given format is Mono or Color - */ - void updateMono() { - std::regex re("Mono(\\d+)"); - std::smatch m; - m_isMono = std::regex_search(m_pixelType, m, re); - } - - /** - * @brief Helper method to update number of components for given pixel type - * uManager supports only 1 or 4 components - */ - void updateNumberOfComponents() { m_components = m_isMono ? 1 : 4; } - - /** - * @brief Helper method to update bit depth for given pixel type - */ - void updateBitDepth() { - m_bitDepth = 8; - if (isMono()) { - std::regex re("Mono(\\d+)"); - std::smatch m; - std::regex_search(m_pixelType, m, re); - if (m.size() > 0) { - if (std::atoi(m[1].str().c_str()) == 16) { - // We do transformation to Mono16 only for Mono16, otherwise - // it will always be Mono8 - m_bitDepth = 16; - } else { - m_bitDepth = 8; +class PixelFormatConverter +{ + /////////////////////////////////////////////////////////////////////////////// + // PUBLIC + /////////////////////////////////////////////////////////////////////////////// +public: + /** + * @brief Constructor + */ + PixelFormatConverter() : + m_pixelType{ "Mono8" }, + m_components{ 1 }, + m_bitDepth{ 8 }, + m_bytesPerPixel{ 1 }, + m_isMono{ true }, + m_vmbFormat{ VmbPixelFormatMono8 } + { + } + + /** + * @brief Destructor + */ + virtual ~PixelFormatConverter() = default; + + /** + * @brief Setter for pixel type + * @param[in] type New pixel type (string value from VMB) + */ + void setPixelType(const std::string &type) + { + m_pixelType = type; + updateFields(); + } + + /** + * @brief Getter to check if given pixel type is Mono or Color + * @return True if Mono, otherwise false + */ + bool isMono() const + { + return m_isMono; + } + + /** + * @brief Getter for number of components for given pixel type + * uManager supports only 1 or 4 components + * @return Number of components + */ + unsigned getNumberOfComponents() const + { + return m_components; + } + + /** + * @brief Getter for bit depth for given pixel type + * @return Number of bits for one pixel + */ + unsigned getBitDepth() const + { + return m_bitDepth; + } + + /** + * @brief Getter for bytes per pixel + * @return Bytes per pixel + */ + unsigned getBytesPerPixel() const + { + return m_bytesPerPixel; + } + + /** + * @brief Getter of destination VmbPixelFormat that fits into the one, that + * uManager supports. In general uManager supports three pixelFormats: + * 1. Mono8 + * 2. Mono16 + * 3. RGB32 + * These types fits into following VmbPixelTypes: + * 1. VmbPixelFormatMono8 + * 2. VmbPixelFormatMono16 + * 3. VmbPixelFormatBgra8 + * @return Destination VmbPixelFormat + */ + VmbPixelFormatType getVmbFormat() const + { + return m_vmbFormat; + } + + /////////////////////////////////////////////////////////////////////////////// + // PRIVATE + /////////////////////////////////////////////////////////////////////////////// +private: + /** + * @brief Helper method to update info if given format is Mono or Color + */ + void updateMono() + { + std::regex re("Mono(\\d+)"); + std::smatch m; + m_isMono = std::regex_search(m_pixelType, m, re); + } + + /** + * @brief Helper method to update number of components for given pixel type + * uManager supports only 1 or 4 components + */ + void updateNumberOfComponents() + { + m_components = m_isMono ? 1 : 4; + } + + /** + * @brief Helper method to update bit depth for given pixel type + */ + void updateBitDepth() + { + m_bitDepth = 8; + if (isMono()) + { + std::regex re("Mono(\\d+)"); + std::smatch m; + std::regex_search(m_pixelType, m, re); + if (m.size() > 0) + { + if (std::atoi(m[1].str().c_str()) == 16) + { + // We do transformation to Mono16 only for Mono16, otherwise + // it will always be Mono8 + m_bitDepth = 16; + } + else + { + m_bitDepth = 8; + } + } + else + { + // ERROR + } + } + else + { + m_bitDepth = 32; + } + } + + /** + * @brief Helper method to update bytes per pixel + */ + void updateBytesPerPixel() + { + m_bytesPerPixel = m_bitDepth / 8; + } + + /** + * @brief Helper method to update destination VmbPixelFormatType + */ + void updateVmbFormat() + { + switch (m_bytesPerPixel) + { + case 1: + m_vmbFormat = VmbPixelFormatMono8; + break; + case 2: + m_vmbFormat = VmbPixelFormatMono16; + break; + case 4: + m_vmbFormat = VmbPixelFormatBgra8; // TODO check if this is a valid format + break; + default: + break; } - } else { - // ERROR - } - } else { - m_bitDepth = 32; } - } - - /** - * @brief Helper method to update bytes per pixel - */ - void updateBytesPerPixel() { m_bytesPerPixel = m_bitDepth / 8; } - - /** - * @brief Helper method to update destination VmbPixelFormatType - */ - void updateVmbFormat() { - switch (m_bytesPerPixel) { - case 1: - m_vmbFormat = VmbPixelFormatMono8; - break; - case 2: - m_vmbFormat = VmbPixelFormatMono16; - break; - case 4: - m_vmbFormat = - VmbPixelFormatBgra8; // TODO check if this is a valid format - break; - default: - break; + + /** + * @brief Helper method to update all required fields + */ + void updateFields() + { + // [IMPORTANT] Keep order of the calls + updateMono(); + updateNumberOfComponents(); + updateBitDepth(); + updateBytesPerPixel(); + updateVmbFormat(); } - } - - /** - * @brief Helper method to update all required fields - */ - void updateFields() { - // [IMPORTANT] Keep order of the calls - updateMono(); - updateNumberOfComponents(); - updateBitDepth(); - updateBytesPerPixel(); - updateVmbFormat(); - } - - std::string m_pixelType; //!< Pixel type (in string) - value from VMB - unsigned m_components; //!< Number of components - unsigned m_bitDepth; //, - AlliedVisionCamera> { - /////////////////////////////////////////////////////////////////////////////// - // PUBLIC - /////////////////////////////////////////////////////////////////////////////// - public: - /** - * @brief Contructor of Allied Vision Camera - * @param[in] deviceName Device name - */ - AlliedVisionCamera(const char* deviceName); - /** - * @brief Allied Vision Camera destructor - */ - virtual ~AlliedVisionCamera(); - - /////////////////////////////////////////////////////////////////////////////// - // uMANAGER API METHODS - /////////////////////////////////////////////////////////////////////////////// - int Initialize() override; - int Shutdown() override; - const unsigned char* GetImageBuffer() override; - unsigned GetImageWidth() const override; - unsigned GetImageHeight() const override; - unsigned GetImageBytesPerPixel() const override; - int SnapImage() override; - long GetImageBufferSize() const override; - unsigned GetBitDepth() const override; - int GetBinning() const override; - int SetBinning(int binSize) override; - void SetExposure(double exp_ms) override; - double GetExposure() const override; - int SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) override; - int GetROI(unsigned& x, unsigned& y, unsigned& xSize, - unsigned& ySize) override; - int ClearROI() override; - int IsExposureSequenceable(bool& isSequenceable) const override; - void GetName(char* name) const override; - int StartSequenceAcquisition(double interval_ms) override; - int StartSequenceAcquisition(long numImages, double interval_ms, - bool stopOnOverflow) override; - int StopSequenceAcquisition() override; - bool IsCapturing() override; - unsigned GetNumberOfComponents() const override; - - /////////////////////////////////////////////////////////////////////////////// - // uMANAGER CALLBACKS - /////////////////////////////////////////////////////////////////////////////// - int onProperty(MM::PropertyBase* pProp, - MM::ActionType eAct); //!<< General property callback - - /////////////////////////////////////////////////////////////////////////////// - // PRIVATE - /////////////////////////////////////////////////////////////////////////////// - private: - // Static variables - static constexpr const VmbUint8_t MAX_FRAMES = - 7; //!<< Max frame number in the buffer - - /** - * @brief Helper method to handle change of pixel type - * @param[in] pixelType New pixel type (as string) - */ - void handlePixelFormatChange(const std::string& pixelType); - - /** - * @brief Resize all buffers for image frames - * @return VmbError_t - */ - VmbError_t resizeImageBuffer(); - - /** - * @brief Setup uManager properties from Vimba features - * @return VmbError_t - */ - VmbError_t setupProperties(); - - /** - * @brief Helper method to create single uManager property from Vimba feature - * @param[in] feature Pointer to the Vimba feature - * @return VmbError_t - */ - VmbError_t createPropertyFromFeature(const VmbFeatureInfo_t* feature); - - /** - * @brief Helper method to set allowed values for given property, based on - * its feature type - * @param[in] feature Vimba feature name - * @param[in] propertyName uManager propery name (if differs from - * feature name) - * @return - */ - VmbError_t setAllowedValues(const VmbFeatureInfo_t* feature, - const char* propertyName); - - /** - * @brief Insert ready frame to the uManager - * @param[in] frame Pointer to the frame - */ - void insertFrame(VmbFrame_t* frame); - - /** - * @brief Method to get feature value, based on its type. Feature value is - * always a string type. - * @param[in] featureInfo Feature info object - * @param[in] featureName Feature name - * @param[out] value Value of feature, read from device - * @return VmbError_t - */ - VmbError_t getFeatureValue(VmbFeatureInfo_t* featureInfo, - const char* featureName, std::string& value) const; - /** - * @brief Method to get feature value, based on its type. Feature value is - * always a string type. - * @param[in] featureName Feature name - * @param[out] value Value of feature, read from device - * @return VmbError_t - */ - VmbError_t getFeatureValue(const char* featureName, std::string& value) const; - - /** - * @brief Method to set a feature value, bases on its type. Feature value is - * always a string type. - * @param[in] featureInfo Feature info object - * @param[in] featureName Feature name - * @param[in] value Value of feature to be set - * @return VmbError_t - */ - VmbError_t setFeatureValue(VmbFeatureInfo_t* featureInfo, - const char* featureName, std::string& value); - - /** - * @brief Method to set a feature value, bases on its type. Feature value is - * always a string type. - * @param[in] featureName Feature name - * @param[in] value Value of feature to be set - * @return VmbError_t - */ - VmbError_t setFeatureValue(const char* featureName, std::string& value); - - /** - * @brief Helper method to map feature name into property name of uManager - * @param[in] feature Vimba Feature name - * @return uManager property name - */ - std::string mapFeatureNameToPropertyName(const char* feature) const; - - /** - * @brief Helper method to map uManager property in Vimba feature or features - * name - * @param[in] property uManager property name - * @param featureNames Vimba feature or features name - */ - std::string mapPropertyNameToFeatureName(const char* property) const; - - /** - * @brief In case trying to set invalid value, adjust it to the closest with - * inceremntal step - - * @param[in] step Incremental step - - * @return Adjusted value resresented as a string - */ - - /** - * @brief In case trying to set invalid value, adjust it to the closest with - * inceremntal step - * @param[in] featureInfo Feature info object - * @param[in] min Minimum for given property - * @param[in] max Maximum for given property - * @param[in] propertyValue Value that was tried to be set - * @return Adjusted value resresented as a string - */ - std::string adjustValue(VmbFeatureInfo_t& featureInfo, double min, double max, - double propertyValue) const; - - /** - * @brief Internal method to transform image to the destination format that - * uManager supports (see \ref PixelFormatConverter) and replaces input frame - * with output (transformed) frame - * @param[in] frame Frame with image to transform from and into - * @return Error in case of failure - */ - VmbError_t transformImage(VmbFrame_t* frame); - - /////////////////////////////////////////////////////////////////////////////// - // MEMBERS - /////////////////////////////////////////////////////////////////////////////// - std::shared_ptr m_sdk; // m_frames; // - m_buffer; // - m_featureToProperty; //!< Map of features name into uManager properties - std::unordered_map - m_propertyToFeature; //!< Map of uManager properties into Vimba features - +class AlliedVisionCamera : public AlliedVisionDeviceBase, AlliedVisionCamera> +{ + /////////////////////////////////////////////////////////////////////////////// + // PUBLIC + /////////////////////////////////////////////////////////////////////////////// +public: + /** + * @brief Contructor of Allied Vision Camera + * @param[in] deviceName Device name + */ + AlliedVisionCamera(const char *deviceName); + /** + * @brief Allied Vision Camera destructor + */ + virtual ~AlliedVisionCamera(); + + /////////////////////////////////////////////////////////////////////////////// + // uMANAGER API METHODS + /////////////////////////////////////////////////////////////////////////////// + int Initialize() override; + int Shutdown() override; + const unsigned char *GetImageBuffer() override; + unsigned GetImageWidth() const override; + unsigned GetImageHeight() const override; + unsigned GetImageBytesPerPixel() const override; + int SnapImage() override; + long GetImageBufferSize() const override; + unsigned GetBitDepth() const override; + int GetBinning() const override; + int SetBinning(int binSize) override; + void SetExposure(double exp_ms) override; + double GetExposure() const override; + int SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) override; + int GetROI(unsigned &x, unsigned &y, unsigned &xSize, unsigned &ySize) override; + int ClearROI() override; + int IsExposureSequenceable(bool &isSequenceable) const override; + void GetName(char *name) const override; + int StartSequenceAcquisition(double interval_ms) override; + int StartSequenceAcquisition(long numImages, double interval_ms, bool stopOnOverflow) override; + int StopSequenceAcquisition() override; + bool IsCapturing() override; + unsigned GetNumberOfComponents() const override; + + /////////////////////////////////////////////////////////////////////////////// + // uMANAGER CALLBACKS + /////////////////////////////////////////////////////////////////////////////// + int onProperty(MM::PropertyBase *pProp, + MM::ActionType eAct); //!<< General property callback + + /////////////////////////////////////////////////////////////////////////////// + // PRIVATE + /////////////////////////////////////////////////////////////////////////////// +private: + // Static variables + static constexpr const VmbUint8_t MAX_FRAMES = 7; //!<< Max frame number in the buffer + + /** + * @brief Helper method to handle change of pixel type + * @param[in] pixelType New pixel type (as string) + */ + void handlePixelFormatChange(const std::string &pixelType); + + /** + * @brief Resize all buffers for image frames + * @return VmbError_t + */ + VmbError_t resizeImageBuffer(); + + /** + * @brief Setup uManager properties from Vimba features + * @return VmbError_t + */ + VmbError_t setupProperties(); + + /** + * @brief Helper method to create single uManager property from Vimba feature + * @param[in] feature Pointer to the Vimba feature + * @return VmbError_t + */ + VmbError_t createPropertyFromFeature(const VmbFeatureInfo_t *feature); + + /** + * @brief Helper method to set allowed values for given property, based on + * its feature type + * @param[in] feature Vimba feature name + * @param[in] propertyName uManager propery name (if differs from + * feature name) + * @return + */ + VmbError_t setAllowedValues(const VmbFeatureInfo_t *feature, const char *propertyName); + + /** + * @brief Insert ready frame to the uManager + * @param[in] frame Pointer to the frame + */ + void insertFrame(VmbFrame_t *frame); + + /** + * @brief Method to get feature value, based on its type. Feature value is + * always a string type. + * @param[in] featureInfo Feature info object + * @param[in] featureName Feature name + * @param[out] value Value of feature, read from device + * @return VmbError_t + */ + VmbError_t getFeatureValue(VmbFeatureInfo_t *featureInfo, const char *featureName, std::string &value) const; + /** + * @brief Method to get feature value, based on its type. Feature value is + * always a string type. + * @param[in] featureName Feature name + * @param[out] value Value of feature, read from device + * @return VmbError_t + */ + VmbError_t getFeatureValue(const char *featureName, std::string &value) const; + + /** + * @brief Method to set a feature value, bases on its type. Feature value is + * always a string type. + * @param[in] featureInfo Feature info object + * @param[in] featureName Feature name + * @param[in] value Value of feature to be set + * @return VmbError_t + */ + VmbError_t setFeatureValue(VmbFeatureInfo_t *featureInfo, const char *featureName, std::string &value); + + /** + * @brief Method to set a feature value, bases on its type. Feature value is + * always a string type. + * @param[in] featureName Feature name + * @param[in] value Value of feature to be set + * @return VmbError_t + */ + VmbError_t setFeatureValue(const char *featureName, std::string &value); + + /** + * @brief Helper method to map feature name into property name of uManager + * @param[in] feature Vimba Feature name + * @return uManager property name + */ + std::string mapFeatureNameToPropertyName(const char *feature) const; + + /** + * @brief Helper method to map uManager property in Vimba feature or features + * name + * @param[in] property uManager property name + * @param featureNames Vimba feature or features name + */ + std::string mapPropertyNameToFeatureName(const char *property) const; + + /** + * @brief In case trying to set invalid value, adjust it to the closest with + * inceremntal step + + * @param[in] step Incremental step + + * @return Adjusted value resresented as a string + */ + + /** + * @brief In case trying to set invalid value, adjust it to the closest with + * inceremntal step + * @param[in] featureInfo Feature info object + * @param[in] min Minimum for given property + * @param[in] max Maximum for given property + * @param[in] propertyValue Value that was tried to be set + * @return Adjusted value resresented as a string + */ + std::string adjustValue(VmbFeatureInfo_t &featureInfo, double min, double max, double propertyValue) const; + + /** + * @brief Internal method to transform image to the destination format that + * uManager supports (see \ref PixelFormatConverter) and replaces input frame + * with output (transformed) frame + * @param[in] frame Frame with image to transform from and into + * @return Error in case of failure + */ + VmbError_t transformImage(VmbFrame_t *frame); + + /////////////////////////////////////////////////////////////////////////////// + // MEMBERS + /////////////////////////////////////////////////////////////////////////////// + std::shared_ptr m_sdk; // m_frames; // m_buffer; // m_featureToProperty; //!< Map of features name into uManager properties + std::unordered_map m_propertyToFeature; //!< Map of uManager properties into Vimba features }; #endif diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.cpp index 64e692022..6e1430cb5 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.cpp @@ -1,3 +1 @@ #include "AlliedVisionDeviceBase.h" - - diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h index cccb06d13..642fa566e 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h @@ -27,71 +27,59 @@ /** * @brief Base class for Allied Vision devices */ -template -class AlliedVisionDeviceBase : public CDeviceBase { - /////////////////////////////////////////////////////////////////////////////// - // PUBLIC - /////////////////////////////////////////////////////////////////////////////// +template class AlliedVisionDeviceBase : public CDeviceBase +{ + /////////////////////////////////////////////////////////////////////////////// + // PUBLIC + /////////////////////////////////////////////////////////////////////////////// public: - /** - * @brief Constructor - */ - AlliedVisionDeviceBase() { - CDeviceBase::InitializeDefaultErrorMessages(); - setApiErrorMessages(); - }; + /** + * @brief Constructor + */ + AlliedVisionDeviceBase() + { + CDeviceBase::InitializeDefaultErrorMessages(); + setApiErrorMessages(); + }; - /** - * @brief Destructor - */ - virtual ~AlliedVisionDeviceBase() = default; + /** + * @brief Destructor + */ + virtual ~AlliedVisionDeviceBase() = default; - void logError(int error, std::string message, std::string function = "", - int line = 0) const { - std::string prefix = "[" + function + "():" + std::to_string(line) + "] "; - CDeviceBase::LogMessage(prefix + message); - CDeviceBase::LogMessageCode(error); - } + void logError(int error, std::string message, std::string function = "", int line = 0) const + { + std::string prefix = "[" + function + "():" + std::to_string(line) + "] "; + CDeviceBase::LogMessage(prefix + message); + CDeviceBase::LogMessageCode(error); + } - /////////////////////////////////////////////////////////////////////////////// - // PRIVATE - /////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////// + // PRIVATE + /////////////////////////////////////////////////////////////////////////////// private: - /** - * @brief Setup error messages for Vimba API - */ - void setApiErrorMessages() { - CDeviceBase::SetErrorText(VmbErrorApiNotStarted, - "Vimba X API not started"); - CDeviceBase::SetErrorText(VmbErrorNotFound, "Device cannot be found"); - CDeviceBase::SetErrorText(VmbErrorDeviceNotOpen, - "Device cannot be opened"); - CDeviceBase::SetErrorText(VmbErrorBadParameter, - "Invalid parameter passed to the function"); - CDeviceBase::SetErrorText(VmbErrorNotImplemented, - "Feature not implemented"); - CDeviceBase::SetErrorText(VmbErrorNotSupported, - "Feature not supported"); - CDeviceBase::SetErrorText(VmbErrorUnknown, "Unknown error"); - CDeviceBase::SetErrorText( - VmbErrorInvalidValue, - "The value is not valid: either out of bounds or not an " - "increment of the minimum"); - CDeviceBase::SetErrorText(VmbErrorBadHandle, - "Given device handle is not valid"); - CDeviceBase::SetErrorText( - VmbErrorInvalidAccess, - "Operation is invalid with the current access mode"); - CDeviceBase::SetErrorText(VmbErrorTimeout, "Timeout occured"); - CDeviceBase::SetErrorText(VmbErrorNotAvailable, - "Something is not available"); - CDeviceBase::SetErrorText(VmbErrorNotInitialized, - "Something is not initialized"); - CDeviceBase::SetErrorText(VmbErrorAlready, - "The operation has been already done"); - CDeviceBase::SetErrorText(VmbErrorFeaturesUnavailable, - "Feature is currently unavailable"); - } + /** + * @brief Setup error messages for Vimba API + */ + void setApiErrorMessages() + { + CDeviceBase::SetErrorText(VmbErrorApiNotStarted, "Vimba X API not started"); + CDeviceBase::SetErrorText(VmbErrorNotFound, "Device cannot be found"); + CDeviceBase::SetErrorText(VmbErrorDeviceNotOpen, "Device cannot be opened"); + CDeviceBase::SetErrorText(VmbErrorBadParameter, "Invalid parameter passed to the function"); + CDeviceBase::SetErrorText(VmbErrorNotImplemented, "Feature not implemented"); + CDeviceBase::SetErrorText(VmbErrorNotSupported, "Feature not supported"); + CDeviceBase::SetErrorText(VmbErrorUnknown, "Unknown error"); + CDeviceBase::SetErrorText(VmbErrorInvalidValue, "The value is not valid: either out of bounds or not an " + "increment of the minimum"); + CDeviceBase::SetErrorText(VmbErrorBadHandle, "Given device handle is not valid"); + CDeviceBase::SetErrorText(VmbErrorInvalidAccess, "Operation is invalid with the current access mode"); + CDeviceBase::SetErrorText(VmbErrorTimeout, "Timeout occured"); + CDeviceBase::SetErrorText(VmbErrorNotAvailable, "Something is not available"); + CDeviceBase::SetErrorText(VmbErrorNotInitialized, "Something is not initialized"); + CDeviceBase::SetErrorText(VmbErrorAlready, "The operation has been already done"); + CDeviceBase::SetErrorText(VmbErrorFeaturesUnavailable, "Feature is currently unavailable"); + } }; #endif diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp index 1400542d9..59276c8c6 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp @@ -20,55 +20,77 @@ #include "AlliedVisionCamera.h" -AlliedVisionHub::AlliedVisionHub() : m_sdk(std::make_shared()) {} +AlliedVisionHub::AlliedVisionHub() : + m_sdk(std::make_shared()) +{ +} -int AlliedVisionHub::DetectInstalledDevices() { - LogMessage("Detecting installed cameras..."); - VmbUint32_t camNum; - // Get the number of connected cameras first - VmbError_t err = m_sdk->VmbCamerasList_t(nullptr, 0, &camNum, 0); - if (VmbErrorSuccess == err) { - VmbCameraInfo_t *camInfo = new VmbCameraInfo_t[camNum]; +int AlliedVisionHub::DetectInstalledDevices() +{ + LogMessage("Detecting installed cameras..."); + VmbUint32_t camNum; + // Get the number of connected cameras first + VmbError_t err = m_sdk->VmbCamerasList_t(nullptr, 0, &camNum, 0); + if (VmbErrorSuccess == err) + { + VmbCameraInfo_t *camInfo = new VmbCameraInfo_t[camNum]; - // Get the cameras - err = m_sdk->VmbCamerasList_t(camInfo, camNum, &camNum, sizeof *camInfo); + // Get the cameras + err = m_sdk->VmbCamerasList_t(camInfo, camNum, &camNum, sizeof *camInfo); - if (err == VmbErrorSuccess) { - for (VmbUint32_t i = 0; i < camNum; ++i) { - if (camInfo[i].permittedAccess & VmbAccessModeFull) { - MM::Device *pDev = new AlliedVisionCamera(camInfo[i].cameraIdString); - AddInstalledDevice(pDev); + if (err == VmbErrorSuccess) + { + for (VmbUint32_t i = 0; i < camNum; ++i) + { + if (camInfo[i].permittedAccess & VmbAccessModeFull) + { + MM::Device *pDev = new AlliedVisionCamera(camInfo[i].cameraIdString); + AddInstalledDevice(pDev); + } + } } - } + + delete[] camInfo; + } + else + { + LOG_ERROR(err, "Cannot get installed devices!"); } - delete[] camInfo; - } else { - LOG_ERROR(err, "Cannot get installed devices!"); - } + return err; +} - return err; +int AlliedVisionHub::Initialize() +{ + LogMessage("Init HUB"); + if (m_sdk->isInitialized()) + { + return DEVICE_OK; + } + else + { + LOG_ERROR(VmbErrorApiNotStarted, "SDK not initialized!"); + return VmbErrorApiNotStarted; + } } -int AlliedVisionHub::Initialize() { - LogMessage("Init HUB"); - if (m_sdk->isInitialized()) { +int AlliedVisionHub::Shutdown() +{ + LogMessage("Shutting down HUB"); return DEVICE_OK; - } else { - LOG_ERROR(VmbErrorApiNotStarted, "SDK not initialized!"); - return VmbErrorApiNotStarted; - } } -int AlliedVisionHub::Shutdown() { - LogMessage("Shutting down HUB"); - return DEVICE_OK; +void AlliedVisionHub::GetName(char *name) const +{ + CDeviceUtils::CopyLimitedString(name, g_hubName); } -void AlliedVisionHub::GetName(char *name) const { - CDeviceUtils::CopyLimitedString(name, g_hubName); +bool AlliedVisionHub::Busy() +{ + return false; } -bool AlliedVisionHub::Busy() { return false; } - -std::shared_ptr &AlliedVisionHub::getSDK() { return m_sdk; } +std::shared_ptr &AlliedVisionHub::getSDK() +{ + return m_sdk; +} diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h index c0daf74cd..6ecc4a37a 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h @@ -32,42 +32,42 @@ static constexpr const char *g_hubName = "AlliedVisionHub"; /** * @brief Class that represents a HUB of supported devices */ -class AlliedVisionHub - : public AlliedVisionDeviceBase, AlliedVisionHub> { - /////////////////////////////////////////////////////////////////////////////// - // PUBLIC - /////////////////////////////////////////////////////////////////////////////// +class AlliedVisionHub : public AlliedVisionDeviceBase, AlliedVisionHub> +{ + /////////////////////////////////////////////////////////////////////////////// + // PUBLIC + /////////////////////////////////////////////////////////////////////////////// public: - /** - * @brief Contructor of a HUB - */ - AlliedVisionHub(); + /** + * @brief Contructor of a HUB + */ + AlliedVisionHub(); - /** - * @brief Destructor of a HUB - */ - virtual ~AlliedVisionHub() = default; + /** + * @brief Destructor of a HUB + */ + virtual ~AlliedVisionHub() = default; - /** - * @brief SDK getter - * @return Pointer to SDK - */ - std::shared_ptr &getSDK(); + /** + * @brief SDK getter + * @return Pointer to SDK + */ + std::shared_ptr &getSDK(); - /////////////////////////////////////////////////////////////////////////////// - // uMANAGER API METHODS - /////////////////////////////////////////////////////////////////////////////// - int DetectInstalledDevices() override; - int Initialize() override; - int Shutdown() override; - void GetName(char *name) const override; - bool Busy() override; + /////////////////////////////////////////////////////////////////////////////// + // uMANAGER API METHODS + /////////////////////////////////////////////////////////////////////////////// + int DetectInstalledDevices() override; + int Initialize() override; + int Shutdown() override; + void GetName(char *name) const override; + bool Busy() override; - /////////////////////////////////////////////////////////////////////////////// - // PRIVATE - /////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////// + // PRIVATE + /////////////////////////////////////////////////////////////////////////////// private: - std::shared_ptr m_sdk; // m_sdk; //(m_module)); - m_module = nullptr; - m_loaded = false; - m_libName = nullptr; - } +LibLoader::~LibLoader() +{ + if (m_loaded) + { + FreeModule(static_cast(m_module)); + m_module = nullptr; + m_loaded = false; + m_libName = nullptr; + } } -SymbolWrapper LibLoader::resolveFunction(const char *functionName, - bool &allResolved) const { - void *funPtr = nullptr; - if (allResolved) { - funPtr = GetProcAddress(static_cast(m_module), functionName); - allResolved = allResolved && (funPtr != nullptr); - } - return SymbolWrapper(funPtr); +SymbolWrapper LibLoader::resolveFunction(const char *functionName, bool &allResolved) const +{ + void *funPtr = nullptr; + if (allResolved) + { + funPtr = GetProcAddress(static_cast(m_module), functionName); + allResolved = allResolved && (funPtr != nullptr); + } + return SymbolWrapper(funPtr); } #else // nothing diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h index dcfdb78c7..675ff7230 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h +++ b/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h @@ -28,159 +28,159 @@ /** * @brief Wrapper for single Windows process */ -class SymbolWrapper { - /////////////////////////////////////////////////////////////////////////////// - // PUBLIC - /////////////////////////////////////////////////////////////////////////////// - public: - /** - * @brief Constructor of wrapper - * @param[in] funPtr Pointer to the resolved symbol - */ - explicit SymbolWrapper(void* funPtr) : m_funPtr(funPtr) {} - - /** - * @brief Operator () overload to call function - */ - template ::value>> - operator T*() const { - return reinterpret_cast(m_funPtr); - } - /////////////////////////////////////////////////////////////////////////////// - // PRIVATE - /////////////////////////////////////////////////////////////////////////////// - private: - void* m_funPtr; //::value>> operator T *() const + { + return reinterpret_cast(m_funPtr); + } + /////////////////////////////////////////////////////////////////////////////// + // PRIVATE + /////////////////////////////////////////////////////////////////////////////// +private: + void *m_funPtr; // Date: Thu, 14 Sep 2023 15:07:03 +0200 Subject: [PATCH 038/141] Added missing locale include --- DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index d736fc617..7d582e85b 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "AlliedVisionHub.h" #include "ModuleInterface.h" From 656266349582fd9a19b0d9bc9362b5817f037f3b Mon Sep 17 00:00:00 2001 From: Florian Klostermann Date: Thu, 14 Sep 2023 16:27:47 +0200 Subject: [PATCH 039/141] Run GVSPAdjustPacketSize after opening a GigE camera --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 23 ++++++++++++++++++- .../AlliedVisionCamera/AlliedVisionCamera.h | 3 +-- .../SDK/Loader/LibLoader.cpp | 1 + .../AlliedVisionCamera/SDK/Loader/LibLoader.h | 1 + 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 7d582e85b..ae7815b37 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -21,10 +21,10 @@ #include "AlliedVisionCamera.h" #include +#include #include #include #include -#include #include "AlliedVisionHub.h" #include "ModuleInterface.h" @@ -34,6 +34,8 @@ // STATIC VALUES /////////////////////////////////////////////////////////////////////////////// +constexpr const char *ADJUST_PACKAGE_SIZE_COMMAND = "GVSPAdjustPacketSize"; + const std::unordered_map AlliedVisionCamera::m_featureToProperty = { { g_PixelFormatFeature, MM::g_Keyword_PixelType } }; @@ -117,6 +119,25 @@ int AlliedVisionCamera::Initialize() return err; } + // Try to execute custom command available to Allied Vision GigE Cameras to ensure the packet size is chosen well + VmbCameraInfo_t info; + err = m_sdk->VmbCameraInfoQuery_t(m_cameraName.c_str(), &info, sizeof(info)); + if (err == VmbErrorSuccess && info.streamCount > 0) + { + VmbHandle_t stream = info.streamHandles[0]; + if (m_sdk->VmbFeatureCommandRun_t(stream, ADJUST_PACKAGE_SIZE_COMMAND) == VmbErrorSuccess) + { + VmbBool_t isCommandDone = VmbBoolFalse; + do + { + if (m_sdk->VmbFeatureCommandIsDone_t(stream, ADJUST_PACKAGE_SIZE_COMMAND, &isCommandDone) != VmbErrorSuccess) + { + break; + } + } while (isCommandDone == VmbBoolFalse); + } + } + // Ignore errors from setting up properties (void)setupProperties(); return resizeImageBuffer(); diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 017042cd5..052ca0852 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -456,8 +456,7 @@ class AlliedVisionCamera : public AlliedVisionDeviceBase Date: Thu, 14 Sep 2023 17:27:02 +0200 Subject: [PATCH 040/141] Ip and mac address features are now displayed correctly --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 47 ++++++++++++++++++- .../AlliedVisionCamera/AlliedVisionCamera.h | 4 ++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index ae7815b37..6a8dd90ae 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -38,6 +38,19 @@ constexpr const char *ADJUST_PACKAGE_SIZE_COMMAND = "GVSPAdjustPacketSize"; const std::unordered_map AlliedVisionCamera::m_featureToProperty = { { g_PixelFormatFeature, MM::g_Keyword_PixelType } }; +const std::unordered_set AlliedVisionCamera::m_ipAddressFeatures = { + "MulticastIPAddress", + "GevCurrentSubnetMask", + "GevCurrentIPAddress", + "GevCurrentDefaultGateway", + "GevPersistentIPAddress", + "GevPersistentDefaultGateway", + "GevPersistentSubnetMask", +}; + +const std::unordered_set AlliedVisionCamera::m_macAddressFeatures = { + "GevMACAddress" +}; /////////////////////////////////////////////////////////////////////////////// // Exported MMDevice API @@ -289,6 +302,12 @@ VmbError_t AlliedVisionCamera::createPropertyFromFeature(const VmbFeatureInfo_t return err; } + if (m_ipAddressFeatures.count(feature->name) || m_macAddressFeatures.count(feature->name)) + { + err = CreateStringProperty(propertyName.c_str(), "", true, uManagerCallback); + return err; + } + switch (feature->featureDataType) { case VmbFeatureDataInt: @@ -586,6 +605,10 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase *pProp, MM::ActionType eAct) return err; } + if (m_ipAddressFeatures.count(featureName)) { + wMode = false; + } + const bool readOnly = (rMode && !wMode); const bool featureAvailable = (rMode || wMode); @@ -752,7 +775,29 @@ VmbError_t AlliedVisionCamera::getFeatureValue(VmbFeatureInfo_t *featureInfo, co { break; } - value = std::to_string(out); + if (m_ipAddressFeatures.count(featureName)) + { + std::stringstream ipAddressStream; + ipAddressStream << (0xFF & (out >> 24)) << "." << (0xFF & (out >> 16)) << "." << (0xFF & (out >> 8)) << "." << (0xFF & out); + + value = ipAddressStream.str(); + } + else if (m_macAddressFeatures.count(featureName)) + { + std::stringstream macAddressStream; + macAddressStream << std::hex + << std::setw(2) << std::setfill('0') << static_cast(0xFF & (out >> 40)) << ":" + << std::setw(2) << std::setfill('0') << static_cast(0xFF & (out >> 32)) << ":" + << std::setw(2) << std::setfill('0') << static_cast(0xFF & (out >> 24)) << ":" + << std::setw(2) << std::setfill('0') << static_cast(0xFF & (out >> 16)) << ":" + << std::setw(2) << std::setfill('0') << static_cast(0xFF & (out >> 8)) << ":" + << std::setw(2) << std::setfill('0') << static_cast(0xFF & out); + value = macAddressStream.str(); + } + else + { + value = std::to_string(out); + } break; } case VmbFeatureDataString: diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 052ca0852..2dd14f1c1 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "AlliedVisionDeviceBase.h" #include "DeviceBase.h" @@ -463,6 +464,9 @@ class AlliedVisionCamera : public AlliedVisionDeviceBase m_featureToProperty; //!< Map of features name into uManager properties std::unordered_map m_propertyToFeature; //!< Map of uManager properties into Vimba features + + static const std::unordered_set m_ipAddressFeatures; + static const std::unordered_set m_macAddressFeatures; }; #endif From 16037a21c81fc78578aa3a63f5ae3095aae890fe Mon Sep 17 00:00:00 2001 From: Dennis Langenkamp Date: Thu, 14 Sep 2023 17:31:55 +0200 Subject: [PATCH 041/141] Fixed formatting --- DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 6a8dd90ae..cf3fc90fb 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -605,7 +605,8 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase *pProp, MM::ActionType eAct) return err; } - if (m_ipAddressFeatures.count(featureName)) { + if (m_ipAddressFeatures.count(featureName)) + { wMode = false; } From 40afa77caf6c0cf598779d389abfbb02a65deca0 Mon Sep 17 00:00:00 2001 From: Florian Klostermann Date: Thu, 14 Sep 2023 18:11:54 +0200 Subject: [PATCH 042/141] ignore return from SetPropertyLimits when disabling a feature --- DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index cf3fc90fb..52edeab0f 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -634,7 +634,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase *pProp, MM::ActionType eAct) { case MM::Float: case MM::Integer: - err = SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); + SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); pProp->Set("0"); break; case MM::String: From cfa656e57b4b35b42cf678b47ac9154568a3b4bd Mon Sep 17 00:00:00 2001 From: Dennis Langenkamp Date: Fri, 15 Sep 2023 15:45:30 +0200 Subject: [PATCH 043/141] Revert: Before a feature is read or written the locale is set to the current user locale. Reverted chaning the locale. OnExposureChanged is now called every time the exposure feature changes. --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 216 +++++++++--------- 1 file changed, 106 insertions(+), 110 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 52edeab0f..279cdbe00 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -573,156 +573,152 @@ bool AlliedVisionCamera::IsCapturing() int AlliedVisionCamera::onProperty(MM::PropertyBase *pProp, MM::ActionType eAct) { - // Set locale to current system locale - const auto oldLocale = std::setlocale(LC_ALL, ""); + // Init + VmbError_t err = VmbErrorSuccess; - const auto res = [&] + auto pChildProperty = dynamic_cast(pProp); + if (pChildProperty == nullptr) { - // Init - VmbError_t err = VmbErrorSuccess; + err = VmbErrorBadParameter; + LOG_ERROR(err, "Could not get Property from PropertyBase object"); + return err; + } - auto pChildProperty = dynamic_cast(pProp); - if (pChildProperty == nullptr) - { - err = VmbErrorBadParameter; - LOG_ERROR(err, "Could not get Property from PropertyBase object"); - return err; - } + const auto propertyName = pProp->GetName(); - const auto propertyName = pProp->GetName(); + // Check property mapping + auto const featureName = mapPropertyNameToFeatureName(propertyName.c_str()); - // Check property mapping - auto const featureName = mapPropertyNameToFeatureName(propertyName.c_str()); + // Get Feature Info and Access Mode + VmbFeatureInfo_t featureInfo; + bool rMode{}, wMode{}; + err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), &featureInfo, sizeof(featureInfo)) | + m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, &wMode); + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while getting info or access query!"); + return err; + } - // Get Feature Info and Access Mode - VmbFeatureInfo_t featureInfo; - bool rMode{}, wMode{}; - err = m_sdk->VmbFeatureInfoQuery_t(m_handle, featureName.c_str(), &featureInfo, sizeof(featureInfo)) | - m_sdk->VmbFeatureAccessQuery_t(m_handle, featureName.c_str(), &rMode, &wMode); - if (VmbErrorSuccess != err) - { - LOG_ERROR(err, "Error while getting info or access query!"); - return err; - } + if (m_ipAddressFeatures.count(featureName)) + { + wMode = false; + } - if (m_ipAddressFeatures.count(featureName)) - { - wMode = false; - } + const bool readOnly = (rMode && !wMode); + const bool featureAvailable = (rMode || wMode); - const bool readOnly = (rMode && !wMode); - const bool featureAvailable = (rMode || wMode); + // Get values + std::string propertyValue{}, featureValue{}; + pProp->Get(propertyValue); - // Get values - std::string propertyValue{}, featureValue{}; - pProp->Get(propertyValue); + // Handle property value change + switch (eAct) + { + case MM::ActionType::BeforeGet: //!< Update property from feature - // Handle property value change - switch (eAct) + // Update feature range + if (featureAvailable) { - case MM::ActionType::BeforeGet: //!< Update property from feature + err = setAllowedValues(&featureInfo, propertyName.c_str()); + } + // Feature not available -> clear value and range + else + { + switch (pProp->GetType()) + { + case MM::Float: + case MM::Integer: + SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); + pProp->Set("0"); + break; + case MM::String: + ClearAllowedValues(propertyName.c_str()); + pProp->Set(""); + break; + default: + // feature type not supported + break; + } + } - // Update feature range - if (featureAvailable) + if (rMode) + { + err = getFeatureValue(&featureInfo, featureName.c_str(), featureValue); + if (VmbErrorSuccess != err) { - err = setAllowedValues(&featureInfo, propertyName.c_str()); + LOG_ERROR(err, "Error while getting feature value " + featureName); + return err; } - // Feature not available -> clear value and range - else + + // Update property + if (propertyValue != featureValue) { - switch (pProp->GetType()) + pProp->Set(featureValue.c_str()); + err = GetCoreCallback()->OnPropertyChanged(this, propertyName.c_str(), featureValue.c_str()); + if (propertyName == MM::g_Keyword_PixelType) { - case MM::Float: - case MM::Integer: - SetPropertyLimits(propertyName.c_str(), 0.0, 0.0); - pProp->Set("0"); - break; - case MM::String: - ClearAllowedValues(propertyName.c_str()); - pProp->Set(""); - break; - default: - // feature type not supported - break; + handlePixelFormatChange(featureValue); } - } - if (rMode) - { - err = getFeatureValue(&featureInfo, featureName.c_str(), featureValue); if (VmbErrorSuccess != err) { - LOG_ERROR(err, "Error while getting feature value " + featureName); + LOG_ERROR(err, "Error while calling OnPropertyChanged callback for " + featureName); return err; } - // Update property - if (propertyValue != featureValue) + if (m_exposureFeatureName == featureName) { - pProp->Set(featureValue.c_str()); - err = GetCoreCallback()->OnPropertyChanged(this, propertyName.c_str(), featureValue.c_str()); - if (propertyName == MM::g_Keyword_PixelType) - { - handlePixelFormatChange(featureValue); - } - - if (VmbErrorSuccess != err) - { - LOG_ERROR(err, "Error while calling OnPropertyChanged callback for " + featureName); - return err; - } + GetCoreCallback()->OnExposureChanged(this, std::stod(featureValue) / MS_TO_US); } } + } - // Set property to readonly (grey out in GUI) if it is readonly or unavailable - pChildProperty->SetReadOnly(readOnly || !featureAvailable); + // Set property to readonly (grey out in GUI) if it is readonly or unavailable + pChildProperty->SetReadOnly(readOnly || !featureAvailable); - break; - case MM::ActionType::AfterSet: //!< Update feature from property - err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); - if (err == VmbErrorInvalidValue) + break; + case MM::ActionType::AfterSet: //!< Update feature from property + err = setFeatureValue(&featureInfo, featureName.c_str(), propertyValue); + if (err == VmbErrorInvalidValue) + { + // Update limits first to have latest min and max + err = setAllowedValues(&featureInfo, propertyName.c_str()); + if (VmbErrorSuccess != err) { - // Update limits first to have latest min and max - err = setAllowedValues(&featureInfo, propertyName.c_str()); - if (VmbErrorSuccess != err) - { - LOG_ERROR(err, "Error while setting allowed values for feature " + featureName); - return err; - } - - // Adjust value - double min{}, max{}; - err = GetPropertyLowerLimit(propertyName.c_str(), min) | GetPropertyUpperLimit(propertyName.c_str(), max); - if (VmbErrorSuccess != err) - { - LOG_ERROR(err, "Error while getting limits for " + propertyName); - return err; - } - std::string adjustedValue = adjustValue(featureInfo, min, max, std::stod(propertyValue)); - err = setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); + LOG_ERROR(err, "Error while setting allowed values for feature " + featureName); + return err; } - if (propertyName == MM::g_Keyword_PixelType) + // Adjust value + double min{}, max{}; + err = GetPropertyLowerLimit(propertyName.c_str(), min) | GetPropertyUpperLimit(propertyName.c_str(), max); + if (VmbErrorSuccess != err) { - handlePixelFormatChange(propertyValue); + LOG_ERROR(err, "Error while getting limits for " + propertyName); + return err; } - break; - default: - // nothing - break; + std::string adjustedValue = adjustValue(featureInfo, min, max, std::stod(propertyValue)); + err = setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); } - if (VmbErrorSuccess != err) + if (propertyName == MM::g_Keyword_PixelType) { - LOG_ERROR(err, "Error while updating property " + propertyName); + handlePixelFormatChange(propertyValue); } + break; + default: + // nothing + break; + } - return err; - }(); + if (VmbErrorSuccess != err) + { + LOG_ERROR(err, "Error while updating property " + propertyName); + } - std::setlocale(LC_ALL, oldLocale); + return err; - return res; } void AlliedVisionCamera::handlePixelFormatChange(const std::string &pixelType) From 3522d5a2c8b42d378414d94b728e34a46985ce8a Mon Sep 17 00:00:00 2001 From: Dennis Langenkamp Date: Tue, 19 Sep 2023 15:05:42 +0200 Subject: [PATCH 044/141] Added missing changes --- DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 279cdbe00..e0e2856ae 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -1253,6 +1253,8 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, double interval return err; } + GetCoreCallback()->OnPropertiesChanged(); + return UpdateStatus(); } @@ -1296,6 +1298,8 @@ int AlliedVisionCamera::StopSequenceAcquisition() return err; } + GetCoreCallback()->OnPropertiesChanged(); + return UpdateStatus(); } From 684f73476fa08e3137e716929ce6ad9b0adbba9d Mon Sep 17 00:00:00 2001 From: Dennis Langenkamp Date: Tue, 19 Sep 2023 15:03:06 +0200 Subject: [PATCH 045/141] Fixed float property change always called Inside onProperty for every float property the OnPropertyChanged callback was always called even when the value has not changed, because for the comparison the float were converted to strings with a different count of decimal places. On stream start and stop now OnPropertiesChanged is called to update the ReadOnly status of all properties in the gui. --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index e0e2856ae..12d0b0842 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -585,6 +585,24 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase *pProp, MM::ActionType eAct) } const auto propertyName = pProp->GetName(); + + auto valueEqual = [pProp](const std::string& newValue) -> bool { + switch (pProp->GetType()) + { + case MM::PropertyType::Float: + { + double oldValue{}; + pProp->Get(oldValue); + return oldValue == std::stod(newValue); + } + default: + { + std::string oldValue{}; + pProp->Get(oldValue); + return oldValue == newValue; + } + } + }; // Check property mapping auto const featureName = mapPropertyNameToFeatureName(propertyName.c_str()); @@ -652,7 +670,7 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase *pProp, MM::ActionType eAct) } // Update property - if (propertyValue != featureValue) + if (!valueEqual(featureValue)) { pProp->Set(featureValue.c_str()); err = GetCoreCallback()->OnPropertyChanged(this, propertyName.c_str(), featureValue.c_str()); @@ -700,7 +718,16 @@ int AlliedVisionCamera::onProperty(MM::PropertyBase *pProp, MM::ActionType eAct) } std::string adjustedValue = adjustValue(featureInfo, min, max, std::stod(propertyValue)); err = setFeatureValue(&featureInfo, featureName.c_str(), adjustedValue); + if (err == VmbErrorSuccess) + { + err = GetCoreCallback()->OnPropertyChanged(this, propertyName.c_str(), adjustedValue.c_str()); + } + } + else if (err == VmbErrorSuccess) + { + err = GetCoreCallback()->OnPropertyChanged(this, propertyName.c_str(), propertyValue.c_str()); } + if (propertyName == MM::g_Keyword_PixelType) { From ea54eeb865c725ac6d78b1a37d21d4ea0749f9a1 Mon Sep 17 00:00:00 2001 From: Dennis Langenkamp Date: Tue, 19 Sep 2023 15:48:52 +0200 Subject: [PATCH 046/141] Added missing this to OnPropertiesChanged calls --- DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 12d0b0842..48552f1f1 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -1280,7 +1280,7 @@ int AlliedVisionCamera::StartSequenceAcquisition(long numImages, double interval return err; } - GetCoreCallback()->OnPropertiesChanged(); + GetCoreCallback()->OnPropertiesChanged(this); return UpdateStatus(); } @@ -1325,7 +1325,7 @@ int AlliedVisionCamera::StopSequenceAcquisition() return err; } - GetCoreCallback()->OnPropertiesChanged(); + GetCoreCallback()->OnPropertiesChanged(this); return UpdateStatus(); } From f5b7296db4750d67b07b481d8b1cb2f1054756d7 Mon Sep 17 00:00:00 2001 From: "Florian Klostermann [Allied Vision]" Date: Mon, 2 Oct 2023 16:22:46 +0200 Subject: [PATCH 047/141] Pull request #16: Prepare Micro Manager Pull Request Merge in HSW_SDK/mmcoreanddevices from feature/adjust-license to feature/allied-vision-camera-micro-manager-v2.0.2 Squashed commit of the following: commit faba693dfdb1b1f3bea6fe97256bc0fa9ba8c9ae Author: Florian Klostermann Date: Mon Oct 2 15:53:45 2023 +0200 fixed AVT Makefile.am commit 12869688f62ebfe237e9444303ee9931ac7fb114 Author: Florian Klostermann Date: Mon Oct 2 14:56:00 2023 +0200 adjusted additional include directory for AlliedVisionCamera commit dff1da08a9873eb955d3151711221faee92e5c55 Author: Florian Klostermann Date: Mon Oct 2 14:45:10 2023 +0200 adjsted project files after removing sdk headers from repo commit a06627c0ff2d28c164e6ea706984e2af0ff3e2a6 Author: Florian Klostermann Date: Mon Oct 2 14:36:32 2023 +0200 removed SDK Headers from repo, adjusted Makefile to use VIMBA_X_HOME variable commit 94959ad944621b870d09513826bab5897cbb29a5 Author: Florian Klostermann Date: Mon Oct 2 14:12:34 2023 +0200 adjusted license headers commit 5dd1d42e94e3d2a6a8d5a63057f5d7b40373358c Author: Florian Klostermann Date: Thu Sep 21 17:53:45 2023 +0200 adjusted license header --- .../AlliedVisionCamera/AlliedVisionCamera.cpp | 4 +- .../AlliedVisionCamera/AlliedVisionCamera.h | 4 +- .../AlliedVisionCamera.vcxproj | 18 +- .../AlliedVisionCamera.vcxproj.filters | 32 +- .../AlliedVisionDeviceBase.h | 4 +- .../AlliedVisionCamera/AlliedVisionHub.cpp | 4 +- .../AlliedVisionCamera/AlliedVisionHub.h | 4 +- .../{SDK => }/Loader/Constants.h | 4 +- .../{SDK => }/Loader/LibLoader.cpp | 4 +- .../{SDK => }/Loader/LibLoader.h | 4 +- DeviceAdapters/AlliedVisionCamera/Makefile.am | 4 +- .../AlliedVisionCamera/SDK/VmbC/VmbC.h | 2182 ----------------- .../SDK/VmbC/VmbCTypeDefinitions.h | 610 ----- .../SDK/VmbC/VmbCommonTypes.h | 444 ---- .../SDK/VmbC/VmbConstants.h | 85 - .../SDK/VmbImageTransform/VmbTransform.h | 336 --- .../SDK/VmbImageTransform/VmbTransformTypes.h | 1018 -------- DeviceAdapters/AlliedVisionCamera/license.txt | 25 + 18 files changed, 57 insertions(+), 4729 deletions(-) rename DeviceAdapters/AlliedVisionCamera/{SDK => }/Loader/Constants.h (93%) rename DeviceAdapters/AlliedVisionCamera/{SDK => }/Loader/LibLoader.cpp (98%) rename DeviceAdapters/AlliedVisionCamera/{SDK => }/Loader/LibLoader.h (98%) delete mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbC.h delete mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCTypeDefinitions.h delete mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCommonTypes.h delete mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbConstants.h delete mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransform.h delete mode 100644 DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransformTypes.h create mode 100644 DeviceAdapters/AlliedVisionCamera/license.txt diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp index 48552f1f1..fbc80e37c 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.cpp @@ -1,8 +1,8 @@ /*============================================================================= Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. + This file is distributed under the BSD license. + License text is included with the source distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h index 2dd14f1c1..ec0e2660c 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.h @@ -1,8 +1,8 @@ /*============================================================================= Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. + This file is distributed under the BSD license. + License text is included with the source distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj index 6b1087e6d..96b6f5ded 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj @@ -22,20 +22,14 @@ - - - - - - - - + + - + @@ -123,6 +117,7 @@ true Use pch.h + $(VIMBA_X_HOME)/api/include;%(AdditionalIncludeDirectories) Windows @@ -140,6 +135,7 @@ true Use pch.h + $(VIMBA_X_HOME)/api/include;%(AdditionalIncludeDirectories) Windows @@ -155,7 +151,7 @@ _DEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - $(ProjectDir)SDK;%(AdditionalIncludeDirectories) + $(VIMBA_X_HOME)/api/include;%(AdditionalIncludeDirectories) Windows @@ -171,7 +167,7 @@ NDEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - $(ProjectDir)SDK;%(AdditionalIncludeDirectories) + $(VIMBA_X_HOME)/api/include;%(AdditionalIncludeDirectories) Windows diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters index bdb569ec1..cbb7c0517 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj.filters @@ -18,34 +18,16 @@ Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - + Header Files - + Header Files - + Header Files - + Header Files @@ -53,14 +35,14 @@ Source Files - - Source Files - Source Files Source Files + + Source Files + \ No newline at end of file diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h index 642fa566e..a719d772e 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionDeviceBase.h @@ -1,8 +1,8 @@ /*============================================================================= Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. + This file is distributed under the BSD license. + License text is included with the source distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp index 59276c8c6..aa3147fea 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.cpp @@ -1,8 +1,8 @@ /*============================================================================= Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. + This file is distributed under the BSD license. + License text is included with the source distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h index 6ecc4a37a..7d47f4b96 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionHub.h @@ -1,8 +1,8 @@ /*============================================================================= Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. + This file is distributed under the BSD license. + License text is included with the source distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h b/DeviceAdapters/AlliedVisionCamera/Loader/Constants.h similarity index 93% rename from DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h rename to DeviceAdapters/AlliedVisionCamera/Loader/Constants.h index 17540091b..3ca10d69a 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/Constants.h +++ b/DeviceAdapters/AlliedVisionCamera/Loader/Constants.h @@ -1,8 +1,8 @@ /*============================================================================= Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. + This file is distributed under the BSD license. + License text is included with the source distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp b/DeviceAdapters/AlliedVisionCamera/Loader/LibLoader.cpp similarity index 98% rename from DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp rename to DeviceAdapters/AlliedVisionCamera/Loader/LibLoader.cpp index 118a642a9..de8d8d512 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.cpp +++ b/DeviceAdapters/AlliedVisionCamera/Loader/LibLoader.cpp @@ -1,8 +1,8 @@ /*============================================================================= Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. + This file is distributed under the BSD license. + License text is included with the source distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h b/DeviceAdapters/AlliedVisionCamera/Loader/LibLoader.h similarity index 98% rename from DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h rename to DeviceAdapters/AlliedVisionCamera/Loader/LibLoader.h index adf944f7a..fc63ea663 100644 --- a/DeviceAdapters/AlliedVisionCamera/SDK/Loader/LibLoader.h +++ b/DeviceAdapters/AlliedVisionCamera/Loader/LibLoader.h @@ -1,8 +1,8 @@ /*============================================================================= Copyright (C) 2023 Allied Vision Technologies. All Rights Reserved. - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. + This file is distributed under the BSD license. + License text is included with the source distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, diff --git a/DeviceAdapters/AlliedVisionCamera/Makefile.am b/DeviceAdapters/AlliedVisionCamera/Makefile.am index b5d08b4fa..1f4fa159a 100644 --- a/DeviceAdapters/AlliedVisionCamera/Makefile.am +++ b/DeviceAdapters/AlliedVisionCamera/Makefile.am @@ -1,8 +1,8 @@ AUTOMAKE_OPTIONS = subdir-objects -AM_CXXFLAGS = $(MMDEVAPI_CXXFLAGS) -ISDK +AM_CXXFLAGS = $(MMDEVAPI_CXXFLAGS) -I$(VIMBA_X_HOME)/api/include deviceadapter_LTLIBRARIES = libmmgr_dal_AlliedVisionCamera.la -libmmgr_dal_AlliedVisionCamera_la_SOURCES = AlliedVisionHub.h AlliedVisionHub.cpp AlliedVisionDeviceBase.h AlliedVisionDeviceBase.cpp AlliedVisionCamera.h AlliedVisionCamera.cpp SDK/Loader/Constants.h SDK/Loader/LibLoader.h SDK/Loader/LibLoader.cpp SDK/VmbC/VmbC.h SDK/VmbC/VmbCommonTypes.h SDK/VmbC/VmbConstants.h SDK/Vmbc/VmbCTypeDefinitions.h SDK/VmbImageTransform/VmbTransform.h SDK/VmbImageTransform/VmbTransformTypes.h +libmmgr_dal_AlliedVisionCamera_la_SOURCES = AlliedVisionHub.h AlliedVisionHub.cpp AlliedVisionDeviceBase.h AlliedVisionDeviceBase.cpp AlliedVisionCamera.h AlliedVisionCamera.cpp Loader/Constants.h Loader/LibLoader.h Loader/LibLoader.cpp $(VIMBA_X_HOME)/api/include/VmbC/VmbC.h $(VIMBA_X_HOME)/api/include/VmbC/VmbCommonTypes.h $(VIMBA_X_HOME)/api/include/VmbC/VmbConstants.h $(VIMBA_X_HOME)/api/include/VmbC/VmbCTypeDefinitions.h $(VIMBA_X_HOME)/api/include/VmbImageTransform/VmbTransform.h $(VIMBA_X_HOME)/api/include/VmbImageTransform/VmbTransformTypes.h libmmgr_dal_AlliedVisionCamera_la_LIBADD = $(MMDEVAPI_LIBADD) libmmgr_dal_AlliedVisionCamera_la_LDFLAGS = $(MMDEVAPI_LDFLAGS) diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbC.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbC.h deleted file mode 100644 index 8347195d0..000000000 --- a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbC.h +++ /dev/null @@ -1,2182 +0,0 @@ -/*============================================================================= - Copyright (C) 2012 - 2022 Allied Vision Technologies. All Rights Reserved. - - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. - -------------------------------------------------------------------------------- - - File: VmbC.h - -------------------------------------------------------------------------------- - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, - NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=============================================================================*/ - -/** - * \file - * \brief Main header file for the VmbC API. - * - * This file describes all necessary definitions for using Allied Vision's - * VmbC API. These type definitions are designed to be portable from other - * languages and other operating systems. - * - * General conventions: - * - Method names are composed in the following manner: - * - Vmb"Action" example: ::VmbStartup() - * - Vmb"Entity""Action" or Vmb"ActionTarget""Action" example: ::VmbCameraOpen() - * - Vmb"Entity""SubEntity/ActionTarget""Action" example: ::VmbFeatureCommandRun() - * - * - Strings (generally declared as "const char *") are assumed to have a trailing 0 character - * - All pointer parameters should of course be valid, except if stated otherwise. - * - To ensure compatibility with older programs linked against a former version of the API, - * all struct* parameters have an accompanying sizeofstruct parameter. - * - Functions returning lists are usually called twice: once with a zero buffer - * to get the length of the list, and then again with a buffer of the correct length. - */ - -#ifndef VMBC_H_INCLUDE_ -#define VMBC_H_INCLUDE_ - -#include -#include - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * \brief Timeout parameter signaling a blocking call. - */ -#define VMBINFINITE 0xFFFFFFFF - -/** - * \brief Define for the handle to use for accessing the Vmb system features cast to a given type - * - * Note: The primary purpose of this macro is the use in the VmbC sources. - * API users should use ::gVmbHandle instead. - */ -#define VMB_API_HANDLE(typeName) ((typeName)((((VmbUint64_t)1) << (VmbUint64_t)(sizeof(VmbHandle_t) * 8 - 4)) | ((VmbUint64_t) 1))) - -/** - * \brief Constant for the Vmb handle to be able to access Vmb system features. - */ -static const VmbHandle_t gVmbHandle = VMB_API_HANDLE(VmbHandle_t); - -//===== FUNCTION PROTOTYPES =================================================== - -/** - * \defgroup Functions Vmb C API Functions - * \{ - */ - -/** - * \name API Version - * \defgroup Version API Version - * \{ - */ - -/** - * \brief Retrieve the version number of VmbC. - * - * This function can be called at anytime, even before the API is - * initialized. All other version numbers may be queried via feature access. - * - * \param[out] versionInfo Pointer to the struct where version information resides - * \param[in] sizeofVersionInfo Size of structure in bytes - * - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback. - * - * \retval ::VmbErrorStructSize The given struct size is not valid for this version of the API - * - * \retval ::VmbErrorBadParameter \p versionInfo is null. - * - */ -IMEXPORTC VmbError_t VMB_CALL VmbVersionQuery ( VmbVersionInfo_t* versionInfo, - VmbUint32_t sizeofVersionInfo ); -/** - * \} - */ - -/** - * \name API Initialization - * \{ - * \defgroup Init API Initialization - * \{ - */ - -/** - * \brief Initializes the VmbC API. - * - * Note: This function must be called before any VmbC function other than ::VmbVersionQuery() is run. - * - * \param[in] pathConfiguration A string containing a semicolon (Windows) or colon (other os) separated list of paths. The paths contain directories to search for .cti files, - * paths to .cti files and optionally the path to a configuration xml file. If null is passed the parameter is the cti files found in the paths - * the GENICAM_GENTL{32|64}_PATH environment variable are considered - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorAlready This function was called before and call to ::VmbShutdown has been executed on a non-callback thread - * - * \retval ::VmbErrorInvalidCall If called from a callback or ::VmbShutdown is currently running - * - * \retval ::VmbErrorXml If parsing the settings xml is unsuccessful; a missing default xml file does not result in this error. - * - * \retval ::VmbErrorTLNotFound A transport layer that was marked as required was not found. - * - * \retval ::VmbErrorNoTL No transport layer was found on the system; note that some of the transport layers may have been filtered out via the settings file. - * - * \retval ::VmbErrorIO A log file should be written according to the settings xml file, but this log file could not be opened. - * - * \retval ::VmbErrorBadParameter \p pathConfiguration contains only separator and whitespace chars. - * - */ -IMEXPORTC VmbError_t VMB_CALL VmbStartup (const VmbFilePathChar_t* pathConfiguration); - -/** - * \brief Perform a shutdown of the API. - * - * This frees some resources and deallocates all physical resources if applicable. - * - * The call is silently ignored, if executed from a callback. - * - */ -IMEXPORTC void VMB_CALL VmbShutdown ( void ); - -/** - * \} \} - */ - -/** - * \name Camera Enumeration & Information - * \{ - * \defgroup CameraInfo Camera Enumeration & Information - * \{ - */ - -/** - * List all the cameras that are currently visible to the API. - * - * Note: This function is usually called twice: once with an empty array to query the length - * of the list, and then again with an array of the correct length. - * If camera lists change between the calls, numFound may deviate from the query return. - * - * \param[in,out] cameraInfo Array of VmbCameraInfo_t, allocated by the caller. - * The camera list is copied here. May be null. - * - * \param[in] listLength Number of entries in the callers cameraInfo array. - * - * \param[in,out] numFound Number of cameras found. Can be more than listLength. - * - * \param[in] sizeofCameraInfo Size of one VmbCameraInfo_t entry (if \p cameraInfo is null, this parameter is ignored). - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p numFound is null - * - * \retval ::VmbErrorStructSize The given struct size is not valid for this API version and \p cameraInfo is not null - * - * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries - */ -IMEXPORTC VmbError_t VMB_CALL VmbCamerasList ( VmbCameraInfo_t* cameraInfo, - VmbUint32_t listLength, - VmbUint32_t* numFound, - VmbUint32_t sizeofCameraInfo ); - -/** - * \brief Retrieve information about a single camera given its handle. - * - * Note: Some information is only filled for opened cameras. - * - * \param[in] cameraHandle The handle of the camera; both remote and local device handles are permitted - * - * \param[in,out] info Structure where information will be copied - * - * \param[in] sizeofCameraInfo Size of the structure - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorStructSize The given struct size is not valid for this API version - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorBadParameter \p info is null - * - * \retval ::VmbErrorBadHandle The handle does not correspond to a camera - */ -IMEXPORTC VmbError_t VMB_CALL VmbCameraInfoQueryByHandle( VmbHandle_t cameraHandle, - VmbCameraInfo_t* info, - VmbUint32_t sizeofCameraInfo); - -/** - * \brief Retrieve information about a single camera given the ID of the camera. - * - * Note: Some information is only filled for opened cameras. - * - * \param[in] idString ID of the camera - * - * \param[in,out] info Structure where information will be copied - * - * \param[in] sizeofCameraInfo Size of the structure - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p idString or \p info are null or \p idString is the empty string - * - * \retval ::VmbErrorNotFound No camera with the given id is found - * - * \retval ::VmbErrorStructSize The given struct size is not valid for this API version - */ -IMEXPORTC VmbError_t VMB_CALL VmbCameraInfoQuery ( const char* idString, - VmbCameraInfo_t* info, - VmbUint32_t sizeofCameraInfo ); - -/** - * \brief Open the specified camera. - * - * \param[in] idString ID of the camera. - * \param[in] accessMode The desired access mode. - * \param[out] cameraHandle The remote device handle of the camera, if opened successfully. - * - * A camera may be opened in a specific access mode, which determines - * the level of control you have on a camera. - * Examples for idString: - * - * "DEV_81237473991" for an ID given by a transport layer, - * "169.254.12.13" for an IP address, - * "000F314C4BE5" for a MAC address or - * "DEV_1234567890" for an ID as reported by Vmb - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInUse The camera with the given ID is already opened - * - * \retval ::VmbErrorInvalidCall If called from frame callback or chunk access callback - * - * \retval ::VmbErrorBadParameter If \p idString or \p cameraHandle are null - * - * \retval ::VmbErrorInvalidAccess A camera with the given id was found, but could not be opened - * - * \retval ::VmbErrorNotFound The designated camera cannot be found - */ -IMEXPORTC VmbError_t VMB_CALL VmbCameraOpen ( const char* idString, - VmbAccessMode_t accessMode, - VmbHandle_t* cameraHandle ); - -/** - * \brief Close the specified camera. - * - * Depending on the access mode this camera was opened with, events are killed, - * callbacks are unregistered, and camera control is released. - * - * \param[in] cameraHandle A valid camera handle - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInUse The camera is currently in use with ::VmbChunkDataAccess - * - * \retval ::VmbErrorBadHandle The handle does not correspond to an open camera - * - * \retval ::VmbErrorInvalidCall If called from frame callback or chunk access callback - */ -IMEXPORTC VmbError_t VMB_CALL VmbCameraClose ( const VmbHandle_t cameraHandle ); - -/** - * \} \} - */ - -//----- Features ---------------------------------------------------------- - -/** - * \name General Feature Functions - * \{ - * \defgroup GeneralFeatures General Feature Functions - * \{ - */ - -/** - * \brief List all the features for this entity. - * - * This function lists all implemented features, whether they are currently available or not. - * The list of features does not change as long as the entity is connected. - * - * This function is usually called twice: once with an empty list to query the length - * of the list, and then again with a list of the correct length. - * - * If ::VmbErrorMoreData is returned and \p numFound is non-null, the total number of features has been written to \p numFound. - * - * If there are more elements in \p featureInfoList than features available, the remaining elements - * are filled with zero-initialized ::VmbFeatureInfo_t structs. - * - * \param[in] handle Handle for an entity that exposes features - * \param[out] featureInfoList An array of ::VmbFeatureInfo_t to be filled by the API. May be null if \p numFund is used for size query. - * \param[in] listLength Number of ::VmbFeatureInfo_t elements provided - * \param[out] numFound Number of ::VmbFeatureInfo_t elements found. May be null if \p featureInfoList is not null. - * \param[in] sizeofFeatureInfo Size of a ::VmbFeatureInfo_t entry - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorStructSize The given struct size of ::VmbFeatureInfo_t is not valid for this version of the API - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter Both \p featureInfoList and \p numFound are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeaturesList ( VmbHandle_t handle, - VmbFeatureInfo_t* featureInfoList, - VmbUint32_t listLength, - VmbUint32_t* numFound, - VmbUint32_t sizeofFeatureInfo ); - -/** - * \brief Query information about the constant properties of a feature. - * - * Users provide a pointer to ::VmbFeatureInfo_t, which is then set to the internal representation. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] featureInfo The feature info to query - * \param[in] sizeofFeatureInfo Size of the structure - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorStructSize The given struct size of ::VmbFeatureInfo_t is not valid for this version of the API - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name or \p featureInfo are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound A feature with the given name does not exist. - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureInfoQuery ( const VmbHandle_t handle, - const char* name, - VmbFeatureInfo_t* featureInfo, - VmbUint32_t sizeofFeatureInfo ); - -/** - * \brief List all the features selected by a given feature for this module. - * - * This function lists all selected features, whether they are currently available or not. - * Features with selected features ("selectors") have no direct impact on the camera, - * but only influence the register address that selected features point to. - * The list of features does not change while the camera/interface is connected. - * This function is usually called twice: once with an empty array to query the length - * of the list, and then again with an array of the correct length. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] featureInfoList An array of ::VmbFeatureInfo_t to be filled by the API. May be null if \p numFound is used for size query. - * \param[in] listLength Number of ::VmbFeatureInfo_t elements provided - * \param[out] numFound Number of ::VmbFeatureInfo_t elements found. May be null if \p featureInfoList is not null. - * \param[in] sizeofFeatureInfo Size of a ::VmbFeatureInfo_t entry - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorBadParameter \p name is null or both \p featureInfoList and \p numFound are null - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorStructSize The given struct size of ::VmbFeatureInfo_t is not valid for this version of the API - * - * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureListSelected ( const VmbHandle_t handle, - const char* name, - VmbFeatureInfo_t* featureInfoList, - VmbUint32_t listLength, - VmbUint32_t* numFound, - VmbUint32_t sizeofFeatureInfo ); - -/** - * \brief Return the dynamic read and write capabilities of this feature. - * - * The access mode of a feature may change. For example, if "PacketSize" - * is locked while image data is streamed, it is only readable. - * - * \param[in] handle Handle for an entity that exposes features. - * \param[in] name Name of the feature. - * \param[out] isReadable Indicates if this feature is readable. May be null. - * \param[out] isWriteable Indicates if this feature is writable. May be null. - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name is null or both \p isReadable and \p isWriteable are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureAccessQuery ( const VmbHandle_t handle, - const char* name, - VmbBool_t * isReadable, - VmbBool_t * isWriteable ); - -/** - * \} \} - */ - -/** - * \name Integer Feature Access - * \{ - * \defgroup IntAccess Integer Feature Access - * \{ - */ - -/** - * \brief Get the value of an integer feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] value Value to get - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name or \p value are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Integer - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntGet ( const VmbHandle_t handle, - const char* name, - VmbInt64_t* value ); - -/** - * \brief Set the value of an integer feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[in] value Value to set - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorInvalidCall If called from feature callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter If \p name is null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Integer - * - * \retval ::VmbErrorInvalidAccess The feature is unavailable or not writable - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - * - * \retval ::VmbErrorInvalidValue If value is either out of bounds or not an increment of the minimum - * - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntSet ( const VmbHandle_t handle, - const char* name, - VmbInt64_t value ); - -/** - * \brief Query the range of an integer feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] min Minimum value to be returned. May be null. - * \param[out] max Maximum value to be returned. May be null. - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter If \p name is null or both \p min and \p max are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature name is not Integer - * - * \retval ::VmbErrorInvalidAccess The range information is unavailable or not writable - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntRangeQuery ( const VmbHandle_t handle, - const char* name, - VmbInt64_t* min, - VmbInt64_t* max ); - -/** - * \brief Query the increment of an integer feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] value Value of the increment to get. - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter If \p name or \p value are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Integer - * - * \retval ::VmbErrorInvalidAccess The information is unavailable or cannot be read - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntIncrementQuery ( const VmbHandle_t handle, - const char* name, - VmbInt64_t* value ); - -/** - * \brief Retrieves info about the valid value set of an integer feature. - * - * Retrieves information about the set of valid values of an integer feature. If null is passed as buffer, - * only the size of the set is determined and written to bufferFilledCount; Otherwise the largest possible - * number of elements of the valid value set is copied to buffer. - * - * \param[in] handle The handle for the entity the feature information is retrieved from - * \param[in] name The name of the feature to retrieve the info for; if null is passed ::VmbErrorBadParameter is returned - * \param[in] buffer The array to copy the valid values to or null if only the size of the set is requested - * \param[in] bufferSize The size of buffer; if buffer is null, the value is ignored - * \param[out] setSize The total number of elements in the set; the value is set, if ::VmbErrorMoreData is returned - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name is null or both \p buffer and \p bufferFilledCount are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of the feature is not Integer - * - * \retval ::VmbErrorValidValueSetNotPresent The feature does not provide a valid value set - * - * \retval ::VmbErrorMoreData Some of data was retrieved successfully, but the size of buffer is insufficient to store all elements - * - * \retval ::VmbErrorIncomplete The module the handle refers to is in a state where it cannot complete the request - * - * \retval ::VmbErrorOther Some other issue occurred - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureIntValidValueSetQuery(const VmbHandle_t handle, - const char* name, - VmbInt64_t* buffer, - VmbUint32_t bufferSize, - VmbUint32_t* setSize); - -/** - * \} \} - */ - -/** - * \name Float Feature Access - * \{ - * \defgroup FloatAccess Float Feature Access - * \{ - */ - -/** - * \brief Get the value of a float feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] value Value to get - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name or \p value are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Float - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureFloatGet ( const VmbHandle_t handle, - const char* name, - double* value ); - -/** - * \brief Set the value of a float feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[in] value Value to set - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from feature callback - * - * \retval ::VmbErrorBadParameter \p name is null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Float - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - * - * \retval ::VmbErrorInvalidValue If value is not within valid bounds - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureFloatSet ( const VmbHandle_t handle, - const char* name, - double value ); - -/** - * \brief Query the range of a float feature. - * - * Only one of the values may be queried if the other parameter is set to null, - * but if both parameters are null, an error is returned. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] min Minimum value to be returned. May be null. - * \param[out] max Maximum value to be returned. May be null. - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorBadParameter \p name is null or both \p min and \p max are null - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Float - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureFloatRangeQuery ( const VmbHandle_t handle, - const char* name, - double* min, - double* max ); - -/** - * \brief Query the increment of a float feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] hasIncrement `true` if this float feature has an increment. - * \param[out] value Value of the increment to get. - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorBadParameter \p name is null or both \p value and \p hasIncrement are null - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Float - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureFloatIncrementQuery ( const VmbHandle_t handle, - const char* name, - VmbBool_t* hasIncrement, - double* value ); - -/** - * \} \} -*/ - -/** - * \name Enum Feature Access - * \{ - * \defgroup EnumAccess Enum Feature Access - * \{ - */ - -/** - * \brief Get the value of an enumeration feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] value The current enumeration value. The returned value is a - * reference to the API value - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorBadParameter \p name or \p value are null - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature featureName is not Enumeration - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature is not available - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumGet ( const VmbHandle_t handle, - const char* name, - const char** value ); - -/** - * \brief Set the value of an enumeration feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[in] value Value to set - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from feature callback - * - * \retval ::VmbErrorBadParameter If \p name or \p value are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration - * - * \retval ::VmbErrorNotAvailable The feature is not available - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorInvalidValue \p value is not a enum entry for the feature or the existing enum entry is currently not available - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumSet ( const VmbHandle_t handle, - const char* name, - const char* value ); - -/** - * \brief Query the value range of an enumeration feature. - * - * All elements not filled with the names of enum entries by the function are set to null. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[out] nameArray An array of enumeration value names; may be null if \p numFound is used for size query - * \param[in] arrayLength Number of elements in the array - * \param[out] numFound Number of elements found; may be null if \p nameArray is not null - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name is null or both \p nameArray and \p numFound are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorNotImplemented The feature \p name is not implemented - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration - * - * \retval ::VmbErrorMoreData The given array length was insufficient to hold all available entries - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumRangeQuery ( const VmbHandle_t handle, - const char* name, - const char** nameArray, - VmbUint32_t arrayLength, - VmbUint32_t* numFound ); - -/** - * \brief Check if a certain value of an enumeration is available. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[in] value Value to check - * \param[out] isAvailable Indicates if the given enumeration value is available - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name, \p value or \p isAvailable are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration - * - * \retval ::VmbErrorNotImplemented The feature \p name is not implemented - * - * \retval ::VmbErrorInvalidValue There is no enum entry with string representation of \p value for the given enum feature - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumIsAvailable ( const VmbHandle_t handle, - const char* name, - const char* value, - VmbBool_t * isAvailable ); - -/** - * \brief Get the integer value for a given enumeration string value. - * - * Converts a name of an enum member into an int value ("Mono12Packed" to 0x10C0006) - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[in] value The enumeration value to get the integer value for - * \param[out] intVal The integer value for this enumeration entry - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter If \p name, \p value or \p intVal are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound No feature with the given name was found - * - * \retval ::VmbErrorNotImplemented The feature \p name is not implemented - * - * \retval ::VmbErrorInvalidValue \p value is not the name of a enum entry for the feature - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumAsInt ( const VmbHandle_t handle, - const char* name, - const char* value, - VmbInt64_t* intVal ); - -/** - * \brief Get the enumeration string value for a given integer value. - * - * Converts an int value to a name of an enum member (e.g. 0x10C0006 to "Mono12Packed") - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[in] intValue The numeric value - * \param[out] stringValue The string value for the numeric value - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name or \p stringValue are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound No feature with the given name was found - * - * \retval ::VmbErrorNotImplemented No feature \p name is not implemented - * - * \retval ::VmbErrorInvalidValue \p intValue is not the int value of an enum entry - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Enumeration - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumAsString ( VmbHandle_t handle, - const char* name, - VmbInt64_t intValue, - const char** stringValue ); - -/** - * \brief Get infos about an entry of an enumeration feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] featureName Name of the feature - * \param[in] entryName Name of the enum entry of that feature - * \param[out] featureEnumEntry Infos about that entry returned by the API - * \param[in] sizeofFeatureEnumEntry Size of the structure - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorStructSize Size of ::VmbFeatureEnumEntry_t is not compatible with the API version - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p featureName, \p entryName or \p featureEnumEntry are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorNotImplemented The feature \p name is not implemented - * - * \retval ::VmbErrorInvalidValue There is no enum entry with a string representation of \p entryName - * - * \retval ::VmbErrorWrongType The type of feature featureName is not Enumeration - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureEnumEntryGet ( const VmbHandle_t handle, - const char* featureName, - const char* entryName, - VmbFeatureEnumEntry_t* featureEnumEntry, - VmbUint32_t sizeofFeatureEnumEntry ); - -/** - * \} \} - */ - -/** - * \name String Feature Access - * \{ - * \defgroup StringAccess String Feature Access - * \{ - */ - -/** - * \brief Get the value of a string feature. - * - * This function is usually called twice: once with an empty buffer to query the length - * of the string, and then again with a buffer of the correct length. - * - * The value written to \p sizeFilled includes the terminating 0 character of the string. - * - * If a \p buffer is provided and there its insufficient to hold all the data, the longest - * possible prefix fitting the buffer is copied to \p buffer; the last element of \p buffer is - * set to 0 case. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the string feature - * \param[out] buffer String buffer to fill. May be null if \p sizeFilled is used for size query. - * \param[in] bufferSize Size of the input buffer - * \param[out] sizeFilled Size actually filled. May be null if \p buffer is not null. - * - * - * \return An error code indicating the type of error, if any. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name is null, both \p buffer and \p sizeFilled are null or \p buffer is non-null and bufferSize is 0 - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not String - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - * - * \retval ::VmbErrorMoreData The given buffer size was too small - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureStringGet ( const VmbHandle_t handle, - const char* name, - char* buffer, - VmbUint32_t bufferSize, - VmbUint32_t* sizeFilled ); - -/** - * \brief Set the value of a string feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the string feature - * \param[in] value Value to set - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from feature callback - * - * \retval ::VmbErrorBadParameter \p name or \p value are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not String - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorInvalidValue If length of value exceeded the maximum length - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureStringSet ( const VmbHandle_t handle, - const char* name, - const char* value ); - -/** - * \brief Get the maximum length of a string feature. - * - * The length reported does not include the terminating 0 char. - * - * Note: For some features the maximum size is not fixed and may change. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the string feature - * \param[out] maxLength Maximum length of this string feature - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name or \p maxLength are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorWrongType The type of feature \p name is not String - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureStringMaxlengthQuery ( const VmbHandle_t handle, - const char* name, - VmbUint32_t* maxLength ); - -/** - * \} \} - */ - -/** - * \name Boolean Feature Access - * \{ - * \defgroup BoolAccess Boolean Feature Access - * \{ - */ - -/** - * \brief Get the value of a boolean feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the boolean feature - * \param[out] value Value to be read - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name or \p value are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound If feature is not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Boolean - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureBoolGet ( const VmbHandle_t handle, - const char* name, - VmbBool_t * value ); - -/** - * \brief Set the value of a boolean feature. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the boolean feature - * \param[in] value Value to write - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name is null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound If the feature is not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Boolean - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - * - * \retval ::VmbErrorInvalidValue If value is not within valid bounds - * - * \retval ::VmbErrorInvalidCall If called from feature callback - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureBoolSet ( const VmbHandle_t handle, - const char* name, - VmbBool_t value ); - -/** - * \} \} - */ - -/** - * \name Command Feature Access - * \{ - * \defgroup CmdAccess Command Feature Access - * \{ - */ - -/** - * \brief Run a feature command. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the command feature - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorInvalidCall If called from a feature callback or chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name is null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound Feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Command - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureCommandRun ( const VmbHandle_t handle, - const char* name ); - -/** - * \brief Check if a feature command is done. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the command feature - * \param[out] isDone State of the command. - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorBadParameter If \p name or \p isDone are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound Feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Command - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureCommandIsDone ( const VmbHandle_t handle, - const char* name, - VmbBool_t * isDone ); - -/** - * \} \} - */ - -/** - * \name Raw Feature Access - * \{ - * \defgroup RawAccess Raw Feature Access - * \{ - */ - -/** - * \brief Read the memory contents of an area given by a feature name. - * - * This feature type corresponds to a top-level "Register" feature in GenICam. - * Data transfer is split up by the transport layer if the feature length is too large. - * You can get the size of the memory area addressed by the feature name by ::VmbFeatureRawLengthQuery(). - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the raw feature - * \param[out] buffer Buffer to fill - * \param[in] bufferSize Size of the buffer to be filled - * \param[out] sizeFilled Number of bytes actually filled - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p name, \p buffer or \p sizeFilled are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound Feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Register - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureRawGet ( const VmbHandle_t handle, - const char* name, - char* buffer, - VmbUint32_t bufferSize, - VmbUint32_t* sizeFilled ); - -/** - * \brief Write to a memory area given by a feature name. - * - * This feature type corresponds to a first-level "Register" node in the XML file. - * Data transfer is split up by the transport layer if the feature length is too large. - * You can get the size of the memory area addressed by the feature name by ::VmbFeatureRawLengthQuery(). - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the raw feature - * \param[in] buffer Data buffer to use - * \param[in] bufferSize Size of the buffer - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from feature callback or a chunk access callback - * - * \retval ::VmbErrorBadParameter \p name or \p buffer are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound Feature was not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Register - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureRawSet ( const VmbHandle_t handle, - const char* name, - const char* buffer, - VmbUint32_t bufferSize ); - -/** - * \brief Get the length of a raw feature for memory transfers. - * - * This feature type corresponds to a first-level "Register" node in the XML file. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the raw feature - * \param[out] length Length of the raw feature area (in bytes) - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter If \p name or \p length are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound Feature not found - * - * \retval ::VmbErrorWrongType The type of feature \p name is not Register - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorNotImplemented The feature isn't implemented - * - * \retval ::VmbErrorNotAvailable The feature isn't available currently - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureRawLengthQuery ( const VmbHandle_t handle, - const char* name, - VmbUint32_t* length ); - -/** - * \} \} - */ - -/** - * \name Feature Invalidation - * \{ - * \defgroup FeatureInvalidation Feature Invalidation - * \{ - */ - -/** - * \brief Register a VmbInvalidationCallback callback for feature invalidation signaling. - * - * Any feature change, either of its value or of its access state, may be tracked - * by registering an invalidation callback. - * Registering multiple callbacks for one feature invalidation event is possible because - * only the combination of handle, name, and callback is used as key. If the same - * combination of handle, name, and callback is registered a second time, the callback remains - * registered and the context is overwritten with \p userContext. - * - * \param[in] handle Handle for an entity that emits events - * \param[in] name Name of the event - * \param[in] callback Callback to be run when invalidation occurs - * \param[in] userContext User context passed to function - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorBadParameter If \p name or \p callback are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound No feature with \p name was found for the module associated with \p handle - * - * \retval ::VmbErrorNotImplemented The feature \p name is not implemented - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureInvalidationRegister ( VmbHandle_t handle, - const char* name, - VmbInvalidationCallback callback, - void* userContext ); - -/** - * \brief Unregister a previously registered feature invalidation callback. - * - * Since multiple callbacks may be registered for a feature invalidation event, - * a combination of handle, name, and callback is needed for unregistering, too. - * - * \param[in] handle Handle for an entity that emits events - * \param[in] name Name of the event - * \param[in] callback Callback to be removed - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorBadParameter If \p name or \p callback are null - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound No feature with \p name was found for the module associated with \p handle or there was no listener to unregister - * - * \retval ::VmbErrorNotImplemented The feature \p name is not implemented - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - */ -IMEXPORTC VmbError_t VMB_CALL VmbFeatureInvalidationUnregister ( VmbHandle_t handle, - const char* name, - VmbInvalidationCallback callback ); - -/** - * \} \} - */ - -/** - * \name Image preparation and acquisition - * \{ - * \defgroup Capture Image preparation and acquisition - * \{ - */ - -/** -* \brief Get the necessary payload size for buffer allocation. -* -* Returns the payload size necessary for buffer allocation as queried from the Camera. -* If the stream module provides a PayloadSize feature, this value will be returned instead. -* If a camera handle is passed, the payload size refers to the stream with index 0. -* -* \param[in] handle Camera or stream handle -* \param[out] payloadSize Payload Size -* -* -* \return An error code indicating success or the type of error that occurred. -* -* \retval ::VmbErrorSuccess If no error -* -* \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command -* -* \retval ::VmbErrorBadHandle The given handle is not valid -* -* \retval ::VmbErrorBadParameter \p payloadSize is null -*/ -IMEXPORTC VmbError_t VMB_CALL VmbPayloadSizeGet(VmbHandle_t handle, - VmbUint32_t* payloadSize); - -/** - * \brief Announce frames to the API that may be queued for frame capturing later. - * - * Allows some preparation for frames like DMA preparation depending on the transport layer. - * The order in which the frames are announced is not taken into consideration by the API. - * If frame.buffer is null, the allocation is done by the transport layer. - * - * \param[in] handle Camera or stream handle - * \param[in] frame Frame buffer to announce - * \param[in] sizeofFrame Size of the frame structure - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorStructSize The given struct size is not valid for this version of the API - * - * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given camera handle is not valid - * - * \retval ::VmbErrorBadParameter \p frame is null - * - * \retval ::VmbErrorAlready The frame has already been announced - * - * \retval ::VmbErrorBusy The underlying transport layer does not support announcing frames during acquisition - * - * \retval ::VmbErrorMoreData The given buffer size is invalid (usually 0) - */ -IMEXPORTC VmbError_t VMB_CALL VmbFrameAnnounce ( VmbHandle_t handle, - const VmbFrame_t* frame, - VmbUint32_t sizeofFrame ); - - -/** - * \brief Revoke a frame from the API. - * - * The referenced frame is removed from the pool of frames for capturing images. - * - * \param[in] handle Handle for a camera or stream - * \param[in] frame Frame buffer to be removed from the list of announced frames - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorBadParameter The given frame pointer is not valid - * - * \retval ::VmbErrorBusy The underlying transport layer does not support revoking frames during acquisition - * - * \retval ::VmbErrorNotFound The given frame could not be found for the stream - * - * \retval ::VmbErrorInUse The frame is currently still in use (e.g. in a running frame callback) - */ -IMEXPORTC VmbError_t VMB_CALL VmbFrameRevoke ( VmbHandle_t handle, - const VmbFrame_t* frame ); - - -/** - * \brief Revoke all frames assigned to a certain stream or camera. - * - * In case of an failure some of the frames may have been revoked. To prevent this it is recommended to call - * ::VmbCaptureQueueFlush for the same handle before invoking this function. - * - * \param[in] handle Handle for a stream or camera - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle \p handle is not valid - * - * \retval ::VmbErrorInUse One of the frames of the stream is still in use - */ -IMEXPORTC VmbError_t VMB_CALL VmbFrameRevokeAll ( VmbHandle_t handle ); - - -/** - * \brief Prepare the API for incoming frames. - * - * \param[in] handle Handle for a camera or a stream - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given handle is not valid; this includes the camera no longer being open - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorMoreData The buffer size of the announced frames is insufficient - * - * \retval ::VmbErrorInsufficientBufferCount The operation requires more buffers to be announced; see the StreamAnnounceBufferMinimum stream feature - * - * \retval ::VmbErrorAlready Capturing was already started - */ -IMEXPORTC VmbError_t VMB_CALL VmbCaptureStart ( VmbHandle_t handle ); - - -/** - * \brief Stop the API from being able to receive frames. - * - * Consequences of VmbCaptureEnd(): - * The frame callback will not be called anymore - * - * \note This function waits for the completion of the last callback for the current capture. - * If the callback does not return in finite time, this function may not return in finite time either. - * - * \param[in] handle Handle for a stream or camera - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorInvalidCall If called from a frame callback or a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle \p handle is not valid - */ -IMEXPORTC VmbError_t VMB_CALL VmbCaptureEnd ( VmbHandle_t handle ); - - -/** - * \brief Queue frames that may be filled during frame capturing. - * - * The given frame is put into a queue that will be filled sequentially. - * The order in which the frames are filled is determined by the order in which they are queued. - * If the frame was announced with ::VmbFrameAnnounce() before, the application - * has to ensure that the frame is also revoked by calling ::VmbFrameRevoke() or - * ::VmbFrameRevokeAll() when cleaning up. - * - * \warning \p callback should to return in finite time. Otherwise ::VmbCaptureEnd and - * operations resulting in the stream being closed may not return. - * - * \param[in] handle Handle of a camera or stream - * \param[in] frame Pointer to an already announced frame - * \param[in] callback Callback to be run when the frame is complete. Null is OK. - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorBadParameter If \p frame is null - * - * \retval ::VmbErrorBadHandle No stream related to \p handle could be found - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInternalFault The buffer or bufferSize members of \p frame have been set to null or zero respectively - * - * \retval ::VmbErrorNotFound The frame is not a frame announced for the given stream - * - * \retval ::VmbErrorAlready The frame is currently queued - */ -IMEXPORTC VmbError_t VMB_CALL VmbCaptureFrameQueue ( VmbHandle_t handle, - const VmbFrame_t* frame, - VmbFrameCallback callback ); - -/** - * \brief Wait for a queued frame to be filled (or dequeued). - * - * The frame needs to be queued and not filled for the function to complete successfully. - * - * If a camera handle is passed, the first stream of the camera is used. - * - * \param[in] handle Handle of a camera or stream - * \param[in] frame Pointer to an already announced and queued frame - * \param[in] timeout Timeout (in milliseconds) - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorBadParameter If \p frame or the buffer of \p frame are null or the the buffer size of \p frame is 0 - * - * \retval ::VmbErrorBadHandle No stream related to \p handle could be found - * - * \retval ::VmbErrorNotFound The frame is not one currently queued for the stream - * - * \retval ::VmbErrorAlready The frame has already been dequeued or VmbCaptureFrameWait has been called already for this frame - * - * \retval ::VmbErrorInUse If the frame was queued with a frame callback - * - * \retval ::VmbErrorTimeout Call timed out - * - * \retval ::VmbErrorIncomplete Capture is not active when the function is called - */ -IMEXPORTC VmbError_t VMB_CALL VmbCaptureFrameWait ( const VmbHandle_t handle, - const VmbFrame_t* frame, - VmbUint32_t timeout); - - -/** - * \brief Flush the capture queue. - * - * Control of all the currently queued frames will be returned to the user, - * leaving no frames in the capture queue. - * After this call, no frame notification will occur until frames are queued again - * - * Frames need to be revoked separately, if desired. - * - * This function can only succeeds, if no capture is currently active. - * If ::VmbCaptureStart has been called for the stream, but no successful call to ::VmbCaptureEnd - * happened, the function fails with error code ::VmbErrorInUse. - * - * \param[in] handle The handle of the camera or stream to flush. - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorBadHandle No stream related to \p handle could be found. - * - * \retval ::VmbErrorInUse There is currently an active capture - */ -IMEXPORTC VmbError_t VMB_CALL VmbCaptureQueueFlush(VmbHandle_t handle); - -/** - * \} \} - */ - -/** - * \name Transport Layer Enumeration & Information - * \{ - * \defgroup TransportLayer Transport Layer Enumeration & Information - * \{ - */ - -/** - * \brief List all the transport layers that are used by the API. - * - * Note: This function is usually called twice: once with an empty array to query the length - * of the list, and then again with an array of the correct length. - * - * \param[in,out] transportLayerInfo Array of VmbTransportLayerInfo_t, allocated by the caller. - * The transport layer list is copied here. May be null. - * \param[in] listLength Number of entries in the caller's transportLayerInfo array. - * \param[in,out] numFound Number of transport layers found. May be more than listLength. - * \param[in] sizeofTransportLayerInfo Size of one ::VmbTransportLayerInfo_t entry (ignored if \p transportLayerInfo is null). - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInternalFault An internal fault occurred - * - * \retval ::VmbErrorNotImplemented One of the transport layers does not provide the required information - * - * \retval ::VmbErrorBadParameter \p numFound is null - * - * \retval ::VmbErrorStructSize The given struct size is not valid for this API version - * - * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries - */ -IMEXPORTC VmbError_t VMB_CALL VmbTransportLayersList ( VmbTransportLayerInfo_t* transportLayerInfo, - VmbUint32_t listLength, - VmbUint32_t* numFound, - VmbUint32_t sizeofTransportLayerInfo); - -/** - * \} \} -*/ - -/** - * \name Interface Enumeration & Information - * \{ - * \defgroup Interface Interface Enumeration & Information - * \{ - */ - -/** - * \brief List all the interfaces that are currently visible to the API. - * - * Note: All the interfaces known via GenICam transport layers are listed by this - * command and filled into the provided array. Interfaces may correspond to - * adapter cards or frame grabber cards. - * This function is usually called twice: once with an empty array to query the length - * of the list, and then again with an array of the correct length. - * - * \param[in,out] interfaceInfo Array of ::VmbInterfaceInfo_t, allocated by the caller. - * The interface list is copied here. May be null. - * - * \param[in] listLength Number of entries in the callers interfaceInfo array - * - * \param[in,out] numFound Number of interfaces found. Can be more than listLength - * - * \param[in] sizeofInterfaceInfo Size of one ::VmbInterfaceInfo_t entry - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p numFound is null - * - * \retval ::VmbErrorStructSize The given struct size is not valid for this API version - * - * \retval ::VmbErrorMoreData The given list length was insufficient to hold all available entries - */ -IMEXPORTC VmbError_t VMB_CALL VmbInterfacesList ( VmbInterfaceInfo_t* interfaceInfo, - VmbUint32_t listLength, - VmbUint32_t* numFound, - VmbUint32_t sizeofInterfaceInfo ); - -/** - * \} \} - */ - -/** - * \name Direct Access - * \{ - * \defgroup DirectAccess Direct Access - * \{ - */ - -//----- Memory/Register access -------------------------------------------- - -/** - * \brief Read an array of bytes. - * - * \param[in] handle Handle for an entity that allows memory access - * \param[in] address Address to be used for this read operation - * \param[in] bufferSize Size of the data buffer to read - * \param[out] dataBuffer Buffer to be filled - * \param[out] sizeComplete Size of the data actually read - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - */ -IMEXPORTC VmbError_t VMB_CALL VmbMemoryRead ( const VmbHandle_t handle, - VmbUint64_t address, - VmbUint32_t bufferSize, - char* dataBuffer, - VmbUint32_t* sizeComplete ); - -/** - * \brief Write an array of bytes. - * - * \param[in] handle Handle for an entity that allows memory access - * \param[in] address Address to be used for this read operation - * \param[in] bufferSize Size of the data buffer to write - * \param[in] dataBuffer Data to write - * \param[out] sizeComplete Number of bytes successfully written; if an - * error occurs this is less than bufferSize - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorMoreData Not all data were written; see sizeComplete value for the number of bytes written - */ -IMEXPORTC VmbError_t VMB_CALL VmbMemoryWrite ( const VmbHandle_t handle, - VmbUint64_t address, - VmbUint32_t bufferSize, - const char* dataBuffer, - VmbUint32_t* sizeComplete ); - -/** - * \} \} - */ - -/** - * \name Load & Save Settings - * \{ - * \defgroup LoadSaveSettings Load & Save Settings - * \{ - */ - -/** - * \brief Write the current features related to a module to a xml file - * - * Camera must be opened beforehand and function needs corresponding handle. - * With given filename parameter path and name of XML file can be determined. - * Additionally behaviour of function can be set with providing 'persistent struct'. - * - * \param[in] handle Handle for an entity that allows register access - * \param[in] filePath The path to the file to save the settings to; relative paths are relative to the current working directory - * \param[in] settings Settings struct; if null the default settings are used - * (persist features except LUT for the remote device, maximum 5 iterations, logging only errors) - * \param[in] sizeofSettings Size of settings struct - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorBadParameter If \p filePath is or the settings struct is invalid - * - * \retval ::VmbErrorStructSize If sizeofSettings the struct size does not match the size of the struct expected by the API - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorNotFound The provided handle is insufficient to identify all the modules that should be saved - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorIO There was an issue writing the file. - */ -IMEXPORTC VmbError_t VMB_CALL VmbSettingsSave(VmbHandle_t handle, - const VmbFilePathChar_t* filePath, - const VmbFeaturePersistSettings_t* settings, - VmbUint32_t sizeofSettings); - -/** - * \brief Load all feature values from xml file to device-related modules. - * - * The modules must be opened beforehand. If the handle is non-null it must be a valid handle other than the Vmb API handle. - * Additionally behaviour of function can be set with providing \p settings . Note that even in case of an failure some or all of the features - * may have been set for some of the modules. - * - * The error code ::VmbErrorRetriesExceeded only indicates that the number of retries was insufficient - * to restore the features. Even if the features could not be restored for one of the modules, restoring the features is not aborted but the process - * continues for other modules, if present. - * - * \param[in] handle Handle related to the modules to write the values to; - * may be null to indicate that modules should be identified based on the information provided in the input file - * - * \param[in] filePath The path to the file to load the settings from; relative paths are relative to the current working directory - * \param[in] settings Settings struct; pass null to use the default settings. If the \p maxIterations field is 0, the number of - * iterations is determined by the value loaded from the xml file - * \param[in] sizeofSettings Size of the settings struct - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess If no error - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback - * - * \retval ::VmbErrorStructSize If sizeofSettings the struct size does not match the size of the struct expected by the API - * - * \retval ::VmbErrorWrongType \p handle is neither null nor a transport layer, interface, local device, remote device or stream handle - * - * \retval ::VmbErrorBadHandle The given handle is not valid - * - * \retval ::VmbErrorAmbiguous The modules to restore the settings for cannot be uniquely identified based on the information available - * - * \retval ::VmbErrorNotFound The provided handle is insufficient to identify all the modules that should be restored - * - * \retval ::VmbErrorRetriesExceeded Some or all of the features could not be restored with the max iterations specified - * - * \retval ::VmbErrorInvalidAccess Operation is invalid with the current access mode - * - * \retval ::VmbErrorBadParameter If \p filePath is null or the settings struct is invalid - * - * \retval ::VmbErrorIO There was an issue with reading the file. - */ -IMEXPORTC VmbError_t VMB_CALL VmbSettingsLoad(VmbHandle_t handle, - const VmbFilePathChar_t* filePath, - const VmbFeaturePersistSettings_t* settings, - VmbUint32_t sizeofSettings); - -/** - * \} \} - */ - -/** - * \name Chunk Data - * \{ - * \defgroup ChunkData Chunk Data - * \{ - */ - -/** - * \brief Access chunk data for a frame. - * - * This function can only succeed if the given frame has been filled by the API. - * - * \param[in] frame A pointer to a filled frame that is announced - * \param[in] chunkAccessCallback A callback to access the chunk data from - * \param[in] userContext A pointer to pass to the callback - * - * - * \return An error code indicating success or the type of error that occurred. - * - * \retval ::VmbErrorSuccess The call was successful - * - * \retval ::VmbErrorInvalidCall If called from a chunk access callback or a feature callback - * - * \retval ::VmbErrorApiNotStarted ::VmbStartup() was not called before the current command - * - * \retval ::VmbErrorBadParameter \p frame or \p chunkAccessCallback are null - * - * \retval ::VmbErrorInUse The frame state does not allow for retrieval of chunk data - * (e.g. the frame could have been reenqueued before the chunk access could happen). - * - * \retval ::VmbErrorNotFound The frame is currently not announced for a stream - * - * \retval ::VmbErrorDeviceNotOpen If the device the frame was received from is no longer open - * - * \retval ::VmbErrorNoChunkData \p frame does not contain chunk data - * - * \retval ::VmbErrorParsingChunkData The chunk data does not adhere to the expected format - * - * \retval ::VmbErrorUserCallbackException The callback threw an exception - * - * \retval ::VmbErrorFeaturesUnavailable The feature description for the remote device is unavailable - * - * \retval ::VmbErrorCustom The minimum a user defined error code returned by the callback - */ -IMEXPORTC VmbError_t VMB_CALL VmbChunkDataAccess(const VmbFrame_t* frame, - VmbChunkAccessCallback chunkAccessCallback, - void* userContext); - -/** - * \} \} \} - */ -#ifdef __cplusplus -} -#endif - -#endif // VMBC_H_INCLUDE_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCTypeDefinitions.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCTypeDefinitions.h deleted file mode 100644 index b010d5be6..000000000 --- a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCTypeDefinitions.h +++ /dev/null @@ -1,610 +0,0 @@ -/*============================================================================= - Copyright (C) 2012 - 2021 Allied Vision Technologies. All Rights Reserved. - - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. - -------------------------------------------------------------------------------- - - File: VmbCTypeDefinitions.h - -------------------------------------------------------------------------------- - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, - NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=============================================================================*/ - -/** - * \file - * \brief Struct definitions for the VmbC API. - */ - -#ifndef VMBC_TYPE_DEFINITIONS_H_INCLUDE_ -#define VMBC_TYPE_DEFINITIONS_H_INCLUDE_ - -#include -#include - -#include - -#if defined (_WIN32) -#if defined AVT_VMBAPI_C_EXPORTS // DLL exports -#define IMEXPORTC // We export via the .def file -#elif defined AVT_VMBAPI_C_LIB // static LIB -#define IMEXPORTC -#else // import -#define IMEXPORTC __declspec(dllimport) -#endif - -#ifndef _WIN64 - // Calling convention -#define VMB_CALL __stdcall -#else - // Calling convention -#define VMB_CALL -#endif -#elif defined (__GNUC__) && (__GNUC__ >= 4) && defined (__ELF__) - // SO exports (requires compiler option -fvisibility=hidden) -#ifdef AVT_VMBAPI_C_EXPORTS -#define IMEXPORTC __attribute__((visibility("default"))) -#else -#define IMEXPORTC -#endif - -#ifdef __i386__ - // Calling convention -#define VMB_CALL __attribute__((stdcall)) -#else - // Calling convention -#define VMB_CALL -#endif -#elif defined (__APPLE__) -#define IMEXPORTC __attribute__((visibility("default"))) - // Calling convention -#define VMB_CALL -#else -#error Unknown platform, file needs adaption -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * \name Transport layer - * \{ - */ - - /** -* \brief Camera or transport layer type (for instance U3V or GEV). -*/ -typedef enum VmbTransportLayerType -{ - VmbTransportLayerTypeUnknown = 0, //!< Interface is not known to this version of the API - VmbTransportLayerTypeGEV = 1, //!< GigE Vision - VmbTransportLayerTypeCL = 2, //!< Camera Link - VmbTransportLayerTypeIIDC = 3, //!< IIDC 1394 - VmbTransportLayerTypeUVC = 4, //!< USB video class - VmbTransportLayerTypeCXP = 5, //!< CoaXPress - VmbTransportLayerTypeCLHS = 6, //!< Camera Link HS - VmbTransportLayerTypeU3V = 7, //!< USB3 Vision Standard - VmbTransportLayerTypeEthernet = 8, //!< Generic Ethernet - VmbTransportLayerTypePCI = 9, //!< PCI / PCIe - VmbTransportLayerTypeCustom = 10, //!< Non standard - VmbTransportLayerTypeMixed = 11, //!< Mixed (transport layer only) -} VmbTransportLayerType; - -/** - * \brief Type for an Interface; for values see ::VmbTransportLayerType. - */ -typedef VmbUint32_t VmbTransportLayerType_t; - -/** - * \brief Transport layer information. - * - * Holds read-only information about a transport layer. - */ -typedef struct VmbTransportLayerInfo -{ - /** - * \name Out - * \{ - */ - - const char* transportLayerIdString; //!< Unique id of the transport layer - const char* transportLayerName; //!< Name of the transport layer - const char* transportLayerModelName; //!< Model name of the transport layer - const char* transportLayerVendor; //!< Vendor of the transport layer - const char* transportLayerVersion; //!< Version of the transport layer - const char* transportLayerPath; //!< Full path of the transport layer - VmbHandle_t transportLayerHandle; //!< Handle of the transport layer for feature access - VmbTransportLayerType_t transportLayerType; //!< The type of the transport layer - - /** - * \} - */ -} VmbTransportLayerInfo_t; - -/** - * \} - */ - -/** - * \name Interface - * \{ - */ - -/** - * \brief Interface information. - * - * Holds read-only information about an interface. - */ -typedef struct VmbInterfaceInfo -{ - /** - * \name Out - * \{ - */ - - const char* interfaceIdString; //!< Identifier of the interface - const char* interfaceName; //!< Interface name, given by the transport layer - VmbHandle_t interfaceHandle; //!< Handle of the interface for feature access - VmbHandle_t transportLayerHandle; //!< Handle of the related transport layer for feature access - VmbTransportLayerType_t interfaceType; //!< The technology of the interface - - /** - * \} - */ -} VmbInterfaceInfo_t; - -/** - * \} - */ - -/** - * \name Camera - * \{ - */ - - /** - * \brief Access mode for cameras. - * - * Used in ::VmbCameraInfo_t as flags, so multiple modes can be - * announced, while in ::VmbCameraOpen(), no combination must be used. - */ -typedef enum VmbAccessModeType -{ - VmbAccessModeNone = 0, //!< No access - VmbAccessModeFull = 1, //!< Read and write access - VmbAccessModeRead = 2, //!< Read-only access - VmbAccessModeUnknown = 4, //!< Access type unknown - VmbAccessModeExclusive = 8, //!< Read and write access without permitting access for other consumers -} VmbAccessModeType; - -/** - * \brief Type for an AccessMode; for values see ::VmbAccessModeType. - */ -typedef VmbUint32_t VmbAccessMode_t; - -/** - * \brief Camera information. - * - * Holds read-only information about a camera. - */ -typedef struct VmbCameraInfo -{ - /** - * \name Out - * \{ - */ - - const char* cameraIdString; //!< Identifier of the camera - const char* cameraIdExtended; //!< globally unique identifier for the camera - const char* cameraName; //!< The display name of the camera - const char* modelName; //!< Model name - const char* serialString; //!< Serial number - VmbHandle_t transportLayerHandle; //!< Handle of the related transport layer for feature access - VmbHandle_t interfaceHandle; //!< Handle of the related interface for feature access - VmbHandle_t localDeviceHandle; //!< Handle of the related GenTL local device. NULL if the camera is not opened - VmbHandle_t const* streamHandles; //!< Handles of the streams provided by the camera. NULL if the camera is not opened - VmbUint32_t streamCount; //!< Number of stream handles in the streamHandles array - VmbAccessMode_t permittedAccess; //!< Permitted access modes, see ::VmbAccessModeType - - /** - * \} - */ -} VmbCameraInfo_t; - -/** - * \} - */ - -/** - * \name Feature - * \{ - */ - -/** - * \brief Supported feature data types. - */ -typedef enum VmbFeatureDataType -{ - VmbFeatureDataUnknown = 0, //!< Unknown feature type - VmbFeatureDataInt = 1, //!< 64-bit integer feature - VmbFeatureDataFloat = 2, //!< 64-bit floating point feature - VmbFeatureDataEnum = 3, //!< Enumeration feature - VmbFeatureDataString = 4, //!< String feature - VmbFeatureDataBool = 5, //!< Boolean feature - VmbFeatureDataCommand = 6, //!< Command feature - VmbFeatureDataRaw = 7, //!< Raw (direct register access) feature - VmbFeatureDataNone = 8, //!< Feature with no data -} VmbFeatureDataType; - -/** - * \brief Data type for a Feature; for values see ::VmbFeatureDataType. - */ -typedef VmbUint32_t VmbFeatureData_t; - -/** - * \brief Feature visibility. - */ -typedef enum VmbFeatureVisibilityType -{ - VmbFeatureVisibilityUnknown = 0, //!< Feature visibility is not known - VmbFeatureVisibilityBeginner = 1, //!< Feature is visible in feature list (beginner level) - VmbFeatureVisibilityExpert = 2, //!< Feature is visible in feature list (expert level) - VmbFeatureVisibilityGuru = 3, //!< Feature is visible in feature list (guru level) - VmbFeatureVisibilityInvisible = 4, //!< Feature is visible in the feature list, but should be hidden in GUI applications -} VmbFeatureVisibilityType; - -/** - * \brief Type for Feature visibility; for values see ::VmbFeatureVisibilityType. - */ -typedef VmbUint32_t VmbFeatureVisibility_t; - -/** - * \brief Feature flags. - */ -typedef enum VmbFeatureFlagsType -{ - VmbFeatureFlagsNone = 0, //!< No additional information is provided - VmbFeatureFlagsRead = 1, //!< Static info about read access. Current status depends on access mode, check with ::VmbFeatureAccessQuery() - VmbFeatureFlagsWrite = 2, //!< Static info about write access. Current status depends on access mode, check with ::VmbFeatureAccessQuery() - VmbFeatureFlagsVolatile = 8, //!< Value may change at any time - VmbFeatureFlagsModifyWrite = 16, //!< Value may change after a write -} VmbFeatureFlagsType; - -/** - * \brief Type for Feature flags; for values see ::VmbFeatureFlagsType. - */ -typedef VmbUint32_t VmbFeatureFlags_t; - -/** - * \brief Feature information. - * - * Holds read-only information about a feature. - */ -typedef struct VmbFeatureInfo -{ - /** - * \name Out - * \{ - */ - - const char* name; //!< Name used in the API - const char* category; //!< Category this feature can be found in - const char* displayName; //!< Feature name to be used in GUIs - const char* tooltip; //!< Short description, e.g. for a tooltip - const char* description; //!< Longer description - const char* sfncNamespace; //!< Namespace this feature resides in - const char* unit; //!< Measuring unit as given in the XML file - const char* representation; //!< Representation of a numeric feature - VmbFeatureData_t featureDataType; //!< Data type of this feature - VmbFeatureFlags_t featureFlags; //!< Access flags for this feature - VmbUint32_t pollingTime; //!< Predefined polling time for volatile features - VmbFeatureVisibility_t visibility; //!< GUI visibility - VmbBool_t isStreamable; //!< Indicates if a feature can be stored to / loaded from a file - VmbBool_t hasSelectedFeatures; //!< Indicates if the feature selects other features - - /** - * \} - */ -} VmbFeatureInfo_t; - -/** - * \brief Info about possible entries of an enumeration feature. - */ -typedef struct VmbFeatureEnumEntry -{ - /** - * \name Out - * \{ - */ - - const char* name; //!< Name used in the API - const char* displayName; //!< Enumeration entry name to be used in GUIs - const char* tooltip; //!< Short description, e.g. for a tooltip - const char* description; //!< Longer description - VmbInt64_t intValue; //!< Integer value of this enumeration entry - const char* sfncNamespace; //!< Namespace this feature resides in - VmbFeatureVisibility_t visibility; //!< GUI visibility - - /** - * \} - */ -} VmbFeatureEnumEntry_t; - -/** - * \} - */ - -/** - * \name Frame - * \{ - */ - -/** - * \brief Status of a frame transfer. - */ -typedef enum VmbFrameStatusType -{ - VmbFrameStatusComplete = 0, //!< Frame has been completed without errors - VmbFrameStatusIncomplete = -1, //!< Frame could not be filled to the end - VmbFrameStatusTooSmall = -2, //!< Frame buffer was too small - VmbFrameStatusInvalid = -3, //!< Frame buffer was invalid -} VmbFrameStatusType; - -/** - * \brief Type for the frame status; for values see ::VmbFrameStatusType. - */ -typedef VmbInt32_t VmbFrameStatus_t; - -/** - * \brief Frame flags. - */ -typedef enum VmbFrameFlagsType -{ - VmbFrameFlagsNone = 0, //!< No additional information is provided - VmbFrameFlagsDimension = 1, //!< VmbFrame_t::width and VmbFrame_t::height are provided - VmbFrameFlagsOffset = 2, //!< VmbFrame_t::offsetX and VmbFrame_t::offsetY are provided (ROI) - VmbFrameFlagsFrameID = 4, //!< VmbFrame_t::frameID is provided - VmbFrameFlagsTimestamp = 8, //!< VmbFrame_t::timestamp is provided - VmbFrameFlagsImageData = 16, //!< VmbFrame_t::imageData is provided - VmbFrameFlagsPayloadType = 32, //!< VmbFrame_t::payloadType is provided - VmbFrameFlagsChunkDataPresent = 64, //!< VmbFrame_t::chunkDataPresent is set based on info provided by the transport layer -} VmbFrameFlagsType; - -/** - * \brief Type for Frame flags; for values see ::VmbFrameFlagsType. - */ -typedef VmbUint32_t VmbFrameFlags_t; - -/** - * \brief Frame payload type. - */ -typedef enum VmbPayloadType -{ - VmbPayloadTypeUnknown = 0, //!< Unknown payload type - VmbPayloadTypeImage = 1, //!< image data - VmbPayloadTypeRaw = 2, //!< raw data - VmbPayloadTypeFile = 3, //!< file data - VmbPayloadTypeJPEG = 5, //!< JPEG data as described in the GigEVision 2.0 specification - VmbPayloadTypJPEG2000 = 6, //!< JPEG 2000 data as described in the GigEVision 2.0 specification - VmbPayloadTypeH264 = 7, //!< H.264 data as described in the GigEVision 2.0 specification - VmbPayloadTypeChunkOnly = 8, //!< Chunk data exclusively - VmbPayloadTypeDeviceSpecific = 9, //!< Device specific data format - VmbPayloadTypeGenDC = 11, //!< GenDC data -} VmbPayloadType; - -/** - * \brief Type representing the payload type of a frame. For values see ::VmbPayloadType. - */ -typedef VmbUint32_t VmbPayloadType_t; - -/** - * \brief Type used to represent a dimension value, e.g. the image height. - */ -typedef VmbUint32_t VmbImageDimension_t; - -/** - * \brief Frame delivered by the camera. - */ -typedef struct VmbFrame -{ - /** - * \name In - * \{ - */ - - void* buffer; //!< Comprises image and potentially chunk data - VmbUint32_t bufferSize; //!< The size of the data buffer - void* context[4]; //!< 4 void pointers that can be employed by the user (e.g. for storing handles) - - /** - * \} - */ - - /** - * \name Out - * \{ - */ - - VmbFrameStatus_t receiveStatus; //!< The resulting status of the receive operation - VmbUint64_t frameID; //!< Unique ID of this frame in this stream - VmbUint64_t timestamp; //!< The timestamp set by the camera - VmbUint8_t* imageData; //!< The start of the image data, if present, or null - VmbFrameFlags_t receiveFlags; //!< Flags indicating which additional frame information is available - VmbPixelFormat_t pixelFormat; //!< Pixel format of the image - VmbImageDimension_t width; //!< Width of an image - VmbImageDimension_t height; //!< Height of an image - VmbImageDimension_t offsetX; //!< Horizontal offset of an image - VmbImageDimension_t offsetY; //!< Vertical offset of an image - VmbPayloadType_t payloadType; //!< The type of payload - VmbBool_t chunkDataPresent; //!< True if the transport layer reported chunk data to be present in the buffer - - /** - * \} - */ -} VmbFrame_t; - -/** - * \} - */ - -/** - * \name Save/LoadSettings - * \{ - */ - -/** - * \brief Type of features that are to be saved (persisted) to the XML file when using ::VmbSettingsSave - */ -typedef enum VmbFeaturePersistType -{ - VmbFeaturePersistAll = 0, //!< Save all features to XML, including look-up tables (if possible) - VmbFeaturePersistStreamable = 1, //!< Save only features marked as streamable, excluding look-up tables - VmbFeaturePersistNoLUT = 2 //!< Save all features except look-up tables (default) -} VmbFeaturePersistType; - -/** - * \brief Type for feature persistence; for values see ::VmbFeaturePersistType. - */ -typedef VmbUint32_t VmbFeaturePersist_t; - -/** - * \brief Parameters determining the operation mode of ::VmbSettingsSave and ::VmbSettingsLoad. - */ -typedef enum VmbModulePersistFlagsType -{ - VmbModulePersistFlagsNone = 0x00, //!< Persist/Load features for no module. - VmbModulePersistFlagsTransportLayer = 0x01, //!< Persist/Load the transport layer features. - VmbModulePersistFlagsInterface = 0x02, //!< Persist/Load the interface features. - VmbModulePersistFlagsRemoteDevice = 0x04, //!< Persist/Load the remote device features. - VmbModulePersistFlagsLocalDevice = 0x08, //!< Persist/Load the local device features. - VmbModulePersistFlagsStreams = 0x10, //!< Persist/Load the features of stream modules. - VmbModulePersistFlagsAll = 0xff //!< Persist/Load features for all modules. -} VmbModulePersistFlagsType; - -/** - * \brief Type for module persist flags; for values see VmbModulePersistFlagsType - * - * Use a combination of ::VmbModulePersistFlagsType constants - */ -typedef VmbUint32_t VmbModulePersistFlags_t; - -/** - * \brief A level to use for logging - */ -typedef enum VmbLogLevel -{ - VmbLogLevelNone = 0, //!< Nothing is logged regardless of the severity of the issue - VmbLogLevelError, //!< Only errors are logged - VmbLogLevelDebug, //!< Only error and debug messages are logged - VmbLogLevelWarn, //!< Only error, debug and warn messages are logged - VmbLogLevelTrace, //!< all messages are logged - VmbLogLevelAll = VmbLogLevelTrace, //!< all messages are logged -} VmbLogLevel; - -/** - * \brief The type used for storing the log level - * - * Use a constant from ::VmbLogLevel - */ -typedef VmbUint32_t VmbLogLevel_t; - -/** - * \brief Parameters determining the operation mode of ::VmbSettingsSave and ::VmbSettingsLoad - */ -typedef struct VmbFeaturePersistSettings -{ - /** - * \name In - * \{ - */ - - VmbFeaturePersist_t persistType; //!< Type of features that are to be saved - VmbModulePersistFlags_t modulePersistFlags; //!< Flags specifying the modules to persist/load - VmbUint32_t maxIterations; //!< Number of iterations when loading settings - VmbLogLevel_t loggingLevel; //!< Determines level of detail for load/save settings logging - - /** - * \} - */ -} VmbFeaturePersistSettings_t; - -/** - * \} - */ - -/** - * \name Callbacks - * \{ - */ - -/** - * \brief Invalidation callback type for a function that gets called in a separate thread - * and has been registered with ::VmbFeatureInvalidationRegister(). - * - * While the callback is run, all feature data is atomic. After the callback finishes, - * the feature data may be updated with new values. - * - * Do not spend too much time in this thread; it prevents the feature values - * from being updated from any other thread or the lower-level drivers. - * - * \param[in] handle Handle for an entity that exposes features - * \param[in] name Name of the feature - * \param[in] userContext Pointer to the user context, see ::VmbFeatureInvalidationRegister - */ -typedef void (VMB_CALL* VmbInvalidationCallback)(const VmbHandle_t handle, const char* name, void* userContext); - -/** - * \brief Frame Callback type for a function that gets called in a separate thread - * if a frame has been queued with ::VmbCaptureFrameQueue. - * - * \warning Any operations closing the stream including ::VmbShutdown and ::VmbCameraClose in addition to - * ::VmbCaptureEnd block until any currently active callbacks return. If the callback does not - * return in finite time, the program may not return. - * - * \param[in] cameraHandle Handle of the camera the frame belongs to - * \param[in] streamHandle Handle of the stream the frame belongs to - * \param[in] frame The received frame - */ -typedef void (VMB_CALL* VmbFrameCallback)(const VmbHandle_t cameraHandle, const VmbHandle_t streamHandle, VmbFrame_t* frame); - -/** - * \brief Function pointer type to access chunk data - * - * This function should complete as quickly as possible, since it blocks other updates on the - * remote device. - * - * This function should not throw exceptions, even if VmbC is used from C++. Any exception - * thrown will only result in an error code indicating that an exception was thrown. - * - * \param[in] featureAccessHandle A special handle that can be used for accessing features; - * the handle is only valid during the call of the function. - * \param[in] userContext The value the user passed to ::VmbChunkDataAccess. - * - * \return An error to be returned from ::VmbChunkDataAccess in the absence of other errors; - * A custom exit code >= ::VmbErrorCustom can be returned to indicate a failure via - * ::VmbChunkDataAccess return code - */ -typedef VmbError_t(VMB_CALL* VmbChunkAccessCallback)(VmbHandle_t featureAccessHandle, void* userContext); - -/** - * \} - */ - -#ifdef __cplusplus -} -#endif - -#endif // VMBC_TYPE_DEFINITIONS_H_INCLUDE_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCommonTypes.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCommonTypes.h deleted file mode 100644 index 7e3615472..000000000 --- a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbCommonTypes.h +++ /dev/null @@ -1,444 +0,0 @@ -/*============================================================================= - Copyright (C) 2012 - 2021 Allied Vision Technologies. All Rights Reserved. - - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. - -------------------------------------------------------------------------------- - - File: VmbCommonTypes.h - -------------------------------------------------------------------------------- - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, - NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=============================================================================*/ - -/** - * \file - * \brief Main header file for the common types of the APIs. - * - * This file describes all necessary definitions for types used within - * the Vmb APIs. These type definitions are designed to be - * portable from other languages and other operating systems. - */ - -#ifndef VMBCOMMONTYPES_H_INCLUDE_ -#define VMBCOMMONTYPES_H_INCLUDE_ - -#ifdef _WIN32 -# include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * \name Basic Types - * \{ - */ - -#if defined (_MSC_VER) - - /** - * \brief 8-bit signed integer. - */ - typedef __int8 VmbInt8_t; - - /** - * \brief 8-bit unsigned integer. - */ - typedef unsigned __int8 VmbUint8_t; - - /** - * \brief 16-bit signed integer. - */ - typedef __int16 VmbInt16_t; - - /** - * \brief 16-bit unsigned integer. - */ - typedef unsigned __int16 VmbUint16_t; - - /** - * \brief 32-bit signed integer. - */ - typedef __int32 VmbInt32_t; - - /** - * \brief 32-bit unsigned integer. - */ - typedef unsigned __int32 VmbUint32_t; - - /** - * \brief 64-bit signed integer. - */ - typedef __int64 VmbInt64_t; - - /** - * \brief 64-bit unsigned integer. - */ - typedef unsigned __int64 VmbUint64_t; - -#else - - /** - * \brief 8-bit signed integer. - */ - typedef signed char VmbInt8_t; - - /** - * \brief 8-bit unsigned integer. - */ - typedef unsigned char VmbUint8_t; - - /** - * \brief 16-bit signed integer. - */ - typedef short VmbInt16_t; - - /** - * \brief 16-bit unsigned integer. - */ - typedef unsigned short VmbUint16_t; - - /** - * \brief 32-bit signed integer. - */ - typedef int VmbInt32_t; - - /** - * \brief 32-bit unsigned integer. - */ - typedef unsigned int VmbUint32_t; - - /** - * \brief 64-bit signed integer. - */ - typedef long long VmbInt64_t; - - /** - * \brief 64-bit unsigned integer. - */ - typedef unsigned long long VmbUint64_t; - -#endif - - /** - * \brief Handle, e.g. for a camera. - */ - typedef void* VmbHandle_t; - -#if defined(__cplusplus) || defined(__bool_true_false_are_defined) - - /** - * \brief Standard type for boolean values. - */ - typedef bool VmbBool_t; - -#else - - /** - * \brief Boolean type (equivalent to char). - * - * For values see ::VmbBoolVal - */ - typedef char VmbBool_t; - -#endif - - /** - * \brief enum for bool values. - */ - typedef enum VmbBoolVal - { - VmbBoolTrue = 1, - VmbBoolFalse = 0, - } VmbBoolVal; - - /** - * \brief char type. - */ - typedef unsigned char VmbUchar_t; - -#ifdef _WIN32 - - /** - * \brief Character type used for file paths (Windows uses wchar_t not char). - */ - typedef wchar_t VmbFilePathChar_t; - - /** - * \brief macro for converting a c string literal into a system dependent string literal - * - * Adds L as prefix on Windows and is replaced by the unmodified value on other operating systems. - * - * \code{.c} - * const VmbFilePathChar_t* path = VMB_FILE_PATH_LITERAL("./some/path/tl.cti"); - * \endcode - */ -# define VMB_FILE_PATH_LITERAL(value) L##value - -#else - - /** - * Character type used for file paths - */ - typedef char VmbFilePathChar_t; - - /** - * \brief macro for converting a c string literal into a system dependent string literal - * - * Adds L as prefix on Windows and is replaced by the unmodified value on other operating systems. - * - * \code{.c} - * const VmbFilePathChar_t* path = VMB_FILE_PATH_LITERAL("./some/path/tl.cti"); - * \endcode - */ -# define VMB_FILE_PATH_LITERAL(value) value -#endif - -/** - * \} - */ - -/** - * \name Error Codes - * \{ - */ - - /** - * \brief Error codes, returned by most functions. - */ - typedef enum VmbErrorType - { - VmbErrorSuccess = 0, //!< No error - VmbErrorInternalFault = -1, //!< Unexpected fault in VmbC or driver - VmbErrorApiNotStarted = -2, //!< ::VmbStartup() was not called before the current command - VmbErrorNotFound = -3, //!< The designated instance (camera, feature etc.) cannot be found - VmbErrorBadHandle = -4, //!< The given handle is not valid - VmbErrorDeviceNotOpen = -5, //!< Device was not opened for usage - VmbErrorInvalidAccess = -6, //!< Operation is invalid with the current access mode - VmbErrorBadParameter = -7, //!< One of the parameters is invalid (usually an illegal pointer) - VmbErrorStructSize = -8, //!< The given struct size is not valid for this version of the API - VmbErrorMoreData = -9, //!< More data available in a string/list than space is provided - VmbErrorWrongType = -10, //!< Wrong feature type for this access function - VmbErrorInvalidValue = -11, //!< The value is not valid; either out of bounds or not an increment of the minimum - VmbErrorTimeout = -12, //!< Timeout during wait - VmbErrorOther = -13, //!< Other error - VmbErrorResources = -14, //!< Resources not available (e.g. memory) - VmbErrorInvalidCall = -15, //!< Call is invalid in the current context (e.g. callback) - VmbErrorNoTL = -16, //!< No transport layers are found - VmbErrorNotImplemented = -17, //!< API feature is not implemented - VmbErrorNotSupported = -18, //!< API feature is not supported - VmbErrorIncomplete = -19, //!< The current operation was not completed (e.g. a multiple registers read or write) - VmbErrorIO = -20, //!< Low level IO error in transport layer - VmbErrorValidValueSetNotPresent = -21, //!< The valid value set could not be retrieved, since the feature does not provide this property - VmbErrorGenTLUnspecified = -22, //!< Unspecified GenTL runtime error - VmbErrorUnspecified = -23, //!< Unspecified runtime error - VmbErrorBusy = -24, //!< The responsible module/entity is busy executing actions - VmbErrorNoData = -25, //!< The function has no data to work on - VmbErrorParsingChunkData = -26, //!< An error occurred parsing a buffer containing chunk data - VmbErrorInUse = -27, //!< Something is already in use - VmbErrorUnknown = -28, //!< Error condition unknown - VmbErrorXml = -29, //!< Error parsing XML - VmbErrorNotAvailable = -30, //!< Something is not available - VmbErrorNotInitialized = -31, //!< Something is not initialized - VmbErrorInvalidAddress = -32, //!< The given address is out of range or invalid for internal reasons - VmbErrorAlready = -33, //!< Something has already been done - VmbErrorNoChunkData = -34, //!< A frame expected to contain chunk data does not contain chunk data - VmbErrorUserCallbackException = -35, //!< A callback provided by the user threw an exception - VmbErrorFeaturesUnavailable = -36, //!< The XML for the module is currently not loaded; the module could be in the wrong state or the XML could not be retrieved or could not be parsed properly - VmbErrorTLNotFound = -37, //!< A required transport layer could not be found or loaded - VmbErrorAmbiguous = -39, //!< An entity cannot be uniquely identified based on the information provided - VmbErrorRetriesExceeded = -40, //!< Something could not be accomplished with a given number of retries - VmbErrorInsufficientBufferCount = -41, //!< The operation requires more buffers - VmbErrorCustom = 1, //!< The minimum error code to use for user defined error codes to avoid conflict with existing error codes - } VmbErrorType; - - /** - * \brief Type for an error returned by API methods; for values see ::VmbErrorType. - */ - typedef VmbInt32_t VmbError_t; - -/** - * \} - */ - -/** - * \name Version - * \{ - */ - - /** - * \brief Version information. - */ - typedef struct VmbVersionInfo - { - /** - * \name Out - * \{ - */ - - VmbUint32_t major; //!< Major version number - VmbUint32_t minor; //!< Minor version number - VmbUint32_t patch; //!< Patch version number - - /** - * \} - */ - } VmbVersionInfo_t; - -/** - * \} - */ - - /** - * \name Pixel information - * \{ - */ - - /** - * \brief Indicates if pixel is monochrome or RGB. - */ - typedef enum VmbPixelType - { - VmbPixelMono = 0x01000000, //!< Monochrome pixel - VmbPixelColor = 0x02000000 //!< Pixel bearing color information - } VmbPixelType; - - /** - * \brief Indicates number of bits for a pixel. Needed for building values of ::VmbPixelFormatType. - */ - typedef enum VmbPixelOccupyType - { - VmbPixelOccupy8Bit = 0x00080000, //!< Pixel effectively occupies 8 bits - VmbPixelOccupy10Bit = 0x000A0000, //!< Pixel effectively occupies 10 bits - VmbPixelOccupy12Bit = 0x000C0000, //!< Pixel effectively occupies 12 bits - VmbPixelOccupy14Bit = 0x000E0000, //!< Pixel effectively occupies 14 bits - VmbPixelOccupy16Bit = 0x00100000, //!< Pixel effectively occupies 16 bits - VmbPixelOccupy24Bit = 0x00180000, //!< Pixel effectively occupies 24 bits - VmbPixelOccupy32Bit = 0x00200000, //!< Pixel effectively occupies 32 bits - VmbPixelOccupy48Bit = 0x00300000, //!< Pixel effectively occupies 48 bits - VmbPixelOccupy64Bit = 0x00400000, //!< Pixel effectively occupies 64 bits - } VmbPixelOccupyType; - - /** - * \brief Pixel format types. - * As far as possible, the Pixel Format Naming Convention (PFNC) has been followed, allowing a few deviations. - * If data spans more than one byte, it is always LSB aligned, except if stated differently. - */ - typedef enum VmbPixelFormatType - { - // mono formats - VmbPixelFormatMono8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x0001, //!< Monochrome, 8 bits (PFNC: Mono8) - VmbPixelFormatMono10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0003, //!< Monochrome, 10 bits in 16 bits (PFNC: Mono10) - VmbPixelFormatMono10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0046, //!< Monochrome, 10 bits in 16 bits (PFNC: Mono10p) - VmbPixelFormatMono12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0005, //!< Monochrome, 12 bits in 16 bits (PFNC: Mono12) - VmbPixelFormatMono12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x0006, //!< Monochrome, 2x12 bits in 24 bits (GEV:Mono12Packed) - VmbPixelFormatMono12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0047, //!< Monochrome, 2x12 bits in 24 bits (PFNC: MonoPacked) - VmbPixelFormatMono14 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0025, //!< Monochrome, 14 bits in 16 bits (PFNC: Mono14) - VmbPixelFormatMono16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0007, //!< Monochrome, 16 bits (PFNC: Mono16) - - // bayer formats - VmbPixelFormatBayerGR8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x0008, //!< Bayer-color, 8 bits, starting with GR line (PFNC: BayerGR8) - VmbPixelFormatBayerRG8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x0009, //!< Bayer-color, 8 bits, starting with RG line (PFNC: BayerRG8) - VmbPixelFormatBayerGB8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x000A, //!< Bayer-color, 8 bits, starting with GB line (PFNC: BayerGB8) - VmbPixelFormatBayerBG8 = VmbPixelMono | VmbPixelOccupy8Bit | 0x000B, //!< Bayer-color, 8 bits, starting with BG line (PFNC: BayerBG8) - VmbPixelFormatBayerGR10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x000C, //!< Bayer-color, 10 bits in 16 bits, starting with GR line (PFNC: BayerGR10) - VmbPixelFormatBayerRG10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x000D, //!< Bayer-color, 10 bits in 16 bits, starting with RG line (PFNC: BayerRG10) - VmbPixelFormatBayerGB10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x000E, //!< Bayer-color, 10 bits in 16 bits, starting with GB line (PFNC: BayerGB10) - VmbPixelFormatBayerBG10 = VmbPixelMono | VmbPixelOccupy16Bit | 0x000F, //!< Bayer-color, 10 bits in 16 bits, starting with BG line (PFNC: BayerBG10) - VmbPixelFormatBayerGR12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0010, //!< Bayer-color, 12 bits in 16 bits, starting with GR line (PFNC: BayerGR12) - VmbPixelFormatBayerRG12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0011, //!< Bayer-color, 12 bits in 16 bits, starting with RG line (PFNC: BayerRG12) - VmbPixelFormatBayerGB12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0012, //!< Bayer-color, 12 bits in 16 bits, starting with GB line (PFNC: BayerGB12) - VmbPixelFormatBayerBG12 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0013, //!< Bayer-color, 12 bits in 16 bits, starting with BG line (PFNC: BayerBG12) - VmbPixelFormatBayerGR12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x002A, //!< Bayer-color, 2x12 bits in 24 bits, starting with GR line (GEV:BayerGR12Packed) - VmbPixelFormatBayerRG12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x002B, //!< Bayer-color, 2x12 bits in 24 bits, starting with RG line (GEV:BayerRG12Packed) - VmbPixelFormatBayerGB12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x002C, //!< Bayer-color, 2x12 bits in 24 bits, starting with GB line (GEV:BayerGB12Packed) - VmbPixelFormatBayerBG12Packed = VmbPixelMono | VmbPixelOccupy12Bit | 0x002D, //!< Bayer-color, 2x12 bits in 24 bits, starting with BG line (GEV:BayerBG12Packed) - VmbPixelFormatBayerGR10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0056, //!< Bayer-color, 10 bits continuous packed, starting with GR line (PFNC: BayerGR10p) - VmbPixelFormatBayerRG10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0058, //!< Bayer-color, 10 bits continuous packed, starting with RG line (PFNC: BayerRG10p) - VmbPixelFormatBayerGB10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0054, //!< Bayer-color, 10 bits continuous packed, starting with GB line (PFNC: BayerGB10p) - VmbPixelFormatBayerBG10p = VmbPixelMono | VmbPixelOccupy10Bit | 0x0052, //!< Bayer-color, 10 bits continuous packed, starting with BG line (PFNC: BayerBG10p) - VmbPixelFormatBayerGR12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0057, //!< Bayer-color, 12 bits continuous packed, starting with GR line (PFNC: BayerGR12p) - VmbPixelFormatBayerRG12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0059, //!< Bayer-color, 12 bits continuous packed, starting with RG line (PFNC: BayerRG12p) - VmbPixelFormatBayerGB12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0055, //!< Bayer-color, 12 bits continuous packed, starting with GB line (PFNC: BayerGB12p) - VmbPixelFormatBayerBG12p = VmbPixelMono | VmbPixelOccupy12Bit | 0x0053, //!< Bayer-color, 12 bits continuous packed, starting with BG line (PFNC: BayerBG12p) - VmbPixelFormatBayerGR16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x002E, //!< Bayer-color, 16 bits, starting with GR line (PFNC: BayerGR16) - VmbPixelFormatBayerRG16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x002F, //!< Bayer-color, 16 bits, starting with RG line (PFNC: BayerRG16) - VmbPixelFormatBayerGB16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0030, //!< Bayer-color, 16 bits, starting with GB line (PFNC: BayerGB16) - VmbPixelFormatBayerBG16 = VmbPixelMono | VmbPixelOccupy16Bit | 0x0031, //!< Bayer-color, 16 bits, starting with BG line (PFNC: BayerBG16) - - // rgb formats - VmbPixelFormatRgb8 = VmbPixelColor | VmbPixelOccupy24Bit | 0x0014, //!< RGB, 8 bits x 3 (PFNC: RGB8) - VmbPixelFormatBgr8 = VmbPixelColor | VmbPixelOccupy24Bit | 0x0015, //!< BGR, 8 bits x 3 (PFNC: BGR8) - VmbPixelFormatRgb10 = VmbPixelColor | VmbPixelOccupy48Bit | 0x0018, //!< RGB, 12 bits in 16 bits x 3 (PFNC: RGB12) - VmbPixelFormatBgr10 = VmbPixelColor | VmbPixelOccupy48Bit | 0x0019, //!< RGB, 12 bits in 16 bits x 3 (PFNC: RGB12) - VmbPixelFormatRgb12 = VmbPixelColor | VmbPixelOccupy48Bit | 0x001A, //!< RGB, 12 bits in 16 bits x 3 (PFNC: RGB12) - VmbPixelFormatBgr12 = VmbPixelColor | VmbPixelOccupy48Bit | 0x001B, //!< RGB, 12 bits in 16 bits x 3 (PFNC: RGB12) - VmbPixelFormatRgb14 = VmbPixelColor | VmbPixelOccupy48Bit | 0x005E, //!< RGB, 14 bits in 16 bits x 3 (PFNC: RGB12) - VmbPixelFormatBgr14 = VmbPixelColor | VmbPixelOccupy48Bit | 0x004A, //!< RGB, 14 bits in 16 bits x 3 (PFNC: RGB12) - VmbPixelFormatRgb16 = VmbPixelColor | VmbPixelOccupy48Bit | 0x0033, //!< RGB, 16 bits x 3 (PFNC: RGB16) - VmbPixelFormatBgr16 = VmbPixelColor | VmbPixelOccupy48Bit | 0x004B, //!< RGB, 16 bits x 3 (PFNC: RGB16) - - // rgba formats - VmbPixelFormatArgb8 = VmbPixelColor | VmbPixelOccupy32Bit | 0x0016, //!< ARGB, 8 bits x 4 (PFNC: RGBa8) - VmbPixelFormatRgba8 = VmbPixelFormatArgb8, //!< RGBA, 8 bits x 4, legacy name - VmbPixelFormatBgra8 = VmbPixelColor | VmbPixelOccupy32Bit | 0x0017, //!< BGRA, 8 bits x 4 (PFNC: BGRa8) - VmbPixelFormatRgba10 = VmbPixelColor | VmbPixelOccupy64Bit | 0x005F, //!< RGBA, 8 bits x 4, legacy name - VmbPixelFormatBgra10 = VmbPixelColor | VmbPixelOccupy64Bit | 0x004C, //!< RGBA, 8 bits x 4, legacy name - VmbPixelFormatRgba12 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0061, //!< RGBA, 8 bits x 4, legacy name - VmbPixelFormatBgra12 = VmbPixelColor | VmbPixelOccupy64Bit | 0x004E, //!< RGBA, 8 bits x 4, legacy name - VmbPixelFormatRgba14 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0063, //!< RGBA, 8 bits x 4, legacy name - VmbPixelFormatBgra14 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0050, //!< RGBA, 8 bits x 4, legacy name - VmbPixelFormatRgba16 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0064, //!< RGBA, 8 bits x 4, legacy name - VmbPixelFormatBgra16 = VmbPixelColor | VmbPixelOccupy64Bit | 0x0051, //!< RGBA, 8 bits x 4, legacy name - - // yuv/ycbcr formats - VmbPixelFormatYuv411 = VmbPixelColor | VmbPixelOccupy12Bit | 0x001E, //!< YUV 4:1:1 with 8 bits (PFNC: YUV411_8_UYYVYY, GEV:YUV411Packed) - VmbPixelFormatYuv422 = VmbPixelColor | VmbPixelOccupy16Bit | 0x001F, //!< YUV 4:2:2 with 8 bits (PFNC: YUV422_8_UYVY, GEV:YUV422Packed) - VmbPixelFormatYuv444 = VmbPixelColor | VmbPixelOccupy24Bit | 0x0020, //!< YUV 4:4:4 with 8 bits (PFNC: YUV8_UYV, GEV:YUV444Packed) - VmbPixelFormatYuv422_8 = VmbPixelColor | VmbPixelOccupy16Bit | 0x0032, //!< YUV 4:2:2 with 8 bits Channel order YUYV (PFNC: YUV422_8) - VmbPixelFormatYCbCr8_CbYCr = VmbPixelColor | VmbPixelOccupy24Bit | 0x003A, //!< YCbCr 4:4:4 with 8 bits (PFNC: YCbCr8_CbYCr) - identical to VmbPixelFormatYuv444 - VmbPixelFormatYCbCr422_8 = VmbPixelColor | VmbPixelOccupy16Bit | 0x003B, //!< YCbCr 4:2:2 8-bit YCbYCr (PFNC: YCbCr422_8) - VmbPixelFormatYCbCr411_8_CbYYCrYY = VmbPixelColor | VmbPixelOccupy12Bit | 0x003C, //!< YCbCr 4:1:1 with 8 bits (PFNC: YCbCr411_8_CbYYCrYY) - identical to VmbPixelFormatYuv411 - VmbPixelFormatYCbCr601_8_CbYCr = VmbPixelColor | VmbPixelOccupy24Bit | 0x003D, //!< YCbCr601 4:4:4 8-bit CbYCrt (PFNC: YCbCr601_8_CbYCr) - VmbPixelFormatYCbCr601_422_8 = VmbPixelColor | VmbPixelOccupy16Bit | 0x003E, //!< YCbCr601 4:2:2 8-bit YCbYCr (PFNC: YCbCr601_422_8) - VmbPixelFormatYCbCr601_411_8_CbYYCrYY = VmbPixelColor | VmbPixelOccupy12Bit | 0x003F, //!< YCbCr601 4:1:1 8-bit CbYYCrYY (PFNC: YCbCr601_411_8_CbYYCrYY) - VmbPixelFormatYCbCr709_8_CbYCr = VmbPixelColor | VmbPixelOccupy24Bit | 0x0040, //!< YCbCr709 4:4:4 8-bit CbYCr (PFNC: YCbCr709_8_CbYCr) - VmbPixelFormatYCbCr709_422_8 = VmbPixelColor | VmbPixelOccupy16Bit | 0x0041, //!< YCbCr709 4:2:2 8-bit YCbYCr (PFNC: YCbCr709_422_8) - VmbPixelFormatYCbCr709_411_8_CbYYCrYY = VmbPixelColor | VmbPixelOccupy12Bit | 0x0042, //!< YCbCr709 4:1:1 8-bit CbYYCrYY (PFNC: YCbCr709_411_8_CbYYCrYY) - VmbPixelFormatYCbCr422_8_CbYCrY = VmbPixelColor | VmbPixelOccupy16Bit | 0x0043, //!< YCbCr 4:2:2 with 8 bits (PFNC: YCbCr422_8_CbYCrY) - identical to VmbPixelFormatYuv422 - VmbPixelFormatYCbCr601_422_8_CbYCrY = VmbPixelColor | VmbPixelOccupy16Bit | 0x0044, //!< YCbCr601 4:2:2 8-bit CbYCrY (PFNC: YCbCr601_422_8_CbYCrY) - VmbPixelFormatYCbCr709_422_8_CbYCrY = VmbPixelColor | VmbPixelOccupy16Bit | 0x0045, //!< YCbCr709 4:2:2 8-bit CbYCrY (PFNC: YCbCr709_422_8_CbYCrY) - VmbPixelFormatYCbCr411_8 = VmbPixelColor | VmbPixelOccupy12Bit | 0x005A, //!< YCbCr 4:1:1 8-bit YYCbYYCr (PFNC: YCbCr411_8) - VmbPixelFormatYCbCr8 = VmbPixelColor | VmbPixelOccupy24Bit | 0x005B, //!< YCbCr 4:4:4 8-bit YCbCr (PFNC: YCbCr8) - - VmbPixelFormatLast, - } VmbPixelFormatType; - - /** - * \brief Type for the pixel format; for values see ::VmbPixelFormatType. - */ - typedef VmbUint32_t VmbPixelFormat_t; - -/** - * \} - */ - -#ifdef __cplusplus -} -#endif - -#endif // VMBCOMMONTYPES_H_INCLUDE_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbConstants.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbConstants.h deleted file mode 100644 index 95ebeb690..000000000 --- a/DeviceAdapters/AlliedVisionCamera/SDK/VmbC/VmbConstants.h +++ /dev/null @@ -1,85 +0,0 @@ -/*============================================================================= - Copyright (C) 2021 Allied Vision Technologies. All Rights Reserved. - - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. - -------------------------------------------------------------------------------- - - File: VmbConstants.h - -------------------------------------------------------------------------------- - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, - NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=============================================================================*/ - -/** - * \file - * \brief File containing constants used in the Vmb C API. - */ - -#ifndef VMBCONSTANTS_H_INCLUDE_ -#define VMBCONSTANTS_H_INCLUDE_ - -#ifdef _WIN32 -/** - * \brief the character used to separate file paths in the parameter of ::VmbStartup - */ -#define VMB_PATH_SEPARATOR_CHAR L';' - -/** - * \brief the string used to separate file paths in the parameter of ::VmbStartup - */ -#define VMB_PATH_SEPARATOR_STRING L";" -#else -/** - * \brief the character used to separate file paths in the parameter of ::VmbStartup - */ -#define VMB_PATH_SEPARATOR_CHAR ':' - -/** - * \brief the string used to separate file paths in the parameter of ::VmbStartup - */ -#define VMB_PATH_SEPARATOR_STRING ":" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * \defgroup SfncNamespaces Sfnc Namespace Constants - * \{ - */ - -/** - * \brief The C string identifying the namespace of features not defined in the SFNC - * standard. - */ -#define VMB_SFNC_NAMESPACE_CUSTOM "Custom" - -/** - * \brief The C string identifying the namespace of features defined in the SFNC - * standard. - */ -#define VMB_SFNC_NAMESPACE_STANDARD "Standard" - -/** - * \} - */ - -#ifdef __cplusplus -} -#endif - -#endif // VMBCONSTANTS_H_INCLUDE_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransform.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransform.h deleted file mode 100644 index 77d497eca..000000000 --- a/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransform.h +++ /dev/null @@ -1,336 +0,0 @@ -/*============================================================================= - Copyright (C) 2012 - 2021 Allied Vision Technologies. All Rights Reserved. - - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. - -------------------------------------------------------------------------------- - - File: VmbTransform.h - - Description: Definition of image transform functions for the Vmb APIs. - -------------------------------------------------------------------------------- - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, - NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=============================================================================*/ - -/** - * \file - */ -#ifndef VMB_TRANSFORM_H_ -#define VMB_TRANSFORM_H_ -#ifndef VMB_TRANSFORM -#define VMB_TRANSFORM -#endif - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef VMBIMAGETRANSFORM_API -# ifndef VMB_NO_EXPORT -# ifdef VMB_EXPORTS -# if defined(__ELF__) && (defined(__clang__) || defined(__GNUC__)) -# define VMBIMAGETRANSFORM_API __attribute__((visibility("default"))) -# elif defined( __APPLE__ ) || defined(__MACH__) -# define VMBIMAGETRANSFORM_API __attribute__((visibility("default"))) -# else -# ifndef _WIN64 -# define VMBIMAGETRANSFORM_API __declspec(dllexport) __stdcall -# else -# define VMBIMAGETRANSFORM_API __stdcall -# endif -# endif -# else -# if defined (__ELF__) && (defined(__clang__) || defined(__GNUC__)) -# define VMBIMAGETRANSFORM_API -# elif defined( __APPLE__ ) || defined(__MACH__) -# define VMBIMAGETRANSFORM_API -# else -# define VMBIMAGETRANSFORM_API __declspec(dllimport) __stdcall -# endif -# endif -# else -# define VMBIMAGETRANSFORM_API -# endif -#endif - -/** - * \brief Inquire the library version. - * - * \param[out] value Contains the library version (Major,Minor,Sub,Build). - * - * This function can be called at anytime, even before the library is initialized. - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter \p value is null. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbGetImageTransformVersion ( VmbUint32_t* value ); - -/** - * \brief Get information about processor supported features. - * - * This should be called before using any SIMD (MMX,SSE) optimized functions. - * - * \param[out] technoInfo Returns the supported SIMD technologies. - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter If \p technoInfo is null. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbGetTechnoInfo( VmbTechInfo_t* technoInfo ); - -/** - * \brief Translate an Vmb error code to a human-readable string. - * - * \param[in] errorCode The error code to get a readable string for. - * \param[out] info Pointer to a zero terminated string the error description is written to. - * \param[in] maxInfoLength The size of the \p info buffer. - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter \p info is null, or if maxInfoLength is 0. - * - * \retval ::VmbErrorMoreData If \p maxInfoLength is too small to hold the complete information. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbGetErrorInfo(VmbError_t errorCode, - VmbANSIChar_t* info, - VmbUint32_t maxInfoLength ); - -/** - * \brief Get information about the currently loaded Vmb ImageTransform API. - * - * - * \p infoType may be one of the following values: - * - ::VmbAPIInfoAll: Returns all information about the API - * - ::VmbAPIInfoPlatform: Returns information about the platform the API was built for (x86 or x64) - * - ::VmbAPIInfoBuild: Returns info about the API built (debug or release) - * - ::VmbAPIInfoTechnology: Returns info about the supported technologies the API was built for (OpenMP or OpenCL) - * - * \param[in] infoType Type of information to return - * \param[out] info Pointer to a zero terminated string that - * \param[in] maxInfoLength The length of the \p info buffer - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter \p info is null. - * - * \retval ::VmbErrorMoreData If chars are insufficient \p maxInfoLength to hold the complete information. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbGetApiInfoString(VmbAPIInfo_t infoType, - VmbANSIChar_t* info, - VmbUint32_t maxInfoLength ); - -/** - * \brief Set transformation options to a predefined debayering mode. - * - * The default mode is 2x2 debayering. Debayering modes only work for image widths and heights - * divisible by two. - * - * \param[in] debayerMode The mode used for debayering the raw source image. - * - * \param[in,out] transformInfo Parameter that contains information about special - * transform functionality - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter \p transformInfo is null. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbSetDebayerMode(VmbDebayerMode_t debayerMode, - VmbTransformInfo* transformInfo ); - -/** - * \brief Set transformation options to a 3x3 color matrix transformation. - * - * \param[in] matrix Color correction matrix. - * - * \param[in,out] transformInfo Parameter that is filled with information - * about special transform functionality. - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter If \p matrix or \p transformInfo are null. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbSetColorCorrectionMatrix3x3(const VmbFloat_t* matrix, - VmbTransformInfo* transformInfo ); - -/** - * \brief Initialize the give VmbTransformInfo with gamma correction information. - * - * \param[in] gamma Float gamma correction to set - * \param[in,out] transformInfo Transform info to set gamma correction to - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter \p transformInfo is null. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbSetGammaCorrection(VmbFloat_t gamma, - VmbTransformInfo* transformInfo ); - -/** - * \brief Set the pixel related info of a VmbImage to the values appropriate for the given pixel format. - * - * A VmbPixelFormat_t can be obtained from Vmb C/C++ APIs frame. - * For displaying images, it is suggested to use ::VmbSetImageInfoFromString() or to look up - * a matching VmbPixelFormat_t. - * - * \param[in] pixelFormat The pixel format describes the pixel format to be used. - * \param[in] width The width of the image in pixels. - * \param[in] height The height of the image in pixels. - * \param[in,out] image A pointer to the image struct to write the info to. - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter If \p image is null or one of the members of \p image is invalid. - * - * \retval ::VmbErrorStructSize If the Size member of the image is incorrect. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbSetImageInfoFromPixelFormat(VmbPixelFormat_t pixelFormat, - VmbUint32_t width, - VmbUint32_t height, - VmbImage* image); - -/** - * \brief Set image info member values in VmbImage from string. - * - * This function does not read or write to VmbImage::Data member. - * - * \param[in] imageFormat The string containing the image format. This parameter is case insensitive. - * \param[in] width The width of the image in pixels. - * \param[in] height The height of the image in pixels. - * \param[in,out] image A pointer to the image struct to write the info to. - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter \p imageFormat or \p image are null. - * - * \retval ::VmbErrorStructSize The Size member of \p image contains an invalid value. - * - * \retval ::VmbErrorResources The function ran out of memory while processing. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbSetImageInfoFromString(const VmbANSIChar_t* imageFormat, - VmbUint32_t width, - VmbUint32_t height, - VmbImage* image); - -/** - * \brief Set output image dependent on the input image, user specifies pixel layout and bit depth of the out format. - * - * \param[in] inputPixelFormat Input Vmb pixel format - * \param[in] width width of the output image - * \param[in] height height of the output image - * \param[in] outputPixelLayout pixel component layout for output image - * \param[in] bitsPerPixel bit depth of output 8 and 16 supported - * \param[out] outputImage The output image to write the compatible format to. - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter \p outputImage is null. - * - * \retval ::VmbErrorStructSize The Size member of \p outputImage contains an invalid size. - * - * \retval ::VmbErrorNotImplemented No suitable transformation is implemented. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbSetImageInfoFromInputParameters(VmbPixelFormat_t inputPixelFormat, - VmbUint32_t width, - VmbUint32_t height, - VmbPixelLayout_t outputPixelLayout, - VmbUint32_t bitsPerPixel, - VmbImage* outputImage); - -/** - * \brief Set output image compatible to input image with given layout and bit depth. - * The output image will have same dimensions as the input image. - * - * \param[in] inputImage The input image with fully initialized image info elements. - * \param[in] outputPixelLayout The desired layout for the output image. - * \param[in] bitsPerPixel The desided bit depth for output image. 8 bit and 16 bit are supported. - * \param[out] outputImage The output image to write the compatible format to. - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter \p inputImage or \p outputImage are null, - * or the PixelInfo member of the ImageInfo member of the \p inputImage does not correspond to a supported pixel format. - * - * \retval ::VmbErrorStructSize The Size member of \p outputImage contains an invalid size. - * - * \retval ::VmbErrorNotImplemented No suitable transformation is implemented. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbSetImageInfoFromInputImage(const VmbImage* inputImage, - VmbPixelLayout_t outputPixelLayout, - VmbUint32_t bitsPerPixel, - VmbImage* outputImage); - -/** - * \brief Transform an image from one pixel format to another providing additional transformation options, if necessary. - * - * The transformation is defined by the provided images and the \p parameter. - * - * Create the source and destination image info structure with VmbSetImageInfoFromPixelFormat - * or VmbSetimageInfoFromString and keep those structures as template. - * For calls to transform, simply attach the image to the Data member. - * The optional parameters, when set, are constraints on the transform. - * - * \param[in] source The pointer to source image. - * \param[in,out] destination The pointer to destination image. - * \param[in] parameter An array of transform parameters; may be null. - * \param[in] parameterCount The number of transform parameters. - * - * \return An error code indicating success or the type of error. - * - * \retval ::VmbErrorSuccess The call was successful. - * - * \retval ::VmbErrorBadParameter if any image pointer or their "Data" members is NULL, or - * if "Width" or "Height" don't match between source and destination, or - * if one of the parameters for the conversion does not fit - * - * \retval ::VmbErrorStructSize The Size member of \p source or \p destination contain an invalid value. - * - * \retval ::VmbErrorNotImplemented The transformation from the format of \p source to the format of \p destination is not implemented. - */ -VmbError_t VMBIMAGETRANSFORM_API VmbImageTransform(const VmbImage* source, - VmbImage* destination, - const VmbTransformInfo* parameter, - VmbUint32_t parameterCount); - -#ifdef __cplusplus -} -#endif - -#endif // VMB_TRANSFORM_H_ diff --git a/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransformTypes.h b/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransformTypes.h deleted file mode 100644 index c2438470c..000000000 --- a/DeviceAdapters/AlliedVisionCamera/SDK/VmbImageTransform/VmbTransformTypes.h +++ /dev/null @@ -1,1018 +0,0 @@ -/*============================================================================= - Copyright (C) 2012 - 2021 Allied Vision Technologies. All Rights Reserved. - - Redistribution of this header file, in original or modified form, without - prior written consent of Allied Vision Technologies is prohibited. - -------------------------------------------------------------------------------- - - File: VmbTransformTypes.h - - Description: Definition of types used in the Vmb Image Transform library. - -------------------------------------------------------------------------------- - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, - NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=============================================================================*/ - -/** - * \file - */ -#ifndef VMB_TRANSFORM_TYPES_H_ -#define VMB_TRANSFORM_TYPES_H_ - -#include - -/** - * \brief the type of character to use for strings. - */ -typedef char VmbANSIChar_t; - -/** - * \brief The floating point type to use for matrices. - */ -typedef float VmbFloat_t; - -/** - * \brief Enumeration for the Bayer pattern. - */ -typedef enum VmbBayerPattern -{ - VmbBayerPatternRGGB=0, //!< RGGB pattern, red pixel comes first - VmbBayerPatternGBRG, //!< RGGB pattern, green pixel of blue row comes first - VmbBayerPatternGRBG, //!< RGGB pattern, green pixel of red row comes first - VmbBayerPatternBGGR, //!< RGGB pattern, blue pixel comes first - VmbBayerPatternCYGM=128, //!< CYGM pattern, cyan pixel comes first in the first row, green in the second row (of the sensor) - VmbBayerPatternGMCY, //!< CYGM pattern, green pixel comes first in the first row, cyan in the second row (of the sensor) - VmbBayerPatternCYMG, //!< CYGM pattern, cyan pixel comes first in the first row, magenta in the second row (of the sensor) - VmbBayerPatternMGCY, //!< CYGM pattern, magenta pixel comes first in the first row, cyan in the second row (of the sensor) - VmbBayerPatternLAST=255 -} VmbBayerPattern; - -/** - * \brief Type for an error returned by API methods; for values see ::VmbBayerPattern. - */ -typedef VmbUint32_t VmbBayerPattern_t; - -/** - * \brief Enumeration for the endianness. - */ -typedef enum VmbEndianness -{ - VmbEndiannessLittle=0, //!< Little endian data format - VmbEndiannessBig, //!< Big endian data format - VmbEndiannessLast=255 -} VmbEndianness; - -/** - * \brief Type for the endianness; for values see ::VmbEndianness. - */ -typedef VmbUint32_t VmbEndianness_t; - -/** - * \brief Enumeration for the image alignment. - */ -typedef enum VmbAlignment -{ - VmbAlignmentMSB=0, //!< Data is MSB aligned (pppp pppp pppp ....) - VmbAlignmentLSB, //!< Data is LSB aligned (.... pppp pppp pppp) - VmbAlignmentLAST=255 -} VmbAlignment; - -/** - * \brief Enumeration for the image alignment; for values see ::VmbAlignment - */ -typedef VmbUint32_t VmbAlignment_t; - -/** - * \name Library Info - * \defgroup Library Info - * \{ - */ - -/** - * \brief States of the multi media technology support for operating system and processor. - */ -typedef struct VmbSupportState_t -{ - VmbBool_t Processor; //!< technology supported by the processor - VmbBool_t OperatingSystem; //!< technology supported by the OS -} VmbSupportState_t; - -/** - * \brief States of the support for different multimedia technologies - */ -typedef struct VmbTechInfo_t -{ - VmbSupportState_t IntelMMX; //!< INTEL first gen MultiMedia eXtension - VmbSupportState_t IntelSSE; //!< INTEL Streaming SIMD Extension - VmbSupportState_t IntelSSE2; //!< INTEL Streaming SIMD Extension 2 - VmbSupportState_t IntelSSE3; //!< INTEL Streaming SIMD Extension 3 - VmbSupportState_t IntelSSSE3; //!< INTEL Supplemental Streaming SIMD Extension 3 - VmbSupportState_t AMD3DNow; //!< AMD 3DNow -} VmbTechInfo_t; - -/** - * \brief API info types - */ -typedef enum VmbAPIInfo -{ - VmbAPIInfoAll, //!< All the info (platform, build type and technologies) - VmbAPIInfoPlatform, //!< Platform the api was build for - VmbAPIInfoBuild, //!< build type (debug or release) - VmbAPIInfoTechnology, //!< info about special technologies uses in building the API - VmbAPIInfoLast -} VmbAPIInfo; - -/** - * \brief API info type; for values see ::VmbAPIInfo - */ -typedef VmbUint32_t VmbAPIInfo_t; - -/** - * \} - */ - -/** - * \name Pixel Access Structs - * \defgroup Pixel Access Structs - * \{ - */ - -/** - * \brief Structure for accessing data in 12-bit transfer mode. - * - * Two pixel are coded into 3 bytes. - */ -typedef struct Vmb12BitPackedPair_t -{ - VmbUint8_t m_nVal8_1 ; //!< High byte of the first Pixel - VmbUint8_t m_nVal8_1Low : 4; //!< Low nibble of the first pixel - VmbUint8_t m_nVal8_2Low : 4; //!< Low nibble of the second pixel - VmbUint8_t m_nVal8_2 ; //!< High byte of the second pixel -} Vmb12BitPackedPair_t; - -/** - * \brief Struct for accessing data of a 8 bit grayscale image stored as unsigned integer. - * - * This corresponds to ::VmbPixelFormatMono8. - */ -typedef struct VmbMono8_t -{ -#ifdef __cplusplus - typedef VmbUint8_t value_type; -#endif - VmbUint8_t Y; //!< gray part -} VmbMono8_t; - -/** - * \brief Struct for accessing data of a 8 bit grayscale image stored as signed integer. - */ -typedef struct VmbMono8s_t -{ -#ifdef __cplusplus - typedef VmbInt8_t value_type; -#endif - VmbInt8_t Y; //!< gray part -} VmbMono8s_t; - -/** - * \brief Struct for accessing pixel data of a 10 bit mono padded buffer. - * - * The pixel data is LSB aligned and little endianness encoded on little endian systems. - * - * The pixel data is MSB aligned and big endianness encoded on big endian systems. - * - * On little endian systems this corresponds to ::VmbPixelFormatMono10. - */ -typedef struct VmbMono10_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t Y; //!< gray part -} VmbMono10_t; - -/** - * \brief Struct for accessing pixel data of a 12 bit mono padded buffer. - * - * The pixel data is LSB aligned and little endianness encoded on little endian systems. - * - * The pixel data is MSB aligned and big endianness encoded on big endian systems. - * - * On little endian systems this corresponds to ::VmbPixelFormatMono12. - */ -typedef struct VmbMono12_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t Y; //!< gray part -} VmbMono12_t; - -/** - * \brief Struct for accessing pixel data of a 14 bit mono padded buffer. - * - * The pixel data is LSB aligned and little endianness encoded on little endian systems. - * - * The pixel data is MSB aligned and big endianness encoded on big endian systems. - * - * On little endian systems this corresponds to ::VmbPixelFormatMono14. - */ -typedef struct VmbMono14_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t Y; //!< gray part -} VmbMono14_t; - -/** - * \brief Struct for accessing 16 bit grayscale image stored as unsigned integer. - * - * The pixel data is LSB aligned and little endianness encoded on little endian systems. - * - * The pixel data is MSB aligned and big endianness encoded on big endian systems. - * - * On little endian systems this corresponds to ::VmbPixelFormatMono16. - */ -typedef struct VmbMono16_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t Y; //!< gray part -} VmbMono16_t; - -/** - * \brief Struct for accessing 16 bit grayscale image stored as signed integer. - */ -typedef struct VmbMono16s_t -{ -#ifdef __cplusplus - typedef VmbInt16_t value_type; -#endif - VmbInt16_t Y; //!< gray part -} VmbMono16s_t; - -/** - * \brief Structure for accessing RGB data using 8 bit per channel. - * - * This corresponds to ::VmbPixelFormatRgb8 - */ -typedef struct VmbRGB8_t -{ -#ifdef __cplusplus - typedef VmbUint8_t value_type; -#endif - VmbUint8_t R; //!< red part - VmbUint8_t G; //!< green part - VmbUint8_t B; //!< blue part -} VmbRGB8_t; - -/** - * \brief Structure for accessing RGB data using 10 bit per channel padded to 16 bit; 48 bits per pixel are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatRgb10 on little endian systems. - */ -typedef struct VmbRGB10_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t R; //!< red part - VmbUint16_t G; //!< green part - VmbUint16_t B; //!< blue part -} VmbRGB10_t; - -/** - * \brief Structure for accessing RGB data using 12 bit per channel padded to 16 bit; 48 bits per pixel are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatRgb12 on little endian systems. - */ -typedef struct VmbRGB12_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t R; //!< red part - VmbUint16_t G; //!< green part - VmbUint16_t B; //!< blue part -} VmbRGB12_t; - -/** - * \brief Structure for accessing RGB data using 14 bit per channel padded to 16 bit; 48 bits per pixel are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatRgb14 on little endian systems. - */ -typedef struct VmbRGB14_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t R; //!< red part - VmbUint16_t G; //!< green part - VmbUint16_t B; //!< blue part -} VmbRGB14_t; - -/** - * \brief Struct for accessing RGB pixels stored as 16 bit unsigend integer per channel. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatRgb16 on little endian systems. - */ -typedef struct VmbRGB16_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t R; //!< red part - VmbUint16_t G; //!< green part - VmbUint16_t B; //!< blue part -} VmbRGB16_t; - -/** - * \brief Structure for accessing BGR data using 8 bit per channel. - * - * Corresponds to ::VmbPixelFormatBgr8 - */ -typedef struct VmbBGR8_t -{ -#ifdef __cplusplus - typedef VmbUint8_t value_type; -#endif - VmbUint8_t B; //!< blue part - VmbUint8_t G; //!< green part - VmbUint8_t R; //!< red part -} VmbBGR8_t; - -/** - * \brief Structure for accessing BGR data using 10 bit per channel padded to 16 bit; 48 bits per pixel are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatBgr10 on little endian systems. - */ -typedef struct VmbBGR10_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t B; //!< blue part - VmbUint16_t G; //!< green part - VmbUint16_t R; //!< red part -} VmbBGR10_t; - -/** - * \brief Structure for accessing BGR data using 12 bit per channel padded to 16 bit; 48 bits per pixel are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatBgr12 on little endian systems. - */ -typedef struct VmbBGR12_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t B; //!< blue part - VmbUint16_t G; //!< green part - VmbUint16_t R; //!< red part -} VmbBGR12_t; - -/** - * \brief Structure for accessing BGR data using 14 bit per channel padded to 16 bit; 48 bits per pixel are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatBgr14 on little endian systems. - */ -typedef struct VmbBGR14_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t B; //!< blue part - VmbUint16_t G; //!< green part - VmbUint16_t R; //!< red part -} VmbBGR14_t; - -/** - * \brief Structure for accessing BGR data using 16 bit per channel; 48 bits per pixel are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatBgr16 on little endian systems. - */ -typedef struct VmbBGR16_t -{ -#ifdef __cplusplus - typedef VmbUint16_t value_type; -#endif - VmbUint16_t B; //!< blue part - VmbUint16_t G; //!< green part - VmbUint16_t R; //!< red part -} VmbBGR16_t; - -/** - * \brief Structure for accessing RGBA data using 8 bit per channel. - */ -typedef struct VmbRGBA8_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint8_t value_type; -#endif - VmbUint8_t R; //!< red part - VmbUint8_t G; //!< green part - VmbUint8_t B; //!< blue part - VmbUint8_t A; //!< unused -} VmbRGBA8_t; - -/** - * \brief Alias for ::VmbRGBA8_t - */ -typedef VmbRGBA8_t VmbRGBA32_t; - -/** - * \brief Structure for accessing BGRA data using 8 bit per channel. - * - * This corresponds to ::VmbPixelFormatBgra8 - */ -typedef struct VmbBGRA8_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint8_t value_type; -#endif - VmbUint8_t B; //!< blue part - VmbUint8_t G; //!< green part - VmbUint8_t R; //!< red part - VmbUint8_t A; //!< unused -} VmbBGRA8_t; - -/** - * \brief Alias for ::VmbBGRA8_t - */ -typedef VmbBGRA8_t VmbBGRA32_t; - -/** - * \brief Struct for accessing ARGB values stored using a 8 bit unsigned integer per channel. - * - * Corresponds to ::VmbPixelFormatArgb8 - */ -typedef struct VmbARGB8_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint8_t value_type; -#endif - VmbUint8_t A; //!< unused - VmbUint8_t R; //!< red part - VmbUint8_t G; //!< green part - VmbUint8_t B; //!< blue part -} VmbARGB8_t; - -/** - * \brief Alias for ::VmbARGB8_t - */ -typedef VmbARGB8_t VmbARGB32_t; - -/** - * \brief Structure for accessing BGRA data using a 8 bit unsigned integer per channel. - */ -typedef struct VmbABGR8_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint8_t value_type; -#endif - VmbUint8_t A; //!< unused - VmbUint8_t B; //!< blue part - VmbUint8_t G; //!< green part - VmbUint8_t R; //!< red part -} VmbABGR8_t; - -/** - * \brief Alias for ::VmbABGR8_t - */ -typedef VmbABGR8_t VmbABGR32_t; - -/** - * \brief Structure for accessing RGBA data using 10 bit per channel padded to 16 bit; 64 bit are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatRgba10 on little endian systems. - */ -typedef struct VmbRGBA10_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint16_t value_type; -#endif - VmbUint16_t R; //!< red part - VmbUint16_t G; //!< green part - VmbUint16_t B; //!< blue part - VmbUint16_t A; //!< unused -} VmbRGBA10_t; - -/** - * \brief Structure for accessing BGRA data using 10 bit per channel padded to 16 bit; 64 bit are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatBgra10 on little endian systems. - */ -typedef struct VmbBGRA10_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint16_t value_type; -#endif - VmbUint16_t B; //!< blue part - VmbUint16_t G; //!< green part - VmbUint16_t R; //!< red part - VmbUint16_t A; //!< unused -} VmbBGRA10_t; - -/** - * \brief Structure for accessing RGBA data using 12 bit per channel padded to 16 bit; 64 bit are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatRgba12 on little endian systems. - */ -typedef struct VmbRGBA12_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint16_t value_type; -#endif - VmbUint16_t R; //!< red part - VmbUint16_t G; //!< green part - VmbUint16_t B; //!< blue part - VmbUint16_t A; //!< unused -} VmbRGBA12_t; - -/** - * \brief Structure for accessing RGBA data using 14 bit per channel padded to 16 bit; 64 bit are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatRgba14 on little endian systems. - */ -typedef struct VmbRGBA14_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint16_t value_type; -#endif - VmbUint16_t R; //!< red part - VmbUint16_t G; //!< green part - VmbUint16_t B; //!< blue part - VmbUint16_t A; //!< unused -} VmbRGBA14_t; - -/** - * \brief Structure for accessing BGRA data using 12 bit per channel padded to 16 bit; 64 bit are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatBgra12 on little endian systems. - */ -typedef struct VmbBGRA12_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint16_t value_type; -#endif - VmbUint16_t B; //!< blue part - VmbUint16_t G; //!< green part - VmbUint16_t R; //!< red part - VmbUint16_t A; //!< unused -} VmbBGRA12_t; - -/** - * \brief Structure for accessing BGRA data using 14 bit per channel padded to 16 bit; 64 bit are used in total. - * - * Each channel is LSB aligned and little endianness encoded on little endian systems. - * - * Each channel is MSB aligned and big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatBgra14 on little endian systems. - */ -typedef struct VmbBGRA14_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint16_t value_type; -#endif - VmbUint16_t B; //!< blue part - VmbUint16_t G; //!< green part - VmbUint16_t R; //!< red part - VmbUint16_t A; //!< unused -} VmbBGRA14_t; - -/** - * \brief Structure for accessing RGBA data using 16 bit per channel. - * - * Each channel is little endianness encoded on little endian systems. - * - * Each channel is big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatRgba16 on little endian systems. - */ -typedef struct VmbRGBA16_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint16_t value_type; -#endif - VmbUint16_t R; //!< red part - VmbUint16_t G; //!< green part - VmbUint16_t B; //!< blue part - VmbUint16_t A; //!< unused -} VmbRGBA16_t; - -/** - * \brief Alias for ::VmbRGBA16_t - */ -typedef VmbRGBA16_t VmbRGBA64_t; - -/** - * \brief Structure for accessing BGRA data using 16 bit per channel. - * - * Each channel is little endianness encoded on little endian systems. - * - * Each channel is big endianness encoded on big endian systems. - * - * Corresponds to ::VmbPixelFormatBgra16 on little endian systems. - */ -typedef struct VmbBGRA16_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint16_t value_type; -#endif - VmbUint16_t B; //!< blue part - VmbUint16_t G; //!< green part - VmbUint16_t R; //!< red part - VmbUint16_t A; //!< unused -} VmbBGRA16_t; - -/** - * \brief Alias for ::VmbBGRA64_t - */ -typedef VmbBGRA16_t VmbBGRA64_t; - -/** - * \brief Structure for accessing data in the YUV 4:4:4 format (YUV) prosilica component order. - * - * Corresponds to ::VmbPixelFormatYuv444 - */ -typedef struct VmbYUV444_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint8_t value_type; -#endif - VmbUint8_t U; //!< U - VmbUint8_t Y; //!< Luma - VmbUint8_t V; //!< V -} VmbYUV444_t; - -/** - * \brief Structure for accessing data in the YUV 4:2:2 format (UYVY) - * - * This struct provides data for 2 pixels (Y0, U, V) and (Y1, U, V) - * - * Corresponds to ::VmbPixelFormatYuv422 - */ -typedef struct VmbYUV422_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint8_t value_type; -#endif - VmbUint8_t U; //!< the U part for both pixels - VmbUint8_t Y0; //!< the intensity of the first pixel - VmbUint8_t V; //!< the V part for both pixels - VmbUint8_t Y1; //!< the intensity of the second pixel -} VmbYUV422_t; - -/** - * \brief Structure for accessing data in the YUV 4:1:1 format (UYYVYY) - * - * This struct provides data for 2 pixels (Y0, U, V), (Y1, U, V), (Y2, U, V) and (Y3, U, V) - * - * Corresponds to ::VmbPixelFormatYuv411 - */ -typedef struct VmbYUV411_t -{ -#ifdef __cplusplus - /** - * \brief The data type used to store one channel. - */ - typedef VmbUint8_t value_type; -#endif - VmbUint8_t U; //!< the U part for all four pixels - VmbUint8_t Y0; //!< the intensity of the first pixel - VmbUint8_t Y1; //!< the intensity of the second pixel - VmbUint8_t V; //!< the V part for all four pixels - VmbUint8_t Y2; //!< the intensity of the third pixel - VmbUint8_t Y3; //!< the intensity of the fourth pixel -} VmbYUV411_t; - -/** - * \} - */ - -/** - * \brief Image pixel layout information. - */ -typedef enum VmbPixelLayout -{ - VmbPixelLayoutMono, //!< Monochrome pixel data; pixels are padded, if necessary. - VmbPixelLayoutMonoPacked, //!< Monochrome pixel data; pixels some bytes contain data for more than one pixel. - VmbPixelLayoutRaw, //!< Some Bayer pixel format where pixels each byte contains only data for a single pixel. - VmbPixelLayoutRawPacked, //!< Some Bayer pixel format where some bytes contain data for more than one pixel. - VmbPixelLayoutRGB, //!< Non-packed RGB data in channel order R, G, B - VmbPixelLayoutBGR, //!< Non-packed RGB data in channel order B, G, R - VmbPixelLayoutRGBA, //!< Non-packed RGBA data in channel order R, G, B, A - VmbPixelLayoutBGRA, //!< Non-packed RGBA data in channel order B, G, R, A - VmbPixelLayoutYUV411_UYYVYY, //!< YUV data; pixel order for 4 pixels is U, Y0, Y1, V, Y2, Y3 - VmbPixelLayoutYUV411_YYUYYV, //!< YUV data; pixel order for 4 pixels is Y0, Y1, U, Y2, Y3, V - VmbPixelLayoutYUV422_UYVY, //!< YUV data; pixel order for 2 pixels is U, Y0, V, Y1 - VmbPixelLayoutYUV422_YUYV, //!< YUV data; pixel order for 2 pixels is Y0, U, Y1, V - VmbPixelLayoutYUV444_UYV, //!< YUV data; pixel order is U, Y, V - VmbPixelLayoutYUV444_YUV, //!< YUV data; pixel order is Y, U, V - VmbPixelLayoutMonoP, //!< Monochrome pixel data; pixels are padded, if necessary. \todo What is the difference to VmbPixelLayoutMono? - VmbPixelLayoutMonoPl, //!< \todo unused, remove? - VmbPixelLayoutRawP, //!< Some Bayer pixel format where pixels each byte contains only data for a single pixel. \todo What's the difference to VmbPixelLayoutRawPacked? - VmbPixelLayoutRawPl, //!< \todo unused, remove? - VmbPixelLayoutYYCbYYCr411 = VmbPixelLayoutYUV411_YYUYYV, //!< Alias for ::VmbPixelLayoutYUV411_YYUYYV - VmbPixelLayoutCbYYCrYY411 = VmbPixelLayoutYUV411_UYYVYY, //!< Alias for ::VmbPixelLayoutYUV411_UYYVYY - VmbPixelLayoutYCbYCr422 = VmbPixelLayoutYUV422_YUYV, //!< Alias for ::VmbPixelLayoutYUV422_YUYV - VmbPixelLayoutCbYCrY422 = VmbPixelLayoutYUV422_UYVY, //!< Alias for ::VmbPixelLayoutYUV422_UYVY - VmbPixelLayoutYCbCr444 = VmbPixelLayoutYUV444_YUV, //!< Alias for ::VmbPixelLayoutYUV444_YUV - VmbPixelLayoutCbYCr444 = VmbPixelLayoutYUV444_UYV, //!< Alias for ::VmbPixelLayoutYUV444_UYV - - VmbPixelLayoutLAST, -} VmbPixelLayout; - -/** - * \brief Image pixel layout information; for values see ::VmbPixelLayout - */ -typedef VmbUint32_t VmbPixelLayout_t; - -/** - * \brief Image color space information. - */ -typedef enum VmbColorSpace -{ - VmbColorSpaceUndefined, - VmbColorSpaceITU_BT709, //!< \todo color space description - VmbColorSpaceITU_BT601, //!< \todo color space description - -} VmbColorSpace; - -/** - * \brief Image color space information; for values see ::VmbColorSpace - */ -typedef VmbUint32_t VmbColorSpace_t; - -/** - * \brief Image pixel information - */ -typedef struct VmbPixelInfo -{ - VmbUint32_t BitsPerPixel; //!< The number of bits used to store the data for one pixel - VmbUint32_t BitsUsed; //!< The number of bits that actually contain data. - VmbAlignment_t Alignment; //!< Indicates, if the most significant or the least significant bit is filled for pixel formats not using all bits of the buffer to store data. - VmbEndianness_t Endianness; //!< Endianness of the pixel data - VmbPixelLayout_t PixelLayout; //!< Channel order, and in case of YUV formats relative order. - VmbBayerPattern_t BayerPattern; //!< The bayer pattern - VmbColorSpace_t Reserved; //!< Unused member reserved for future use. -} VmbPixelInfo; - -/** - * \brief Struct containing information about the image data in the Data member of a ::VmbImage. - */ -typedef struct VmbImageInfo -{ - VmbUint32_t Width; //!< The width of the image in pixels - VmbUint32_t Height; //!< The height of the image in pixels - VmbInt32_t Stride; //!< \todo description; do we actually use this - VmbPixelInfo PixelInfo; //!< Information about the pixel format -} VmbImageInfo; - -/** - * \brief vmb image type - */ -typedef struct VmbImage -{ - VmbUint32_t Size; //!< The size of this struct; If set incorrectly, API functions will return ::VmbErrorStructSize - void* Data; //!< The image data - VmbImageInfo ImageInfo; //!< Information about pixel format, size, and stride of the image. - -} VmbImage; - -/** - * \brief Transform info for special debayering modes. - */ -typedef enum VmbDebayerMode -{ - VmbDebayerMode2x2, //!< \todo description - VmbDebayerMode3x3, //!< \todo description - VmbDebayerModeLCAA, //!< \todo description - VmbDebayerModeLCAAV, //!< \todo description - VmbDebayerModeYUV422, //!< \todo description -} VmbDebayerMode; - -/** - * \brief Transform info for special debayering mode; for values see ::VmbDebayerMode - */ -typedef VmbUint32_t VmbDebayerMode_t; - -/** - * \name Transformation Parameters - * \defgroup Transformation Parameters - * \{ - */ - -/** - * \brief Transform parameter types. - */ -typedef enum VmbTransformType -{ - VmbTransformTypeNone, //!< Invalid type - VmbTransformTypeDebayerMode, //!< Debayering mode - VmbTransformTypeColorCorrectionMatrix, //!< Color correction matrix - VmbTransformTypeGammaCorrection, //!< Gamma correction - VmbTransformTypeOffset, //!< Offset - VmbTransformTypeGain, //!< Gain -} VmbTransformType; - -/** - * \brief Transform parameter type; for avalues see ::VmbTransformType - */ -typedef VmbUint32_t VmbTransformType_t; - -/** - * \brief Struct definition for holding the debayering mode. - * - * The struct is used to pass the data to ::VmbImageTransform via transform parameter. - * It corresponds to the ::VmbTransformTypeDebayerMode parameter type. - */ -typedef struct VmbTransformParameteDebayer -{ - VmbDebayerMode_t Method; //!< The DeBayering method to use. -} VmbTransformParameterDebayer; - - -/** - * \brief Transform info for color correction using a 3x3 matrix multiplication. - * - * The struct is used to pass the data to ::VmbImageTransform via transform parameter. - * It corresponds to the ::VmbTransformTypeColorCorrectionMatrix parameter type. - * - * \todo what does each index represent; how to get from 2d to 1d? - */ -typedef struct VmbTransformParameterMatrix3x3 -{ - VmbFloat_t Matrix[9]; //!< The color correction matrix to use for the transformation. -} VmbTransformParameterMatrix3x3; - -/** - * \brief Struct definition for a gamma value. - * - * This is currently not supported by ::VmbImageTransform. - * It corresponds to the ::VmbTransformTypeGammaCorrection parameter type. - */ -typedef struct VmbTransformParameterGamma -{ - VmbFloat_t Gamma; //!< The gamma value to use for the transformation -} VmbTransformParameterGamma; - -/** - * \brief Struct definition for holding the offset to pass via transform parameter. - * - * The struct is used to pass the data to ::VmbImageTransform via transform parameter. - * It corresponds to the ::VmbTransformTypeOffset parameter type. - */ -typedef struct VmbTransformParameterOffset -{ - VmbInt32_t Offset; //!< The offset to use for the transformation. -} VmbTransformParameterOffset; - -/** - * \brief Struct definition for holding the gain value. - * - * The struct is used to pass the data to ::VmbImageTransform via transform parameter. - * It corresponds to the ::VmbTransformTypeGain parameter type. - */ -typedef struct VmbTransformParameterGain -{ - VmbUint32_t Gain; //!< The gain to use for the transformation -} VmbTransformParameterGain; - -/** - * \brief Union for possible transformation parameter types. - * - * Each possible data type corresponds to a constant of ::VmbTransformType. - */ -typedef union VmbTransformParameter -{ - VmbTransformParameterMatrix3x3 Matrix3x3; //!< A matrix with 3 rows and 3 columns. - VmbTransformParameterDebayer Debayer; //!< A debayering mode - VmbTransformParameterGamma Gamma; //!< A gamma value - VmbTransformParameterOffset Offset; //!< \todo offset (is this even used?) - VmbTransformParameterGain Gain; //!< A gain value -} VmbTransformParameter; - -/** - * \} - */ - -/** - * \brief Transform info interface structure. - */ -typedef struct VmbTransformInfo -{ - VmbTransformType_t TransformType; //!< The type of the information stored in the Parameter member. - VmbTransformParameter Parameter; //!< The parameter data. -} VmbTransformInfo; - -#endif // VMB_TRANSFORM_TYPES_H_ diff --git a/DeviceAdapters/AlliedVisionCamera/license.txt b/DeviceAdapters/AlliedVisionCamera/license.txt new file mode 100644 index 000000000..0032977fd --- /dev/null +++ b/DeviceAdapters/AlliedVisionCamera/license.txt @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2023, Allied Vision Technologies GmbH +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From c3d5783b30cc058a0f3babfdfa24e1db23e228f6 Mon Sep 17 00:00:00 2001 From: Lars Kool Date: Tue, 10 Oct 2023 11:57:43 +0200 Subject: [PATCH 048/141] First commit to MM --- DeviceAdapters/IDSPeak/Changelog.txt | 8 + DeviceAdapters/IDSPeak/IDSPeak.cpp | 2101 +++++++++++++++++ DeviceAdapters/IDSPeak/IDSPeak.h | 294 +++ DeviceAdapters/IDSPeak/IDSPeak.vcxproj | 106 + .../IDSPeak/IDSPeak.vcxproj.filters | 35 + DeviceAdapters/IDSPeak/LICENSE | 28 + DeviceAdapters/IDSPeak/README.md | 50 + 7 files changed, 2622 insertions(+) create mode 100644 DeviceAdapters/IDSPeak/Changelog.txt create mode 100644 DeviceAdapters/IDSPeak/IDSPeak.cpp create mode 100644 DeviceAdapters/IDSPeak/IDSPeak.h create mode 100644 DeviceAdapters/IDSPeak/IDSPeak.vcxproj create mode 100644 DeviceAdapters/IDSPeak/IDSPeak.vcxproj.filters create mode 100644 DeviceAdapters/IDSPeak/LICENSE create mode 100644 DeviceAdapters/IDSPeak/README.md diff --git a/DeviceAdapters/IDSPeak/Changelog.txt b/DeviceAdapters/IDSPeak/Changelog.txt new file mode 100644 index 000000000..2cbc6ddfe --- /dev/null +++ b/DeviceAdapters/IDSPeak/Changelog.txt @@ -0,0 +1,8 @@ +Changelog IDSPeak: + +2023.09.25 +- First commit to Micro-Manager + +2023.10.10 +- Changed .dll to Release version (no longer requires MS Visual Studio to work) +- Added multi-camera support \ No newline at end of file diff --git a/DeviceAdapters/IDSPeak/IDSPeak.cpp b/DeviceAdapters/IDSPeak/IDSPeak.cpp new file mode 100644 index 000000000..cfc6b1d7b --- /dev/null +++ b/DeviceAdapters/IDSPeak/IDSPeak.cpp @@ -0,0 +1,2101 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: IDSPeak.cpp +// PROJECT: Micro-Manager +// SUBSYSTEM: DeviceAdapters +//----------------------------------------------------------------------------- +// DESCRIPTION: Driver for IDS peak series of USB cameras +// +// Based on IDS peak SDK and Micro-manager DemoCamera example +// tested with SDK version 2.5 +// Requires Micro-manager Device API 71 or higher! +// +// AUTHOR: Lars Kool, Institut Pierre-Gilles de Gennes +// +// YEAR: 2023 +// +// VERSION: 1.1 +// +// LICENSE: This file is distributed under the BSD license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +//LAST UPDATE: 09.10.2023 LK + +#include "IDSPeak.h" +#include +#include +#include +#include "ModuleInterface.h" +#include +#include +#include "WriteCompactTiffRGB.h" +#include +#include + +using namespace std; +const double CIDSPeak::nominalPixelSizeUm_ = 1.0; +double g_IntensityFactor_ = 1.0; +const char* g_PixelType_8bit = "8bit"; +const char* g_PixelType_32bitRGBA = "32bit RGBA"; + +// External names used used by the rest of the system +// to load particular device from the "IDSPeak.dll" library +const char* g_CameraDeviceName = "IDSCam"; + +/////////////////////////////////////////////////////////////////////////////// +// Exported MMDevice API +/////////////////////////////////////////////////////////////////////////////// + +MODULE_API void InitializeModuleData() +{ + RegisterDevice(g_CameraDeviceName, MM::CameraDevice, "IDS camera"); +} + +MODULE_API MM::Device* CreateDevice(const char* deviceName) +{ + if (deviceName == 0) + return 0; + + // decide which device class to create based on the deviceName parameter + if (strcmp(deviceName, g_CameraDeviceName) == 0) + { + // create camera + return new CIDSPeak(); + } + + // ...supplied name not recognized + return 0; +} + +MODULE_API void DeleteDevice(MM::Device* pDevice) +{ + delete pDevice; +} + +/////////////////////////////////////////////////////////////////////////////// +// CIDSPeak implementation +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +/** +* CIDSPeak constructor. +* Setup default all variables and create device properties required to exist +* before intialization. In this case, no such properties were required. All +* properties will be created in the Initialize() method. +* +* As a general guideline Micro-Manager devices do not access hardware in the +* the constructor. We should do as little as possible in the constructor and +* perform most of the initialization in the Initialize() method. +*/ +CIDSPeak::CIDSPeak() : + CCameraBase (), + initialized_(false), + readoutUs_(0.0), + bitDepth_(8), + roiX_(0), + roiY_(0), + roiMinSizeX_(0), + roiMinSizeY_(0), + roiInc_(1), + sequenceStartTime_(0), + isSequenceable_(false), + sequenceMaxLength_(100), + sequenceRunning_(false), + sequenceIndex_(0), + binSize_(1), + cameraCCDXSize_(512), + cameraCCDYSize_(512), + ccdT_(0.0), + triggerDevice_(""), + stopOnOverflow_(false), + supportsMultiROI_(false), + multiROIFillValue_(0), + nComponents_(1), + exposureMax_(10000.0), + exposureMin_(0.0), + exposureInc_(1.0), + exposureCur_(10.0), + framerateCur_(10), + framerateMax_(200), + framerateMin_(0.1), + framerateInc_(0.1), + imageCounter_(0), + gainMaster_(1.0), + gainRed_(1.0), + gainGreen_(1.0), + gainBlue_(1.0) +{ + // call the base class method to set-up default error codes/messages + InitializeDefaultErrorMessages(); + readoutStartTime_ = GetCurrentMMTime(); + thd_ = new MySequenceThread(this); +} + +/** +* CIDSPeak destructor. +* If this device used as intended within the Micro-Manager system, +* Shutdown() will be always called before the destructor. But in any case +* we need to make sure that all resources are properly released even if +* Shutdown() was not called. +*/ +CIDSPeak::~CIDSPeak() +{ + StopSequenceAcquisition(); + delete thd_; +} + +/** +* Obtains device name. +* Required by the MM::Device API. +*/ +void CIDSPeak::GetName(char* name) const +{ + // Return the name used to referr to this device adapte + CDeviceUtils::CopyLimitedString(name, g_CameraDeviceName); +} + +/** +* Intializes the hardware. +* Required by the MM::Device API. +* Typically we access and initialize hardware at this point. +* Device properties are typically created here as well, except +* the ones we need to use for defining initialization parameters. +* Such pre-initialization properties are created in the constructor. +* (This device does not have any pre-initialization properties) +*/ +int CIDSPeak::Initialize() +{ + if (initialized_) + return DEVICE_OK; + + // Initalize peak status + status = PEAK_STATUS_SUCCESS; + + // Initialize peak library + status = peak_Library_Init(); + if (status != PEAK_STATUS_SUCCESS) { return ERR_LIBRARY_NOT_INIT; } + + // update camera list + status = peak_CameraList_Update(NULL); + if (status != PEAK_STATUS_SUCCESS) { return ERR_CAMERA_NOT_FOUND; } + + // get length of camera list + size_t cameraListLength = 0; + status = peak_CameraList_Get(NULL, &cameraListLength); + + // exit program if no camera was found + if (status != PEAK_STATUS_SUCCESS) { return ERR_CAMERA_NOT_FOUND; } + + // allocate memory for the camera list + peak_camera_descriptor* cameraList = (peak_camera_descriptor*)calloc( + cameraListLength, sizeof(peak_camera_descriptor)); + + // get the camera list + status = peak_CameraList_Get(cameraList, &cameraListLength); + if (status != PEAK_STATUS_SUCCESS) { return ERR_CAMERA_NOT_FOUND; } + + // Open the cameras and assign cameraIDs + vector cameraIndices; + CPropertyAction* pAct = new CPropertyAction(this, &CIDSPeak::OnChangeCamera); + int nRet = CreateStringProperty(MM::g_Keyword_CameraID, "0", false, pAct); + for (int i = 0; i < cameraListLength; i++) + { + if (peak_Camera_GetAccessStatus(cameraList[i].cameraID) == PEAK_ACCESS_READWRITE) + { + hCams.push_back(PEAK_INVALID_HANDLE); + status = peak_Camera_Open(cameraList[i].cameraID, &hCams[i]); + if (status == PEAK_STATUS_SUCCESS) + { + cameraIndices.push_back(CDeviceUtils::ConvertToString(i)); + } + } + } + // free the camera list, not needed any longer + free(cameraList); + if (cameraIndices.size() == 0) { return ERR_CAMERA_NOT_FOUND; } + + CamID_ = stoi(cameraIndices[0]); + + nRet = SetAllowedValues(MM::g_Keyword_CameraID, cameraIndices); + nCameras_ = cameraIndices.size(); + nRet = CreateIntegerProperty("nCameras", (long)nCameras_, true); + hCam = hCams[CamID_]; + + // check which camera was actually opened + peak_camera_descriptor cameraInfo; + status = peak_Camera_GetDescriptor(peak_Camera_ID_FromHandle(hCam), &cameraInfo); + if (status != PEAK_STATUS_SUCCESS) { return ERR_CAMERA_NOT_FOUND; } + + // set property list + // ----------------- + + // Name + nRet = CreateStringProperty(MM::g_Keyword_Name, g_CameraDeviceName, true); + assert(nRet == DEVICE_OK); + + // Description + nRet = CreateStringProperty(MM::g_Keyword_Description, "IDS Peak Camera Adapter", true); + assert(nRet == DEVICE_OK); + + // CameraName + modelName_ = cameraInfo.modelName; + pAct = new CPropertyAction(this, &CIDSPeak::OnModelName); + nRet = CreateStringProperty(MM::g_Keyword_CameraName, "model name placeholder", true, pAct); + assert(nRet == DEVICE_OK); + + // SerialNumber + serialNum_ = cameraInfo.serialNumber; + pAct = new CPropertyAction(this, &CIDSPeak::OnSerialNumber); + nRet = CreateStringProperty("Serial Number", "serial number placeholder", true, pAct); + assert(nRet == DEVICE_OK); + + // binning + pAct = new CPropertyAction(this, &CIDSPeak::OnBinning); + nRet = CreateIntegerProperty(MM::g_Keyword_Binning, 0, false, pAct); + assert(nRet == DEVICE_OK); + + // pixel type + pAct = new CPropertyAction(this, &CIDSPeak::OnPixelType); + nRet = CreateStringProperty(MM::g_Keyword_PixelType, "pixeltype placeholder", false, pAct); + assert(nRet == DEVICE_OK); + + // Exposure time + nRet = CreateFloatProperty(MM::g_Keyword_Exposure, exposureCur_, false); + assert(nRet == DEVICE_OK); + + // Frame rate + pAct = new CPropertyAction(this, &CIDSPeak::OnFrameRate); + nRet = CreateFloatProperty("MDA framerate", 1, false, pAct); + assert(nRet == DEVICE_OK); + + // Auto white balance + initializeAutoWBConversion(); + status = peak_AutoWhiteBalance_Mode_Get(hCam, &peakAutoWhiteBalance_); + pAct = new CPropertyAction(this, &CIDSPeak::OnAutoWhiteBalance); + nRet = CreateStringProperty("Auto white balance", "Off", false, pAct); + assert(nRet == DEVICE_OK); + + vector autoWhiteBalanceValues; + autoWhiteBalanceValues.push_back("Off"); + autoWhiteBalanceValues.push_back("Once"); + autoWhiteBalanceValues.push_back("Continuous"); + + nRet = SetAllowedValues("Auto white balance", autoWhiteBalanceValues); + if (nRet != DEVICE_OK) + return nRet; + + // Gain master + status = peak_Gain_GetRange(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_MASTER, &gainMin_, &gainMax_, &gainInc_); + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_MASTER, &gainMaster_); + pAct = new CPropertyAction(this, &CIDSPeak::OnGainMaster); + nRet = CreateFloatProperty("Gain Master", 1.0, false, pAct); + assert(nRet == DEVICE_OK); + nRet = SetPropertyLimits("Gain Master", gainMin_, gainMax_); + if (nRet != DEVICE_OK) + return nRet; + + // Gain Red (should be set after gain master) + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_RED, &gainRed_); + pAct = new CPropertyAction(this, &CIDSPeak::OnGainRed); + nRet = CreateFloatProperty("Gain Red", gainRed_, false, pAct); + assert(nRet == DEVICE_OK); + nRet = SetPropertyLimits("Gain Red", gainMin_, gainMax_); + if (nRet != DEVICE_OK) + return nRet; + + // Gain Green (should be set after gain master) + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_GREEN, &gainGreen_); + pAct = new CPropertyAction(this, &CIDSPeak::OnGainGreen); + nRet = CreateFloatProperty("Gain Green", gainGreen_, false, pAct); + assert(nRet == DEVICE_OK); + nRet = SetPropertyLimits("Gain Green", gainMin_, gainMax_); + if (nRet != DEVICE_OK) + return nRet; + + //Gain Blue (should be called after gain master) + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_BLUE, &gainBlue_); + pAct = new CPropertyAction(this, &CIDSPeak::OnGainBlue); + nRet = CreateFloatProperty("Gain Blue", gainBlue_, false, pAct); + assert(nRet == DEVICE_OK); + nRet = SetPropertyLimits("Gain Blue", gainMin_, gainMax_); + if (nRet != DEVICE_OK) + return nRet; + + // camera temperature ReadOnly, and request camera temperature + pAct = new CPropertyAction(this, &CIDSPeak::OnCCDTemp); + nRet = CreateFloatProperty("CCDTemperature", 0, true, pAct); + assert(nRet == DEVICE_OK); + + // readout time + pAct = new CPropertyAction(this, &CIDSPeak::OnReadoutTime); + nRet = CreateFloatProperty(MM::g_Keyword_ReadoutTime, 0, false, pAct); + assert(nRet == DEVICE_OK); + + // CCD size of the camera we are modeling + // getSensorInfo needs to be called before the CreateIntegerProperty + // calls, othewise the default (512) values will be displayed. + pAct = new CPropertyAction(this, &CIDSPeak::OnCameraCCDXSize); + nRet = CreateIntegerProperty("OnCameraCCDXSize", 512, true, pAct); + assert(nRet == DEVICE_OK); + pAct = new CPropertyAction(this, &CIDSPeak::OnCameraCCDYSize); + nRet = CreateIntegerProperty("OnCameraCCDYSize", 512, true, pAct); + assert(nRet == DEVICE_OK); + + // Trigger device + pAct = new CPropertyAction(this, &CIDSPeak::OnTriggerDevice); + nRet = CreateStringProperty("TriggerDevice", "", false, pAct); + assert(nRet == DEVICE_OK); + + pAct = new CPropertyAction(this, &CIDSPeak::OnSupportsMultiROI); + nRet = CreateIntegerProperty("AllowMultiROI", 0, false, pAct); + assert(nRet == DEVICE_OK); + nRet = AddAllowedValue("AllowMultiROI", "0"); + assert(nRet == DEVICE_OK); + nRet = AddAllowedValue("AllowMultiROI", "1"); + assert(nRet == DEVICE_OK); + + pAct = new CPropertyAction(this, &CIDSPeak::OnMultiROIFillValue); + nRet = CreateIntegerProperty("MultiROIFillValue", 0, false, pAct); + assert(nRet == DEVICE_OK); + nRet = SetPropertyLimits("MultiROIFillValue", 0, 65536); + assert(nRet == DEVICE_OK); + + // Whether or not to use exposure time sequencing + pAct = new CPropertyAction(this, &CIDSPeak::OnIsSequenceable); + std::string propName = "UseExposureSequences"; + nRet = CreateStringProperty(propName.c_str(), "No", false, pAct); + assert(nRet == DEVICE_OK); + nRet = AddAllowedValue(propName.c_str(), "Yes"); + assert(nRet == DEVICE_OK); + nRet = AddAllowedValue(propName.c_str(), "No"); + assert(nRet == DEVICE_OK); + + // initialize image buffer + GenerateEmptyImage(img_); + + // initialize first camera + nRet = cameraChanged(); + if (nRet != DEVICE_OK) { return nRet; } + + // synchronize all properties + // -------------------------- + nRet = UpdateStatus(); + if (nRet != DEVICE_OK) + return nRet; + + initialized_ = true; + return DEVICE_OK; +} + +/** +* Shuts down (unloads) the device. +* Required by the MM::Device API. +* Ideally this method will completely unload the device and release all resources. +* Shutdown() may be called multiple times in a row. +* After Shutdown() we should be allowed to call Initialize() again to load the device +* without causing problems. +*/ +int CIDSPeak::Shutdown() +{ + // Close open camera and set pointer to NULL + for (size_t i = 0; i < nCameras_; i++) + peak_Camera_Close(hCams[i]); + nCameras_ = 0; + hCam = NULL; + + // Close peak library + status = peak_Library_Exit(); + + initialized_ = false; + + return DEVICE_OK; +} + +/** +* Performs exposure and grabs a single image. +* This function should block during the actual exposure and return immediately afterwards +* (i.e., before readout). This behavior is needed for proper synchronization with the shutter. +* Required by the MM::Camera API. +*/ +int CIDSPeak::SnapImage() +{ + int nRet = DEVICE_OK; + static int callCounter = 0; + ++callCounter; + + MM::MMTime startTime = GetCurrentMMTime(); + + unsigned int framesToAcquire = 1; + unsigned int pendingFrames = framesToAcquire; + unsigned int timeoutCount = 0; + + // Make SnapImage responsive, even if low framerate has been set + double framerateTemp = framerateCur_; + nRet = framerateSet(1000 / exposureCur_); + + uint32_t three_frame_times_timeout_ms = (uint32_t)((3000.0 / framerateCur_) + 0.5); + + status = peak_Acquisition_Start(hCam, framesToAcquire); + if (status != PEAK_STATUS_SUCCESS) { return ERR_ACQ_START; } + + while (pendingFrames > 0) + { + peak_frame_handle hFrame; + status = peak_Acquisition_WaitForFrame(hCam, three_frame_times_timeout_ms, &hFrame); + if (status == PEAK_STATUS_TIMEOUT) + { + timeoutCount++; + if (timeoutCount > 99) { return ERR_ACQ_TIMEOUT; } + else { continue; } + } + else if (status == PEAK_STATUS_ABORTED) { break; } + else if (status != PEAK_STATUS_SUCCESS) { return ERR_ACQ_FRAME; } + + // At this point we successfully got a frame handle. We can deal with the info now! + nRet = transferBuffer(hFrame, img_); + + // Now we have transfered all information, we can release the frame. + status = peak_Frame_Release(hCam, hFrame); + if (PEAK_ERROR(status)) { return ERR_ACQ_RELEASE; } + pendingFrames--; + } + readoutStartTime_ = GetCurrentMMTime(); + // Revert framerate back to framerate before snapshot + nRet = framerateSet(framerateTemp); + return nRet; +} + + +/** +* Returns pixel data. +* Required by the MM::Camera API. +* The calling program will assume the size of the buffer based on the values +* obtained from GetImageBufferSize(), which in turn should be consistent with +* values returned by GetImageWidth(), GetImageHight() and GetImageBytesPerPixel(). +* The calling program also assumes that camera never changes the size of +* the pixel buffer on its own. In other words, the buffer can change only if +* appropriate properties are set (such as binning, pixel type, etc.) +*/ +const unsigned char* CIDSPeak::GetImageBuffer() +{ + MMThreadGuard g(imgPixelsLock_); + MM::MMTime readoutTime(readoutUs_); + while (readoutTime > (GetCurrentMMTime() - readoutStartTime_)) {} + unsigned char* pB = (unsigned char*)(img_.GetPixels()); + return pB; +} + +/** +* Returns image buffer X-size in pixels. +* Required by the MM::Camera API. +*/ +unsigned CIDSPeak::GetImageWidth() const +{ + return img_.Width(); +} + +/** +* Returns image buffer Y-size in pixels. +* Required by the MM::Camera API. +*/ +unsigned CIDSPeak::GetImageHeight() const +{ + return img_.Height(); +} + +/** +* Returns image buffer pixel depth in bytes. +* Required by the MM::Camera API. +*/ +unsigned CIDSPeak::GetImageBytesPerPixel() const +{ + return img_.Depth(); +} + +/** +* Returns the bit depth (dynamic range) of the pixel. +* This does not affect the buffer size, it just gives the client application +* a guideline on how to interpret pixel values. +* Required by the MM::Camera API. +*/ +unsigned CIDSPeak::GetBitDepth() const +{ + return bitDepth_; +} + +/** +* Returns the size in bytes of the image buffer. +* Required by the MM::Camera API. +*/ +long CIDSPeak::GetImageBufferSize() const +{ + return img_.Width() * img_.Height() * GetImageBytesPerPixel(); +} + +/** +* Sets the camera Region Of Interest. +* Required by the MM::Camera API. +* This command will change the dimensions of the image. +* Depending on the hardware capabilities the camera may not be able to configure the +* exact dimensions requested - but should try do as close as possible. +* If both xSize and ySize are set to 0, the ROI is set to the entire CCD +* @param x - top-left corner coordinate +* @param y - top-left corner coordinate +* @param xSize - width +* @param ySize - height +*/ +int CIDSPeak::SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) +{ + int nRet = DEVICE_OK; + if (peak_ROI_GetAccessStatus(hCam) == PEAK_ACCESS_READWRITE) + { + multiROIXs_.clear(); + multiROIYs_.clear(); + multiROIWidths_.clear(); + multiROIHeights_.clear(); + if (xSize == 0 && ySize == 0) + { + // effectively clear ROI + nRet = ResizeImageBuffer(); + if (nRet != DEVICE_OK) { return nRet; } + roiX_ = 0; + roiY_ = 0; + xSize = cameraCCDXSize_; + ySize = cameraCCDYSize_; + } + else + { + // If ROI is smaller than the minimum required size, set size to minimum + if (xSize < roiMinSizeX_) { xSize = roiMinSizeX_; } + if (ySize < roiMinSizeY_) { ySize = roiMinSizeY_; } + // If ROI is larger than the CCD, set size to CCD size + if (xSize > (unsigned int)cameraCCDXSize_) { xSize = cameraCCDXSize_; } + if (ySize > (unsigned int)cameraCCDYSize_) { ySize = cameraCCDYSize_; } + // If ROI is not multiple of increment, reduce ROI such that it is + xSize -= xSize % roiInc_; + ySize -= ySize % roiInc_; + // Check if ROI goes out of bounds, if so, push it in + if (x + xSize > (unsigned int)cameraCCDXSize_) { x = cameraCCDXSize_ - xSize; } + if (y + ySize > (unsigned int)cameraCCDYSize_) { y = cameraCCDYSize_ - ySize; } + // apply ROI + img_.Resize(xSize, ySize); + roiX_ = x; + roiY_ = y; + } + // Actually push the ROI settings to the camera + peak_roi roi; + roi.offset.x = roiX_; + roi.offset.y = roiY_; + roi.size.width = xSize; + roi.size.height = ySize; + status = peak_ROI_Set(hCam, roi); + } + else { return DEVICE_CAN_NOT_SET_PROPERTY; } + return nRet; +} + +/** +* Returns the actual dimensions of the current ROI. +* If multiple ROIs are set, then the returned ROI should encompass all of them. +* Required by the MM::Camera API. +*/ +int CIDSPeak::GetROI(unsigned& x, unsigned& y, unsigned& xSize, unsigned& ySize) +{ + x = roiX_; + y = roiY_; + + xSize = img_.Width(); + ySize = img_.Height(); + + return DEVICE_OK; +} + +/** +* Resets the Region of Interest to full frame. +* Required by the MM::Camera API. +*/ +int CIDSPeak::ClearROI() +{ + // Passing all zeros to SetROI sets the ROI to the full frame + int nRet = SetROI(0, 0, 0, 0); + return nRet; +} + +/** + * Queries if the camera supports multiple simultaneous ROIs. + * Optional method in the MM::Camera API; by default cameras do not support + * multiple ROIs. + */ +bool CIDSPeak::SupportsMultiROI() +{ + return supportsMultiROI_; +} + +/** + * Queries if multiple ROIs have been set (via the SetMultiROI method). Must + * return true even if only one ROI was set via that method, but must return + * false if an ROI was set via SetROI() or if ROIs have been cleared. + * Optional method in the MM::Camera API; by default cameras do not support + * multiple ROIs, so this method returns false. + */ +bool CIDSPeak::IsMultiROISet() +{ + return multiROIXs_.size() > 0; +} + +/** + * Queries for the current set number of ROIs. Must return zero if multiple + * ROIs are not set (including if an ROI has been set via SetROI). + * Optional method in the MM::Camera API; by default cameras do not support + * multiple ROIs. + */ +int CIDSPeak::GetMultiROICount(unsigned int& count) +{ + count = (unsigned int)multiROIXs_.size(); + return DEVICE_OK; +} + +/** + * Set multiple ROIs. Replaces any existing ROI settings including ROIs set + * via SetROI. + * Optional method in the MM::Camera API; by default cameras do not support + * multiple ROIs. + * @param xs Array of X indices of upper-left corner of the ROIs. + * @param ys Array of Y indices of upper-left corner of the ROIs. + * @param widths Widths of the ROIs, in pixels. + * @param heights Heights of the ROIs, in pixels. + * @param numROIs Length of the arrays. + */ +int CIDSPeak::SetMultiROI(const unsigned int* xs, const unsigned int* ys, + const unsigned* widths, const unsigned int* heights, + unsigned numROIs) +{ + multiROIXs_.clear(); + multiROIYs_.clear(); + multiROIWidths_.clear(); + multiROIHeights_.clear(); + unsigned int minX = UINT_MAX; + unsigned int minY = UINT_MAX; + unsigned int maxX = 0; + unsigned int maxY = 0; + for (unsigned int i = 0; i < numROIs; ++i) + { + multiROIXs_.push_back(xs[i]); + multiROIYs_.push_back(ys[i]); + multiROIWidths_.push_back(widths[i]); + multiROIHeights_.push_back(heights[i]); + if (minX > xs[i]) + { + minX = xs[i]; + } + if (minY > ys[i]) + { + minY = ys[i]; + } + if (xs[i] + widths[i] > maxX) + { + maxX = xs[i] + widths[i]; + } + if (ys[i] + heights[i] > maxY) + { + maxY = ys[i] + heights[i]; + } + } + img_.Resize(maxX - minX, maxY - minY); + roiX_ = minX; + roiY_ = minY; + return DEVICE_OK; +} + +/** + * Queries for current multiple-ROI setting. May be called even if no ROIs of + * any type have been set. Must return length of 0 in that case. + * Optional method in the MM::Camera API; by default cameras do not support + * multiple ROIs. + * @param xs (Return value) X indices of upper-left corner of the ROIs. + * @param ys (Return value) Y indices of upper-left corner of the ROIs. + * @param widths (Return value) Widths of the ROIs, in pixels. + * @param heights (Return value) Heights of the ROIs, in pixels. + * @param numROIs Length of the input arrays. If there are fewer ROIs than + * this, then this value must be updated to reflect the new count. + */ +int CIDSPeak::GetMultiROI(unsigned* xs, unsigned* ys, unsigned* widths, + unsigned* heights, unsigned* length) +{ + unsigned int roiCount = (unsigned int)multiROIXs_.size(); + if (roiCount > *length) + { + // This should never happen. + return DEVICE_INTERNAL_INCONSISTENCY; + } + for (unsigned int i = 0; i < roiCount; ++i) + { + xs[i] = multiROIXs_[i]; + ys[i] = multiROIYs_[i]; + widths[i] = multiROIWidths_[i]; + heights[i] = multiROIHeights_[i]; + } + *length = roiCount; + return DEVICE_OK; +} + +/** +* Returns the current exposure setting in milliseconds. +* Required by the MM::Camera API. +*/ +double CIDSPeak::GetExposure() const +{ + char buf[MM::MaxStrLength]; + int nRet = GetProperty(MM::g_Keyword_Exposure, buf); + if (nRet != DEVICE_OK) { return 0.; } // If something goes wrong, return 0. + return atof(buf); +} + +/** + * Returns the current exposure from a sequence and increases the sequence counter + * Used for exposure sequences + */ +double CIDSPeak::GetSequenceExposure() +{ + if (exposureSequence_.size() == 0) + return this->GetExposure(); + + double exposure = exposureSequence_[sequenceIndex_]; + + sequenceIndex_++; + if (sequenceIndex_ >= exposureSequence_.size()) + sequenceIndex_ = 0; + + return exposure; +} + +/** +* Sets exposure in milliseconds. +* Required by the MM::Camera API. +*/ +void CIDSPeak::SetExposure(double exp) +{ + // Convert milliseconds to microseconds (peak cameras expect time in microseconds) + // and make exposure set multiple of increment. + double exposureSet = ceil(exp / exposureInc_) * exposureInc_ * 1000; + // Check if we can write to the exposure time of the camera, if not do nothing + if (peak_ExposureTime_GetAccessStatus(hCam) == PEAK_ACCESS_READWRITE) + { + // Check if exposure time is less than the minimun exposure time + // If so, set it to minimum exposure time. + if (exp <= exposureMin_) { + printf("Exposure time too short. Exposure time set to minimum."); + status = peak_ExposureTime_Set(hCam, exposureMin_ * 1000); + } + // Check if exposure time is less than the maximum exposure time + // If so, set it to maximum exposure time. + else if (exp >= exposureMax_) { + printf("Exposure time too long. Exposure time set to maximum."); + status = peak_ExposureTime_Set(hCam, exposureMax_ * 1000); + } + // + else + { + status = peak_ExposureTime_Set(hCam, exposureSet); + } + // Update framerate range + status = peak_FrameRate_GetRange(hCam, &framerateMin_, &framerateMax_, &framerateInc_); + SetPropertyLimits("MDA framerate", framerateMin_, framerateMax_); + + // Set displayed exposure time + status = peak_ExposureTime_Get(hCam, &exposureCur_); + exposureCur_ /= 1000; + SetProperty(MM::g_Keyword_Exposure, CDeviceUtils::ConvertToString(exposureCur_)); + GetCoreCallback()->OnExposureChanged(this, exp); + } +} + +/** +* Returns the current binning factor. +* Required by the MM::Camera API. +*/ +int CIDSPeak::GetBinning() const +{ + char buf[MM::MaxStrLength]; + int nRet = GetProperty(MM::g_Keyword_Binning, buf); + if (nRet != DEVICE_OK) { return 0; } // If something goes wrong, return 0 (unphysical binning) + return atoi(buf); +} + +/** +* Sets binning factor. +* Required by the MM::Camera API. +*/ +int CIDSPeak::SetBinning(int binF) +{ + // Update binning + status = peak_Binning_Set(hCam, (uint32_t)binF, (uint32_t)binF); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_ERR; } + binSize_ = binF; + int nRet = SetProperty(MM::g_Keyword_Binning, CDeviceUtils::ConvertToString(binF)); + return nRet; +} + +int CIDSPeak::IsExposureSequenceable(bool& isSequenceable) const +{ + isSequenceable = isSequenceable_; + return DEVICE_OK; +} + +int CIDSPeak::GetExposureSequenceMaxLength(long& nrEvents) const +{ + if (!isSequenceable_) { + return DEVICE_UNSUPPORTED_COMMAND; + } + + nrEvents = sequenceMaxLength_; + return DEVICE_OK; +} + +int CIDSPeak::StartExposureSequence() +{ + if (!isSequenceable_) { + return DEVICE_UNSUPPORTED_COMMAND; + } + + // may need thread lock + sequenceRunning_ = true; + return DEVICE_OK; +} + +int CIDSPeak::StopExposureSequence() +{ + if (!isSequenceable_) { + return DEVICE_UNSUPPORTED_COMMAND; + } + + // may need thread lock + sequenceRunning_ = false; + sequenceIndex_ = 0; + return DEVICE_OK; +} + +/** + * Clears the list of exposures used in sequences + */ +int CIDSPeak::ClearExposureSequence() +{ + if (!isSequenceable_) { + return DEVICE_UNSUPPORTED_COMMAND; + } + + exposureSequence_.clear(); + return DEVICE_OK; +} + +/** + * Adds an exposure to a list of exposures used in sequences + */ +int CIDSPeak::AddToExposureSequence(double exposureTime_ms) +{ + if (!isSequenceable_) { + return DEVICE_UNSUPPORTED_COMMAND; + } + + exposureSequence_.push_back(exposureTime_ms); + return DEVICE_OK; +} + +int CIDSPeak::SendExposureSequence() const { + if (!isSequenceable_) { + return DEVICE_UNSUPPORTED_COMMAND; + } + + return DEVICE_OK; +} + +int CIDSPeak::SetAllowedBinning() +{ + int nRet = DEVICE_OK; + if (peak_Binning_GetAccessStatus(hCam) == PEAK_ACCESS_READWRITE) + { + // Get the binning factors, uses two staged data query (first get length of list, then get list) + size_t binningFactorCount; + status = peak_Binning_FactorY_GetList(hCam, NULL, &binningFactorCount); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_ERR; } + uint32_t* binningFactorList = (uint32_t*)calloc(binningFactorCount, sizeof(uint32_t)); + status = peak_Binning_FactorY_GetList(hCam, binningFactorList, &binningFactorCount); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_ERR; } + + bool curr_bin_invalid = true; + vector binValues; + for (size_t i = 0; i < binningFactorCount; i++) + { + if (binningFactorList[i] == (uint32_t)binSize_) { curr_bin_invalid = false; } + + binValues.push_back(to_string(binningFactorList[i])); + } + + if (curr_bin_invalid) + { + nRet = SetBinning(1); + } + else + { + nRet = SetBinning(binSize_); + } + nRet = ClearAllowedValues(MM::g_Keyword_Binning); + nRet = SetAllowedValues(MM::g_Keyword_Binning, binValues); + return nRet; + } + else { return ERR_NO_WRITE_ACCESS; } +} + + +/** + * Required by the MM::Camera API + * Please implement this yourself and do not rely on the base class implementation + * The Base class implementation is deprecated and will be removed shortly + */ +int CIDSPeak::StartSequenceAcquisition(double interval) +{ + return StartSequenceAcquisition(LONG_MAX, interval, false); +} + +/** +* Stop and wait for the Sequence thread finished +*/ +int CIDSPeak::StopSequenceAcquisition() +{ + if (!thd_->IsStopped()) { + thd_->Stop(); + thd_->wait(); + } + + return DEVICE_OK; +} + +/** +* Simple implementation of Sequence Acquisition +* A sequence acquisition should run on its own thread and transport new images +* coming of the camera into the MMCore circular buffer. +*/ +int CIDSPeak::StartSequenceAcquisition(long numImages, double interval_ms, bool stopOnOverflow) +{ + if (IsCapturing()) + { + return DEVICE_CAMERA_BUSY_ACQUIRING; + } + int nRet = DEVICE_OK; + + // Adjust framerate to match requested interval between frames + nRet = framerateSet(1000 / interval_ms); + + // Wait until shutter is ready + nRet = GetCoreCallback()->PrepareForAcq(this); + if (nRet != DEVICE_OK) + return nRet; + sequenceStartTime_ = GetCurrentMMTime(); + imageCounter_ = 0; + thd_->Start(numImages, interval_ms); + stopOnOverflow_ = stopOnOverflow; + return DEVICE_OK; +} + +/* + * Inserts Image and MetaData into MMCore circular Buffer + */ +int CIDSPeak::InsertImage() +{ + MM::MMTime timeStamp = this->GetCurrentMMTime(); + char label[MM::MaxStrLength]; + this->GetLabel(label); + + // Important: metadata about the image are generated here: + Metadata md; + md.put("Camera", label); + md.put(MM::g_Keyword_Elapsed_Time_ms, CDeviceUtils::ConvertToString((timeStamp - sequenceStartTime_).getMsec())); + md.put(MM::g_Keyword_Metadata_ROI_X, CDeviceUtils::ConvertToString((long)roiX_)); + md.put(MM::g_Keyword_Metadata_ROI_Y, CDeviceUtils::ConvertToString((long)roiY_)); + + char buf[MM::MaxStrLength]; + GetProperty(MM::g_Keyword_Binning, buf); + md.put(MM::g_Keyword_Binning, buf); + + imageCounter_++; + + MMThreadGuard g(imgPixelsLock_); + int nRet = GetCoreCallback()->InsertImage(this, img_.GetPixels(), + img_.Width(), + img_.Height(), + img_.Depth(), + md.Serialize().c_str()); + + if (!stopOnOverflow_ && nRet == DEVICE_BUFFER_OVERFLOW) + { + // do not stop on overflow - just reset the buffer + GetCoreCallback()->ClearImageBuffer(this); + return GetCoreCallback()->InsertImage(this, img_.GetPixels(), + img_.Width(), + img_.Height(), + img_.Depth(), + md.Serialize().c_str()); + } + else { return nRet; } +} + +/* + * Do actual capturing + * Called from inside the thread + */ +int CIDSPeak::RunSequenceOnThread() +{ + int nRet = DEVICE_ERR; + MM::MMTime startTime = GetCurrentMMTime(); + + // Trigger + if (triggerDevice_.length() > 0) { + MM::Device* triggerDev = GetDevice(triggerDevice_.c_str()); + if (triggerDev != 0) { + LogMessage("trigger requested"); + triggerDev->SetProperty("Trigger", "+"); + } + } + + uint32_t three_frame_times_timeout_ms = (uint32_t)(3000 / framerateCur_ + 10); + + peak_frame_handle hFrame; + status = peak_Acquisition_WaitForFrame(hCam, three_frame_times_timeout_ms, &hFrame); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_ERR; } + else { nRet = DEVICE_OK; } + + // At this point we successfully got a frame handle. We can deal with the info now! + nRet = transferBuffer(hFrame, img_); + if (nRet != DEVICE_OK) { return DEVICE_ERR; } + else { nRet = DEVICE_OK; } + + nRet = InsertImage(); + if (nRet != DEVICE_OK) { return DEVICE_ERR; } + else { nRet = DEVICE_OK; } + + // Now we have transfered all information, we can release the frame. + status = peak_Frame_Release(hCam, hFrame); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_ERR; } + else { nRet = DEVICE_OK; } + + return nRet; +}; + +bool CIDSPeak::IsCapturing() { + return !thd_->IsStopped(); +} + +/* + * called from the thread function before exit + */ +void CIDSPeak::OnThreadExiting() throw() +{ + try + { + LogMessage(g_Msg_SEQUENCE_ACQUISITION_THREAD_EXITING); + GetCoreCallback() ? GetCoreCallback()->AcqFinished(this, 0) : DEVICE_OK; + } + catch (...) + { + LogMessage(g_Msg_EXCEPTION_IN_ON_THREAD_EXITING, false); + } +} + + +MySequenceThread::MySequenceThread(CIDSPeak* pCam) + :intervalMs_(default_intervalMS) + , numImages_(default_numImages) + , imageCounter_(0) + , stop_(true) + , suspend_(false) + , camera_(pCam) + , startTime_(0) + , actualDuration_(0) + , lastFrameTime_(0) +{}; + +MySequenceThread::~MySequenceThread() {}; + +void MySequenceThread::Stop() { + MMThreadGuard g(this->stopLock_); + stop_ = true; +} + +void MySequenceThread::Start(long numImages, double intervalMs) +{ + MMThreadGuard g1(this->stopLock_); + MMThreadGuard g2(this->suspendLock_); + numImages_ = numImages; + intervalMs_ = intervalMs; + imageCounter_ = 0; + stop_ = false; + suspend_ = false; + activate(); + actualDuration_ = MM::MMTime{}; + startTime_ = camera_->GetCurrentMMTime(); + lastFrameTime_ = MM::MMTime{}; +} + +//void MySequenceThread::SetIntervalMs(double intervalms) +//{ +// intervalMs_ = intervalms; +//} + +bool MySequenceThread::IsStopped() { + MMThreadGuard g(this->stopLock_); + return stop_; +} + +void MySequenceThread::Suspend() { + MMThreadGuard g(this->suspendLock_); + suspend_ = true; +} + +bool MySequenceThread::IsSuspended() { + MMThreadGuard g(this->suspendLock_); + return suspend_; +} + +void MySequenceThread::Resume() { + MMThreadGuard g(this->suspendLock_); + suspend_ = false; +} + +int MySequenceThread::svc(void) throw() +{ + int nRet = DEVICE_ERR; + peak_status status = PEAK_STATUS_SUCCESS; + try + { + // peak_Acquisition_Start doesn't take LONG_MAX (2.1B) as near infinite, it crashes. + // Instead, if numImages is LONG_MAX, PEAK_INFINITE is passed. This means that sometimes + // the acquisition has to be stopped manually, but since this is properly escaped anyway + // (in case of manual closing live view), this is all handled. + if (numImages_ == LONG_MAX) { status = peak_Acquisition_Start(camera_->hCam, PEAK_INFINITE); } + else { status = peak_Acquisition_Start(camera_->hCam, (uint32_t)numImages_); } + + // Check if acquisition is started properly + if (status != PEAK_STATUS_SUCCESS) { return ERR_ACQ_START; } + + // do-while loop over numImages_ + do + { + nRet = camera_->RunSequenceOnThread(); + } while (nRet == DEVICE_OK && !IsStopped() && imageCounter_++ < numImages_ - 1); + + // If the acquisition is stopped manually, the acquisition has to be properly closed to + // prevent the camera to be locked in acquisition mode. + if (IsStopped()) + { + status = peak_Acquisition_Stop(camera_->hCam); + camera_->LogMessage("SeqAcquisition interrupted by the user\n"); + } + } + catch (...) { + camera_->LogMessage(g_Msg_EXCEPTION_IN_THREAD, false); + } + stop_ = true; + actualDuration_ = camera_->GetCurrentMMTime() - startTime_; + camera_->OnThreadExiting(); + return nRet; +} + + +/////////////////////////////////////////////////////////////////////////////// +// CIDSPeak Action handlers +/////////////////////////////////////////////////////////////////////////////// + +int CIDSPeak::OnMaxExposure(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(exposureMax_); + return DEVICE_OK; + } + else if (eAct == MM::AfterSet) + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + double exposureSet; + pProp->Get(exposureSet); + + if (peak_ExposureTime_GetAccessStatus(hCam) == PEAK_ACCESS_READWRITE) + { + status = peak_ExposureTime_Set(hCam, exposureMax_ * 1000); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_ERR; } // Should not be possible + status = peak_ExposureTime_Get(hCam, &exposureCur_); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_ERR; } // Should not be possible + exposureCur_ /= 1000; + int nRet = SetProperty(MM::g_Keyword_Exposure, CDeviceUtils::ConvertToString(exposureCur_)); + GetCoreCallback()->OnExposureChanged(this, exposureCur_); + return nRet; + } + else { return ERR_NO_WRITE_ACCESS; } + } + return DEVICE_OK; // Should not be possible, but doesn't affect anything +} + +/** +* Handles "Binning" property. +*/ +int CIDSPeak::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + int nRet = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + // the user just set the new value for the property, so we have to + // apply this value to the 'hardware'. + long binFactor; + pProp->Get(binFactor); + if (binFactor > 0 && binFactor < 10) + { + // calculate ROI using the previous bin settings + double factor = (double)binFactor / (double)binSize_; + roiX_ = (unsigned int)(roiX_ / factor); + roiY_ = (unsigned int)(roiY_ / factor); + for (unsigned int i = 0; i < multiROIXs_.size(); ++i) + { + multiROIXs_[i] = (unsigned int)(multiROIXs_[i] / factor); + multiROIYs_[i] = (unsigned int)(multiROIYs_[i] / factor); + multiROIWidths_[i] = (unsigned int)(multiROIWidths_[i] / factor); + multiROIHeights_[i] = (unsigned int)(multiROIHeights_[i] / factor); + } + img_.Resize( + (unsigned int)(img_.Width() / factor), + (unsigned int)(img_.Height() / factor) + ); + binSize_ = binFactor; + std::ostringstream os; + os << binSize_; + OnPropertyChanged("Binning", os.str().c_str()); + nRet = DEVICE_OK; + } + }break; + case MM::BeforeGet: + { + nRet = DEVICE_OK; + pProp->Set(binSize_); + }break; + default: + break; + } + return nRet; +} + +int CIDSPeak::OnFrameRate(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(framerateCur_); + } + else if (eAct == MM::AfterSet) + { + double framerateTemp; + pProp->Get(framerateTemp); + framerateSet(framerateTemp); + } + return DEVICE_OK; +} + +/** +* Handles "Auto whitebalance" property. +*/ +int CIDSPeak::OnAutoWhiteBalance(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + int nRet = DEVICE_OK; + + if (eAct == MM::BeforeGet) + { + if (peak_AutoWhiteBalance_GetAccessStatus(hCam) == PEAK_ACCESS_READWRITE + || peak_AutoWhiteBalance_GetAccessStatus(hCam) == PEAK_ACCESS_READONLY) + { + status = peak_AutoWhiteBalance_Mode_Get(hCam, &peakAutoWhiteBalance_); + } + const char* autoWB = peakAutoToString[peakAutoWhiteBalance_].c_str(); + pProp->Set(autoWB); + } + + else if (eAct == MM::AfterSet) + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + string autoWB; + pProp->Get(autoWB); + + status = peak_AutoWhiteBalance_Mode_Set(hCam, (peak_auto_feature_mode)stringToPeakAuto[autoWB]); + if (status != PEAK_STATUS_SUCCESS) { nRet = ERR_NO_WRITE_ACCESS; } + else { peakAutoWhiteBalance_ = (peak_auto_feature_mode)stringToPeakAuto[autoWB]; } + } + return nRet; +} + +/** +* Handles "Gain master" property. +*/ +int CIDSPeak::OnGainMaster(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + + int nRet = DEVICE_OK; + + if (eAct == MM::BeforeGet) + { + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_MASTER, &gainMaster_); + pProp->Set(gainMaster_); + } + + else if (eAct == MM::AfterSet) + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + double gainMaster; + pProp->Get(gainMaster); + + status = peak_Gain_Set(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_RED, gainMaster); + if (status != PEAK_STATUS_SUCCESS) { nRet = ERR_NO_WRITE_ACCESS; } + else { gainMaster_ = gainMaster; } + } + return nRet; +} + +/** +* Handles "Gain red" property. +*/ +int CIDSPeak::OnGainRed(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + + int nRet = DEVICE_OK; + + if (eAct == MM::BeforeGet) + { + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_RED, &gainRed_); + pProp->Set(gainRed_); + } + + else if (eAct == MM::AfterSet) + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + double gainRed; + pProp->Get(gainRed); + + status = peak_Gain_Set(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_RED, gainRed); + if (status != PEAK_STATUS_SUCCESS) { nRet = ERR_NO_WRITE_ACCESS; } + else { gainRed_ = gainRed; } + + } + return nRet; +} + +/** +* Handles "Gain green" property. +*/ +int CIDSPeak::OnGainGreen(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + + int nRet = DEVICE_OK; + + if (eAct == MM::BeforeGet) + { + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_GREEN, &gainGreen_); + pProp->Set(gainGreen_); + } + + else if (eAct == MM::AfterSet) + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + double gainGreen; + pProp->Get(gainGreen); + + status = peak_Gain_Set(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_GREEN, gainGreen); + if (status != PEAK_STATUS_SUCCESS) { nRet = ERR_NO_WRITE_ACCESS; } + else { gainGreen_ = gainGreen; } + + } + return nRet; +} + +/** +* Handles "Gain blue" property. +*/ +int CIDSPeak::OnGainBlue(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + + int nRet = DEVICE_OK; + + if (eAct == MM::BeforeGet) + { + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_BLUE, &gainBlue_); + pProp->Set(gainRed_); + } + + else if (eAct == MM::AfterSet) + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + double gainBlue; + pProp->Get(gainBlue); + + status = peak_Gain_Set(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_BLUE, gainBlue); + if (status != PEAK_STATUS_SUCCESS) { nRet = ERR_NO_WRITE_ACCESS; } + else { gainBlue_ = gainBlue; } + + } + return nRet; +} + +/** +* Handles "PixelType" property. +*/ +int CIDSPeak::OnPixelType(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + int nRet = DEVICE_OK; + switch (eAct) + { + case MM::AfterSet: + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + string pixelType; + pProp->Get(pixelType); + + if (peak_PixelFormat_GetAccessStatus(hCam) == PEAK_ACCESS_READWRITE) + { + if (pixelType == g_PixelType_8bit) + { + status = peak_PixelFormat_Set(hCam, PEAK_PIXEL_FORMAT_MONO8); + nComponents_ = 1; + } + else + { + status = peak_PixelFormat_Set(hCam, PEAK_PIXEL_FORMAT_BAYER_RG8); + nComponents_ = 4; + } + } + else + { + return ERR_NO_WRITE_ACCESS; + } + + // Only 8bit formats are supported for now + bitDepth_ = 8; + + // Resize buffer to accomodate the new image + img_.Resize(img_.Width(), img_.Height(), nComponents_ * (bitDepth_ / 8)); + nRet = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + if (nComponents_ == 1) + { + pProp->Set(g_PixelType_8bit); + } + else + { + pProp->Set(g_PixelType_32bitRGBA); + } + + } break; + default: + break; + } + return nRet; +} + +/** +* Handles "ReadoutTime" property. +* Not sure this does anything... +*/ +int CIDSPeak::OnReadoutTime(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::AfterSet) + { + double readoutMs; + pProp->Get(readoutMs); + + readoutUs_ = readoutMs * 1000.0; + } + else if (eAct == MM::BeforeGet) + { + pProp->Set(readoutUs_ / 1000.0); + } + + return DEVICE_OK; +} + +int CIDSPeak::OnSupportsMultiROI(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::AfterSet) + { + long tvalue = 0; + pProp->Get(tvalue); + supportsMultiROI_ = (tvalue != 0); + } + else if (eAct == MM::BeforeGet) + { + pProp->Set((long)supportsMultiROI_); + } + + return DEVICE_OK; +} + +int CIDSPeak::OnMultiROIFillValue(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::AfterSet) + { + long tvalue = 0; + pProp->Get(tvalue); + multiROIFillValue_ = (int)tvalue; + } + else if (eAct == MM::BeforeGet) + { + pProp->Set((long)multiROIFillValue_); + } + + return DEVICE_OK; +} + +int CIDSPeak::OnCameraCCDXSize(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(cameraCCDXSize_); + } + else if (eAct == MM::AfterSet) + { + long value; + pProp->Get(value); + if ((value < 16) || (33000 < value)) + return DEVICE_ERR; // invalid image size + if (value != cameraCCDXSize_) + { + cameraCCDXSize_ = value; + img_.Resize(cameraCCDXSize_ / binSize_, cameraCCDYSize_ / binSize_); + } + } + return DEVICE_OK; + +} + +int CIDSPeak::OnCameraCCDYSize(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(cameraCCDYSize_); + } + else if (eAct == MM::AfterSet) + { + long value; + pProp->Get(value); + if ((value < 16) || (33000 < value)) + return DEVICE_ERR; // invalid image size + if (value != cameraCCDYSize_) + { + cameraCCDYSize_ = value; + img_.Resize(cameraCCDXSize_ / binSize_, cameraCCDYSize_ / binSize_); + } + } + return DEVICE_OK; + +} + +int CIDSPeak::OnTriggerDevice(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(triggerDevice_.c_str()); + } + else if (eAct == MM::AfterSet) + { + pProp->Get(triggerDevice_); + } + return DEVICE_OK; +} + + +int CIDSPeak::OnCCDTemp(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + // This is a readonly function + if (eAct == MM::BeforeGet) + { + status = getTemperature(&ccdT_); + pProp->Set(ccdT_); + } + return DEVICE_OK; +} + +int CIDSPeak::OnModelName(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + int nRet = DEVICE_OK; + // This is a readonly function + if (eAct == MM::BeforeGet) + { + pProp->Set(modelName_.c_str()); + } + return nRet; +} + +int CIDSPeak::OnSerialNumber(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + int nRet = DEVICE_OK; + // This is a readonly function + if (eAct == MM::BeforeGet) + { + pProp->Set(serialNum_.c_str()); + } + return nRet; +} + +int CIDSPeak::OnIsSequenceable(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + std::string val = "Yes"; + if (eAct == MM::BeforeGet) + { + if (!isSequenceable_) + { + val = "No"; + } + pProp->Set(val.c_str()); + } + else if (eAct == MM::AfterSet) + { + isSequenceable_ = false; + pProp->Get(val); + if (val == "Yes") + { + isSequenceable_ = true; + } + } + + return DEVICE_OK; +} + +int CIDSPeak::OnChangeCamera(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + int nRet = DEVICE_OK; + if (eAct == MM::BeforeGet) + { + pProp->Set(CDeviceUtils::ConvertToString(CamID_)); + } + else if (eAct == MM::AfterSet) + { + string CamID_temp; + pProp->Get(CamID_temp); + CamID_ = stoi(CamID_temp); + hCam = hCams[CamID_]; + cameraChanged(); + } + return nRet; +} + +/////////////////////////////////////////////////////////////////////////////// +// Private CIDSPeak methods +/////////////////////////////////////////////////////////////////////////////// + +/** +* Sync internal image buffer size to the chosen property values. +*/ +int CIDSPeak::ResizeImageBuffer() +{ + char buf[MM::MaxStrLength]; + int nRet = GetProperty(MM::g_Keyword_Binning, buf); + if (nRet != DEVICE_OK) + return nRet; + binSize_ = atol(buf); + + img_.Resize(cameraCCDXSize_ / binSize_, cameraCCDYSize_ / binSize_, nComponents_ * (bitDepth_/8)); + return DEVICE_OK; +} + +void CIDSPeak::GenerateEmptyImage(ImgBuffer& img) +{ + MMThreadGuard g(imgPixelsLock_); + if (img.Height() == 0 || img.Width() == 0 || img.Depth() == 0) + return; + unsigned char* pBuf = const_cast(img.GetPixels()); + memset(pBuf, 0, img.Height() * img.Width() * img.Depth()); +} + +peak_status CIDSPeak::getTemperature(double* sensorTemp) +{ + size_t enumerationEntryCount = 0; + + if (PEAK_IS_READABLE( + peak_GFA_Feature_GetAccessStatus(hCam, PEAK_GFA_MODULE_REMOTE_DEVICE, "DeviceFirmwareVersion"))) + { + // get the length of the feature string + status = peak_GFA_Enumeration_GetList(hCam, PEAK_GFA_MODULE_REMOTE_DEVICE, "DeviceTemperatureSelector", NULL, &enumerationEntryCount); + status = getGFAfloat("DeviceTemperature", sensorTemp); + } + else + { + printf("No read access to device temperature"); + } + return status; +} + +int CIDSPeak::cleanExit() +{ + + // Clean up before exit + // Stop acquisition, if running + if (peak_Acquisition_IsStarted(hCam)) + { + // Stop acquisition + status = peak_Acquisition_Stop(hCam); + checkForSuccess(status, PEAK_TRUE); + } + + // Close camera, if open + if (hCam != PEAK_INVALID_HANDLE) + { + // Close Camera + status = peak_Camera_Close(hCam); + checkForSuccess(status, PEAK_TRUE); + } + + // Exit library + status = peak_Library_Exit(); + checkForSuccess(status, PEAK_TRUE); + + return status; +} + +//Returns PEAK_TRUE, if function was successful. +//Returns PEAK_FALSE, if function returned with an error. If continueExecution == PEAK_FALSE, +//the backend is exited. +peak_bool CIDSPeak::checkForSuccess(peak_status checkStatus, peak_bool continueExecution) +{ + if (PEAK_ERROR(checkStatus)) + { + peak_status lastErrorCode = PEAK_STATUS_SUCCESS; + size_t lastErrorMessageSize = 0; + + // Get size of error message + status = peak_Library_GetLastError(&lastErrorCode, NULL, &lastErrorMessageSize); + if (PEAK_ERROR(status)) + { + // Something went wrong getting the last error! + printf("Last-Error: Getting last error code failed! Status: %#06x\n", status); + return PEAK_FALSE; + } + + if (checkStatus != lastErrorCode) + { + // Another error occured in the meantime. Proceed with the last error. + printf("Last-Error: Another error occured in the meantime!\n"); + } + + // Allocate and zero-initialize the char array for the error message + char* lastErrorMessage = (char*)calloc((lastErrorMessageSize) / sizeof(char), sizeof(char)); + if (lastErrorMessage == NULL) + { + // Cannot allocate lastErrorMessage. Most likely not enough Memory. + printf("Last-Error: Failed to allocate memory for the error message!\n"); + free(lastErrorMessage); + return PEAK_FALSE; + } + + // Get the error message + status = peak_Library_GetLastError(&lastErrorCode, lastErrorMessage, &lastErrorMessageSize); + if (PEAK_ERROR(status)) + { + // Unable to get error message. This shouldn't ever happen. + printf("Last-Error: Getting last error message failed! Status: %#06x; Last error code: %#06x\n", status, + lastErrorCode); + free(lastErrorMessage); + return PEAK_FALSE; + } + + printf("Last-Error: %s | Code: %#06x\n", lastErrorMessage, lastErrorCode); + free(lastErrorMessage); + + if (!continueExecution) + { + cleanExit(); + } + + return PEAK_FALSE; + } + return PEAK_TRUE; +} + +int CIDSPeak::getSensorInfo() +{ + // check if the feature is readable + if (PEAK_IS_READABLE( + peak_GFA_Feature_GetAccessStatus(hCam, PEAK_GFA_MODULE_REMOTE_DEVICE, "DeviceFirmwareVersion"))) + { + int64_t temp_x; + int64_t temp_y; + status = getGFAInt("WidthMax", &temp_x); + status = getGFAInt("HeightMax", &temp_y); + cameraCCDXSize_ = (long)temp_x; + cameraCCDYSize_ = (long)temp_y; + } + else + { + return ERR_NO_READ_ACCESS; + } + if (status == PEAK_STATUS_SUCCESS) { return DEVICE_OK; } + else { return DEVICE_ERR; } +} + +peak_status CIDSPeak::getGFAString(const char* featureName, char* stringValue) +{ + size_t stringLength = 0; + + // get the length of the feature string + status = peak_GFA_String_Get(hCam, PEAK_GFA_MODULE_REMOTE_DEVICE, featureName, NULL, &stringLength); + + // if successful, read the firmware version + if (checkForSuccess(status, PEAK_TRUE)) + { + // read the string value of featureName + status = peak_GFA_String_Get( + hCam, PEAK_GFA_MODULE_REMOTE_DEVICE, featureName, stringValue, &stringLength); + } + return status; +} + +peak_status CIDSPeak::getGFAInt(const char* featureName, int64_t* intValue) +{ + // read the integer value of featureName + status = peak_GFA_Integer_Get( + hCam, PEAK_GFA_MODULE_REMOTE_DEVICE, featureName, intValue); + return status; +} + +peak_status CIDSPeak::getGFAfloat(const char* featureName, double* floatValue) +{ + // read the float value of featureName + status = peak_GFA_Float_Get( + hCam, PEAK_GFA_MODULE_REMOTE_DEVICE, featureName, floatValue); + return status; +} + +void CIDSPeak::initializeAutoWBConversion() +{ + peakAutoToString.insert(pair(PEAK_AUTO_FEATURE_MODE_OFF, "Off")); + peakAutoToString.insert(pair(PEAK_AUTO_FEATURE_MODE_ONCE, "Once")); + peakAutoToString.insert(pair(PEAK_AUTO_FEATURE_MODE_CONTINUOUS, "Continuous")); + + stringToPeakAuto.insert(pair("Off", PEAK_AUTO_FEATURE_MODE_OFF)); + stringToPeakAuto.insert(pair("Once", PEAK_AUTO_FEATURE_MODE_ONCE)); + stringToPeakAuto.insert(pair("Continuous", PEAK_AUTO_FEATURE_MODE_CONTINUOUS)); +} + +int CIDSPeak::transferBuffer(peak_frame_handle hFrame, ImgBuffer& img) +{ + peak_frame_handle hFrameConverted; + peak_buffer peakBuffer; + uint8_t* memoryAddress; + size_t memorySize; + unsigned char* pBuf = (unsigned char*) const_cast(img.GetPixels()); + + // Convert data types to MM supported data types + // Monochrome is natively supported by MM, so no conversion is needed + if (nComponents_ == 1) + { + status = peak_Frame_Buffer_Get(hFrame, &peakBuffer); + // Transfer the frame buffer to the img buffer expected by MM. + memoryAddress = peakBuffer.memoryAddress; + memorySize = peakBuffer.memorySize; + memcpy(pBuf, memoryAddress, memorySize); + } + // Convert all 8bit pixel formats into BGRA8 (8bit format expected by MM) + else if (nComponents_ == 4) + { + status = peak_IPL_PixelFormat_Set(hCam, PEAK_PIXEL_FORMAT_BGRA8); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_UNSUPPORTED_DATA_FORMAT; } + status = peak_IPL_ProcessFrame(hCam, hFrame, &hFrameConverted); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_UNSUPPORTED_DATA_FORMAT; } + status = peak_Frame_Buffer_Get(hFrameConverted, &peakBuffer); + // Transfer the frame buffer to the img buffer expected by MM. + memoryAddress = peakBuffer.memoryAddress; + memorySize = peakBuffer.memorySize; + memcpy(pBuf, memoryAddress, memorySize); + peak_Frame_Release(hCam, hFrameConverted); + } + else + { + return DEVICE_UNSUPPORTED_DATA_FORMAT; + } + + // Exit if something went wrong during the conversion/obtaining the buffer. + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_UNSUPPORTED_DATA_FORMAT; } + + return DEVICE_OK; +} + +int CIDSPeak::updateAutoWhiteBalance() +{ + if (peak_AutoWhiteBalance_GetAccessStatus(hCam) == PEAK_ACCESS_READONLY + || peak_AutoWhiteBalance_GetAccessStatus(hCam) == PEAK_ACCESS_READWRITE) + { + // Update the gain channels + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_MASTER, &gainMaster_); + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_RED, &gainRed_); + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_GREEN, &gainGreen_); + status = peak_Gain_Get(hCam, PEAK_GAIN_TYPE_DIGITAL, PEAK_GAIN_CHANNEL_BLUE, &gainBlue_); + // Update the auto white balance mode + status = peak_AutoWhiteBalance_Mode_Get(hCam, &peakAutoWhiteBalance_); + } + else { return ERR_NO_READ_ACCESS; } + + if (status == PEAK_STATUS_SUCCESS) { return DEVICE_OK; } + else { return DEVICE_ERR; } +} + +int CIDSPeak::framerateSet(double framerate) +{ + // Check if interval doesn't exceed framerate limitations of camera + // Else set interval to match max framerate + if (framerate > framerateMax_) + { + framerate = framerateMax_; + } + else if (framerate < framerateMin_) + { + framerate = framerateMin_; + } + + if (peak_FrameRate_GetAccessStatus(hCam) == PEAK_ACCESS_READWRITE) + { + status = peak_FrameRate_Set(hCam, framerate); + framerateCur_ = framerate; + } + else + { + return ERR_NO_WRITE_ACCESS; + } + return DEVICE_OK; +} + +// Actual initialization of the camera (is called every time camera is swapped). +int CIDSPeak::cameraChanged() +{ + int nRet = DEVICE_OK; + peak_camera_descriptor cameraInfo; + status = peak_Camera_GetDescriptor(peak_Camera_ID_FromHandle(hCam), &cameraInfo); + if (status != PEAK_STATUS_SUCCESS) { return ERR_CAMERA_NOT_FOUND; } + + // CameraName + modelName_ = cameraInfo.modelName; + + // CameraID + serialNum_ = cameraInfo.serialNumber; + + // Binning + nRet = SetAllowedBinning(); + if (nRet != DEVICE_OK) + return nRet; + uint32_t binx; + uint32_t biny; + status = peak_Binning_Get(hCam, &binx, &biny); + binSize_ = (long)binx; + nRet = SetBinning(binSize_); + + // PixelType, assumes 8bit mono is always possible + vector pixelTypeValues; + pixelTypeValues.push_back(g_PixelType_8bit); + if (isColorCamera()) + { + pixelTypeValues.push_back(g_PixelType_32bitRGBA); + } + nRet = ClearAllowedValues(MM::g_Keyword_PixelType); + nRet = SetAllowedValues(MM::g_Keyword_PixelType, pixelTypeValues); + if (nRet != DEVICE_OK) + return nRet; + + peak_pixel_format format; + status = peak_PixelFormat_Get(hCam, &format); + if (format == PEAK_PIXEL_FORMAT_MONO8) + { + pixelType_ = g_PixelType_8bit; + nComponents_ = 1; + SetProperty(MM::g_Keyword_PixelType, g_PixelType_8bit); + } + else + { + pixelType_ = g_PixelType_32bitRGBA; + nComponents_ = 8; + SetProperty(MM::g_Keyword_PixelType, g_PixelType_32bitRGBA); + } + + // Exposure time + status = peak_ExposureTime_GetRange(hCam, &exposureMin_, &exposureMax_, &exposureInc_); + if (status != PEAK_STATUS_SUCCESS) { return ERR_DEVICE_NOT_AVAILABLE; } + exposureMin_ /= 1000; + exposureMax_ /= 1000; + exposureInc_ /= 1000; + nRet = SetPropertyLimits(MM::g_Keyword_Exposure, exposureMin_, exposureMax_); + if (nRet != DEVICE_OK) + return nRet; + status = peak_ExposureTime_Get(hCam, &exposureCur_); + exposureCur_ /= 1000; + + // Framerate range + status = peak_FrameRate_GetRange(hCam, &framerateMin_, &framerateMax_, &framerateInc_); + nRet = SetPropertyLimits("MDA framerate", framerateMin_, framerateMax_); + status = peak_FrameRate_Get(hCam, &framerateCur_); + + // Get sensor size + nRet = getSensorInfo(); + + // Obtain ROI properties + // The SetROI function used the CCD size, so this function should + // always be put after the getSensorInfo call + // It is assumed that the maximum ROI size is the size of the CCD + // and that the increment in X and Y are identical + peak_size roi_size_min; + peak_size roi_size_max; + peak_size roi_size_inc; + peak_roi roi; + status = peak_ROI_Size_GetRange(hCam, &roi_size_min, &roi_size_max, &roi_size_inc); + if (status != PEAK_STATUS_SUCCESS) { return DEVICE_ERR; } + roiMinSizeX_ = roi_size_min.width; + roiMinSizeY_ = roi_size_min.height; + roiInc_ = roi_size_inc.height; + status = peak_ROI_Get(hCam, &roi); + SetROI(roi.offset.x, roi.offset.y, roi.size.width, roi.size.height); + img_.Resize(roi.size.width, roi.size.height, nComponents_ * (bitDepth_ / 8)); + + if (nRet != DEVICE_OK) + return nRet; + return nRet; +} + +// Checks if camera supportes color image formats +bool CIDSPeak::isColorCamera() +{ + size_t pixelFormatCount = 0; + status = peak_PixelFormat_GetList(hCam, NULL, &pixelFormatCount); + peak_pixel_format* pixelFormatList = (peak_pixel_format*)calloc( + pixelFormatCount, sizeof(peak_pixel_format)); + status = peak_PixelFormat_GetList(hCam, pixelFormatList, &pixelFormatCount); + for (int i = 0; i < pixelFormatCount; i++) + { + if (pixelFormatList[i] == PEAK_PIXEL_FORMAT_BAYER_RG8) { return true; } + } + return false; +} \ No newline at end of file diff --git a/DeviceAdapters/IDSPeak/IDSPeak.h b/DeviceAdapters/IDSPeak/IDSPeak.h new file mode 100644 index 000000000..5210be769 --- /dev/null +++ b/DeviceAdapters/IDSPeak/IDSPeak.h @@ -0,0 +1,294 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: IDSPeak.h +// PROJECT: Micro-Manager +// SUBSYSTEM: DeviceAdapters +//----------------------------------------------------------------------------- +// DESCRIPTION: Driver for IDS peak series of USB cameras +// +// Based on IDS peak SDK and Micromanager DemoCamera example +// tested with SDK version 2.5 +// Requires Micro-manager Device API 71 or higher! +// +// AUTHOR: Lars Kool, Institut Pierre-Gilles de Gennes +// +// YEAR: 2023 +// +// VERSION: 1.1 +// +// LICENSE: This file is distributed under the BSD license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +//LAST UPDATE: 09.10.2023 LK + +#ifndef _IDSPeak_H_ +#define _IDSPeak_H_ + +#include "DeviceBase.h" +#include "ImgBuffer.h" +#include "DeviceThreads.h" +#include +#include +#include +#include +#include + +#include + +using namespace std; + +#define EXPOSURE_MAX 1000000 + +//////////////////////////////////////// +// Error codes +//////////////////////////////////////// +#define ERR_LIBRARY_NOT_INIT 101 +#define ERR_UNKNOWN_MODE 102 +#define ERR_UNKNOWN_POSITION 103 +#define ERR_IN_SEQUENCE 104 +#define ERR_SEQUENCE_INACTIVE 105 +#define ERR_STAGE_MOVING 106 +#define HUB_NOT_AVAILABLE 107 +#define ERR_MEM_ALLOC 108 +#define ERR_ROI_INVALID 109 +#define ERR_CAMERA_NOT_FOUND 110 +#define ERR_DEVICE_NOT_AVAILABLE 111 +#define ERR_NO_READ_ACCESS 112 +#define ERR_ACQ_START 113 +#define ERR_ACQ_FRAME 114 +#define ERR_ACQ_RELEASE 115 +#define ERR_ACQ_TIMEOUT 116 +#define ERR_NO_WRITE_ACCESS 117 + +const char* NoHubError = "Parent Hub not defined."; + +////////////////////////////////////////////////////////////////////////////// +// CIDSPeak class +////////////////////////////////////////////////////////////////////////////// + +class MySequenceThread; + +class CIDSPeak : public CCameraBase +{ +public: + CIDSPeak(); + ~CIDSPeak(); + + // MMDevice API + // ------------ + int Initialize(); + int Shutdown(); + + vector hCams; + peak_camera_handle hCam = PEAK_INVALID_HANDLE; + peak_status status = PEAK_STATUS_SUCCESS; + void GetName(char* name) const; + int CamID_; + + // MMCamera API + // ------------ + int SnapImage(); + const unsigned char* GetImageBuffer(); + unsigned GetImageWidth() const; + unsigned GetImageHeight() const; + unsigned GetImageBytesPerPixel() const; + unsigned GetBitDepth() const; + long GetImageBufferSize() const; + double GetExposure() const; + void SetExposure(double exp); + int SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize); + int GetROI(unsigned& x, unsigned& y, unsigned& xSize, unsigned& ySize); + int ClearROI(); + bool SupportsMultiROI(); + bool IsMultiROISet(); + int GetMultiROICount(unsigned& count); + int SetMultiROI(const unsigned* xs, const unsigned* ys, + const unsigned* widths, const unsigned* heights, + unsigned numROIs); + int GetMultiROI(unsigned* xs, unsigned* ys, unsigned* widths, + unsigned* heights, unsigned* length); + int PrepareSequenceAcqusition() { return DEVICE_OK; } + int StartSequenceAcquisition(double interval); + int StartSequenceAcquisition(long numImages, double interval_ms, bool stopOnOverflow); + int StopSequenceAcquisition(); + int InsertImage(); + int RunSequenceOnThread(); + bool IsCapturing(); + void OnThreadExiting() throw(); + double GetNominalPixelSizeUm() const { return nominalPixelSizeUm_; } + double GetPixelSizeUm() const { return nominalPixelSizeUm_ * GetBinning(); } + int GetBinning() const; + int SetBinning(int bS); + + int IsExposureSequenceable(bool& isSequenceable) const; + int GetExposureSequenceMaxLength(long& nrEvents) const; + int StartExposureSequence(); + int StopExposureSequence(); + int ClearExposureSequence(); + int AddToExposureSequence(double exposureTime_ms); + int SendExposureSequence() const; + + unsigned GetNumberOfComponents() const { return nComponents_; }; + + // action interface + // ---------------- + int OnChangeCamera(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnModelName(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnSerialNumber(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnMaxExposure(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnPixelType(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnFrameRate(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnReadoutTime(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnCameraCCDXSize(MM::PropertyBase*, MM::ActionType); + int OnCameraCCDYSize(MM::PropertyBase*, MM::ActionType); + int OnTriggerDevice(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnSupportsMultiROI(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnMultiROIFillValue(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnCCDTemp(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnIsSequenceable(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnAutoWhiteBalance(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnGainMaster(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnGainRed(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnGainGreen(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnGainBlue(MM::PropertyBase* pProp, MM::ActionType eAct); + + long GetCCDXSize() { return cameraCCDXSize_; } + long GetCCDYSize() { return cameraCCDYSize_; } + + // My methods + int cleanExit(); + peak_bool checkForSuccess(peak_status status, peak_bool continueExecution); + int getSensorInfo(); + peak_status getGFAString(const char* featureName, char* stringValue); + peak_status getGFAInt(const char* featureName, int64_t* intValue); + peak_status getGFAfloat(const char* featureName, double* floatValue); + peak_status getTemperature(double* sensorTemp); + void initializeAutoWBConversion(); + int transferBuffer(peak_frame_handle hFrame, ImgBuffer& img); + int updateAutoWhiteBalance(); + int framerateSet(double framerate); + int cameraChanged(); + bool isColorCamera(); + + +private: + int SetAllowedBinning(); + void GenerateEmptyImage(ImgBuffer& img); + int ResizeImageBuffer(); + + static const double nominalPixelSizeUm_; + + string modelName_; + string serialNum_; + size_t nCameras_; + double exposureMin_; + double exposureMax_; + double exposureInc_; + double exposureCur_; + double framerateCur_; + double framerateMax_; + double framerateMin_; + double framerateInc_; + ImgBuffer img_; + bool stopOnOverFlow_; + bool initialized_; + double readoutUs_; + MM::MMTime readoutStartTime_; + string pixelType_; + int bitDepth_; + int significantBitDepth_; + int nComponents_; + unsigned roiX_; + unsigned roiY_; + unsigned roiInc_; + unsigned roiMinSizeX_; + unsigned roiMinSizeY_; + MM::MMTime sequenceStartTime_; + bool isSequenceable_; + long sequenceMaxLength_; + bool sequenceRunning_; + unsigned long sequenceIndex_; + double GetSequenceExposure(); + std::vector exposureSequence_; + long imageCounter_; + long binSize_; + long cameraCCDXSize_; + long cameraCCDYSize_; + double ccdT_; + std::string triggerDevice_; + map peakTypeToString; + map stringToPeakType; + + peak_auto_feature_mode peakAutoWhiteBalance_; + map peakAutoToString; + map stringToPeakAuto; + double gainMaster_; + double gainRed_; + double gainGreen_; + double gainBlue_; + double gainMin_; + double gainMax_; + double gainInc_; + + + bool stopOnOverflow_; + + bool supportsMultiROI_; + int multiROIFillValue_; + std::vector multiROIXs_; + std::vector multiROIYs_; + std::vector multiROIWidths_; + std::vector multiROIHeights_; + + MMThreadLock imgPixelsLock_; + friend class MySequenceThread; + + MySequenceThread* thd_; + std::future fut_; +}; + +class MySequenceThread : public MMDeviceThreadBase +{ + friend class CIDSPeak; + enum { default_numImages = 1, default_intervalMS = 100 }; +public: + MySequenceThread(CIDSPeak* pCam); + ~MySequenceThread(); + void Stop(); + void Start(long numImages, double intervalMs); + bool IsStopped(); + void Suspend(); + bool IsSuspended(); + void Resume(); + double GetIntervalMs() { return intervalMs_; } + //void SetIntervalMs(double intervalms); + void SetLength(long images) { numImages_ = images; } + long GetLength() const { return numImages_; } + long GetImageCounter() { return imageCounter_; } + MM::MMTime GetStartTime() { return startTime_; } + MM::MMTime GetActualDuration() { return actualDuration_; } +private: + int svc(void) throw(); + double intervalMs_; + long numImages_; + long imageCounter_; + bool stop_; + bool suspend_; + CIDSPeak* camera_; + MM::MMTime startTime_; + MM::MMTime actualDuration_; + MM::MMTime lastFrameTime_; + MMThreadLock stopLock_; + MMThreadLock suspendLock_; +}; + + +#endif //_IDSPeak_H_ diff --git a/DeviceAdapters/IDSPeak/IDSPeak.vcxproj b/DeviceAdapters/IDSPeak/IDSPeak.vcxproj new file mode 100644 index 000000000..5ef605e43 --- /dev/null +++ b/DeviceAdapters/IDSPeak/IDSPeak.vcxproj @@ -0,0 +1,106 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + {b8c95f39-54bf-40a9-807b-598df2821d55} + + + + + + + + + + + + + + 16.0 + Win32Proj + {823cf77e-8120-41b1-9b25-823a79c93198} + IDSPeak + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + false + + + + true + _DEBUG;IDSPEAK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + C:\Program Files\IDS\ids_peak\comfort_sdk\api\lib\x86_64;C:\Program Files\IDS\ids_peak\comfort_sdk\api\include;%(AdditionalIncludeDirectories) + + + Windows + true + false + + + + + true + true + true + NDEBUG;IDSPEAK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + C:\Program Files\IDS\ids_peak\comfort_sdk\api\lib\x86_64;C:\Program Files\IDS\ids_peak\comfort_sdk\api\include;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + false + + + + + + \ No newline at end of file diff --git a/DeviceAdapters/IDSPeak/IDSPeak.vcxproj.filters b/DeviceAdapters/IDSPeak/IDSPeak.vcxproj.filters new file mode 100644 index 000000000..1ae2eaf2d --- /dev/null +++ b/DeviceAdapters/IDSPeak/IDSPeak.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/DeviceAdapters/IDSPeak/LICENSE b/DeviceAdapters/IDSPeak/LICENSE new file mode 100644 index 000000000..329aef4c9 --- /dev/null +++ b/DeviceAdapters/IDSPeak/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, Lars Kool + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/DeviceAdapters/IDSPeak/README.md b/DeviceAdapters/IDSPeak/README.md new file mode 100644 index 000000000..4bd26162c --- /dev/null +++ b/DeviceAdapters/IDSPeak/README.md @@ -0,0 +1,50 @@ +# Micro-Manager Device Adapter for IDS Peak cameras +Micro-Manager is an application to control microscope hardware, such as cameras. It includes a hardware abstraction layer written in C++ and a user interface written in Java. Micro-Manager needs a translation layer between the driver (written by the manufacturer) and Micro-Manager's C++ backend. This translation layer is called a "Device Adapter" (Micro-Manager has chosen not to call it a "Driver" to distinguish it from the libraries provided by the manufacturers of the devices). + +This GitHub repository contains a Device Adapter for IDS cameras. It contains both the already built .dll (mmgr_dal_IDSPeak.dll) and the C++/h files to build it yourself. Instructions can be found below. The Device Adapter was tested with an IDS USB3-3040CP-HQ Rev 2.2 on Windows 10, and not with any other camera or operating system. Although camera model should not matter, there might be assumed default settings that might not be universally present. A different operating system could be more problematic. + +## Using the precompiled .dll +The following steps will guide you through the process of "installing" the .dll, allowing you to use IDS cameras with Micro-Manager. +1. Clone/download this repository. +2. Download and install "IDS Peak" from the IDS website https://en.ids-imaging.com/downloads.html +3. Copy the "mmgr_dal_IDSPeak.dll" into the root folder of Micro-Manager (e.g. "C:\Program Files\Micro-Manager-2.0") +4. Copy all .dll and .lib files from the IDS Peak software into the root folder of Micro-Manager. The .dll and .lib files can be found in ".\PATH_TO_INSTALL\IDS\ids_peak\comfort_sdk\api\lib\x86_64" (.\PATH_TO_INSTALL is the folder you installed IDS Peak, e.g. "C:\Program Files"). +5. Start Micro-Manager and either walk through the Hardware configuration wizard, or load the .cfg file included in this repository. Make sure the camera is plugged into a USB3 port before launching Micro-Manager. + +## Building the device adapter yourself +More advanced users could build the device adapter themselves, allowing them to tailor the device adapter to their needs. If you just plan to use the default device adapter, there is no benefit to building it yourself. Below you will find a brief walkthrough on building the device adapter. +1. First, follow the Micro-Manager guide on building Micro-Manager (https://micro-manager.org/Building_MM_on_Windows) and on setting up a Visual Studio environment to building device adapters (https://micro-manager.org/Visual_Studio_project_settings_for_device_adapters). +2. In Visual Studio, right-click the project you created in step 1 and choose **Properties**. Under **Configuration Properties > C/C++ > General** add the following folder to the **Additional Include Directories**: ".\ids_peak\comfort_sdk\api\include". You can exit the **Properties** interface now. +3. Right-click the **Project** again, and click **Add > Existing Item**. Browse to ".\ids_peak\comfort_sdk\api\lib\x86_64" and add **"ids_peak_comfort_c.lib"**. +4. Right-click the **Header Files** tab under your project, and click **Add > Existing Item**, add the **"IDSPeak.h"** file. +5. Right-click the **Source Files** tab under your project, and click **Add > Existing Item**, add the **"IDSPeak.cpp"** file. +6. Right-click the **Project**, and click **Build**. The .dll will now be build, and should finish without any warnings/errors. +7. The .dll file can now be found in ".\micro-manager\mmCoreAndDevices\build\Debug\x64". +8. Now you have compiled the .dll, follow the steps under "Using the precompiled .dll" to enable Micro-Manager to communicate with IDS cameras. +9. If you want to use the .dll on a PC other than the one used to build the .dll, it is best to set the Solution Configuration to "Release" (that way the other PC doesn't require an install of Microsoft Visual Studio 2019). The .dll can than be found in ".\micro-manager\mmCoreAndDevices\build\Release\x64". + +## Features +- Imaging in grayscale and 32bit RGBA. One can switch between 8bit grayscale and 32bit RGBA in **Device -> Device Property Browser -> IDSCam - PixelType** +- Multi-camera support. One can switch between cameras using the dropdown in **Device -> Device Property Browser -> IDSCam-CameraID**. The actual ID is an arbitrary zero-indexed identifier. To know which camera is actually open, you can check the **IDSCam-Serial Number** and/or **IDSCam-CameraName**, and compare them to the model and serialnumber of the cameras. Note that switching cameras does not automatically switch settings. + +## Known limitations +- **The maximum framerate of the 32bit RBGA pixel format is much lower than advertized or with IDS Peak Cockpit.** + - This is indeed true, the problem is that the camera doesn't support recording BGRA8, which is the only accepted color format of Micro-Manager. Hence, the image has to be recorded in a different pixel format (in this case Bayer RG8) and then converted to BGRA8 on the fly. The maximum obtainable framerate then depends heavily on the (single core) processing speed of your PC. A potential solution is to not do the conversion (just pass the raw bayer data) and perform the debayering after all data is collected. However his methods is not yet implemented. +- **The minimum interval during the Multi-Dimensional Acquisition (MDA) is approximately 200 ms, even at low exposure times (e.g. 10 ms)** + - This is a limitation of how MDA events are processed. When the interval is set to less than the exposure time, it will record at the maximum framerate possible ~1/exposureTime. Otherwise it will perform something like a timelapse, where it will start the process of acquiring an image after the interval has passed. Sadly the second process has a lot of overhead, which leads to a maximum framerate of ~5 fps. We're currently thinking of ways to fix this. +- **When switching to a cameras, some settings are reset, while others are kept** + - Currently, when switching cameras, the device adapter asks the new camera for its current settings and adapts displayed settings accordingly. Some settings are kept from session to session (typically PixelType, exposureTime and frameRate), while most others are not. If this heavily inhibits the work of others, we could work on a solution where all settings are kept whithin each session, and/or maybe load settings from a config-file. +- **When MM is open, I can't open any IDS camera in another software (e.g. IDS Peak Cockpit)** + - Currently, when MM is started, it opens all cameras and keeps them open untill MM is closed. This allows quicker switching between cameras. But this means that none of the other softwares can communicate with any of the connected IDS cameras (even when they are seemingly not in use by MM). + +## Future features +- Rembering last settings of each camera instance +- More support for other pixel types (10/12 bit grayscale/color) +- Recording Bayer / Packed images in RAW format (to post-process afterwards) +- Give more meaningful error messages +- Improve range of framerates during MDA. + +Note that these are just ideas, no promises are made that these will be implemented in a timely manner (or at all). Other suggestions are more than welcome, either create a github issue or send an email to lars.kool@espci.fr + +## Acknowledgements +This Device Adapter was developed by Lars Kool at Institut Pierre-Gilles de Gennes (Paris, France). \ No newline at end of file From afa89c6f5c77cbeb42f0b1718c6bca4bf74b232a Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Tue, 10 Oct 2023 13:24:39 -0700 Subject: [PATCH 049/141] ArduinoCounter: version 2.0 adds option to invert output. Not fully tested yet. --- .../ArduinoCounter/ArduinoCounter.cpp | 71 +++++++- .../ArduinoCounter/ArduinoCounter.h | 1 + .../ArduinoCounter/ArduinoCounter.ino | 171 ++++++++++++------ 3 files changed, 181 insertions(+), 62 deletions(-) diff --git a/DeviceAdapters/ArduinoCounter/ArduinoCounter.cpp b/DeviceAdapters/ArduinoCounter/ArduinoCounter.cpp index 214515183..53ceb5f2a 100644 --- a/DeviceAdapters/ArduinoCounter/ArduinoCounter.cpp +++ b/DeviceAdapters/ArduinoCounter/ArduinoCounter.cpp @@ -7,7 +7,7 @@ // COPYRIGHT: Altos Labs, 2023, based on code copyright UCSF 2008 // LICENSE: LGPL // -// AUTHOR: Nico Stuurman, nstuurman@altoslabs.com, 7/13/2023 +// AUTHOR: Nico Stuurman, nstuurman@altoslabs.com, 7/13/2023, 10/10/2023 // #include "ArduinoCounter.h" @@ -25,10 +25,13 @@ const char* g_DeviceNameArduinoCounterCamera = "ArduinoCounterCamera"; // Global info about the state of the Arduino. -const double g_Min_MMVersion = 1.0; -const double g_Max_MMVersion = 1.1; +const double g_Min_MMVersion = 2.0; +const double g_Max_MMVersion = 2.0; const char* g_versionProp = "Version"; const char* g_Undefined = "Undefined"; +const char* g_Logic = "Output Logic"; +const char* g_Direct = "Direct"; +const char* g_Invert = "Invert"; @@ -64,7 +67,8 @@ MODULE_API void DeleteDevice(MM::Device* pDevice) ArduinoCounterCamera::ArduinoCounterCamera() : imageBuffer_(0), nrCamerasInUse_(0), - initialized_(false) + initialized_(false), + invert_(false) { InitializeDefaultErrorMessages(); @@ -167,6 +171,11 @@ int ArduinoCounterCamera::Initialize() sversion << version_; CreateProperty(g_versionProp, sversion.str().c_str(), MM::Float, true, pAct); + pAct = new CPropertyAction(this, &ArduinoCounterCamera::OnLogic); + CreateProperty(g_Logic, g_Direct, MM::String, false, pAct); + AddAllowedValue(g_Logic, g_Direct); + AddAllowedValue(g_Logic, g_Invert); + initialized_ = true; @@ -745,6 +754,60 @@ int ArduinoCounterCamera::OnPort(MM::PropertyBase* pProp, MM::ActionType pAct) return DEVICE_OK; } +int ArduinoCounterCamera::OnLogic(MM::PropertyBase* pProp, MM::ActionType pAct) +{ + int ret = DEVICE_OK; + unsigned char command[2]; + command[0] = 'p'; + std::string answer; + if (pAct == MM::BeforeGet) + { + command[1] = '?'; + ret = WriteToComPort(port_.c_str(), (const unsigned char*)command, 2); + if (ret != DEVICE_OK) + return ret; + ret = GetSerialAnswer(port_.c_str(), "\r\n", answer); + if (ret != DEVICE_OK) + return ret; + if (answer == g_Invert) + invert_ = true; + else if (answer == g_Direct) + invert_ = false; + else + return DEVICE_SERIAL_INVALID_RESPONSE; + } + else if (pAct == MM::AfterSet) + { + std::string cmd; + pProp->Get(cmd); + if (cmd == g_Invert) { + command[1] = 'i'; + ret = WriteToComPort(port_.c_str(), (const unsigned char*)command, 2); + if (ret != DEVICE_OK) + return ret; + ret = GetSerialAnswer(port_.c_str(), "\r\n", answer); + if (ret != DEVICE_OK) + return ret; + if (answer != g_Invert) + return DEVICE_SERIAL_INVALID_RESPONSE; + invert_ = true; + } + else if (cmd == g_Direct) { + command[1] = 'd'; + ret = WriteToComPort(port_.c_str(), (const unsigned char*)command, 2); + if (ret != DEVICE_OK) + return ret; + ret = GetSerialAnswer(port_.c_str(), "\r\n", answer); + if (ret != DEVICE_OK) + return ret; + if (answer != g_Direct) + return DEVICE_SERIAL_INVALID_RESPONSE; + invert_ = false; + } + } + return DEVICE_OK; +} + int ArduinoCounterCamera::OnVersion(MM::PropertyBase* pProp, MM::ActionType pAct) { if (pAct == MM::BeforeGet) diff --git a/DeviceAdapters/ArduinoCounter/ArduinoCounter.h b/DeviceAdapters/ArduinoCounter/ArduinoCounter.h index 9bd4d3ecf..41f05f022 100644 --- a/DeviceAdapters/ArduinoCounter/ArduinoCounter.h +++ b/DeviceAdapters/ArduinoCounter/ArduinoCounter.h @@ -136,6 +136,7 @@ class ArduinoCounterCamera : public CCameraBase std::string port_; double version_; bool portAvailable_; + bool invert_; }; diff --git a/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino b/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino index 2efb3f3bc..734980465 100644 --- a/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino +++ b/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino @@ -1,89 +1,144 @@ -unsigned int version_ = 1; +// Arduino firmware that counts input pulses, relays these to +// the output, but stops doing so after a specified +// amount of pulses has been received. +// Since version 2, also can invert the output. +// Communicates through serial, commands: +// gnnn - starts pulse counting. nnn should be an +// integer number (of any size) +// s - Stops counting, and reverts to transducing +// all input to the output +// i - Info: returns identification string +// pn - Sets/Reads the flag inverting the input polarity +// n=i Invert the input to the output +// n=d Do not invert the input to the output +// n=? report output polarity - // pin on which to receive the trigger (2 and 3 can be used with interrupts, although this code does not use interrupts) - const int inPin = 2; - const int outPin = 8; - - const unsigned long timeOut = 1000; +unsigned int version_ = 2; - unsigned int counter = 0; - unsigned int limit; - boolean counting = false; - boolean inputWas; +// pin on which to receive the trigger (2 and 3 can be used with interrupts, although this code does not use interrupts) +const int inPin = 2; + + +const int outPin = 8; + +const unsigned long timeOut = 1000; + +unsigned int counter = 0; +unsigned int limit; +boolean counting = false; +boolean inputWas; +boolean invert = false; void setup() { // put your setup code here, to run once: - Serial.begin(115200); + Serial.begin(115200); - - pinMode(inPin, INPUT); - pinMode(outPin, OUTPUT); + pinMode(inPin, INPUT); + pinMode(outPin, OUTPUT); - inputWas = digitalRead(inPin); - + inputWas = digitalRead(inPin); } void loop() { - while(true){ - // put your main code here, to run repeatedly: - - if (Serial.available() > 0) { - int inByte = Serial.read(); - switch (inByte) { - - // go (i.e. start) 'g' followed by max number of TTLs to pass - case 103: - if (waitForSerial(timeOut)) { - limit = Serial.parseInt(); - Serial.write("Starting with "); - Serial.println(limit, DEC); + while (true) { + // put your main code here, to run repeatedly: + + if (Serial.available() > 0) { + int inByte = Serial.read(); + switch (inByte) { + + // go (i.e. start) 'g' followed by max number of TTLs to pass + case 103: + if (waitForSerial(timeOut)) { + limit = Serial.parseInt(); + Serial.write("Starting with "); + Serial.println(limit, DEC); // set limit here - counting = true; - counter = 0; - inputWas = digitalRead(inPin); - break; - } + counting = true; + counter = 0; + inputWas = digitalRead(inPin); + break; + } // stop 's'; i.e. operate in passthrough mode case 115: counting = false; - Serial.println("Stopping"); + Serial.println("Stopping"); break; // get info 'i'; what version are you? case 105: - Serial.println("ArduinoCounter version 1.0"); - } - - - } - + Serial.println("ArduinoCounter version 2.0"); + break; + // Sets/gets output polarity + case 112: + if (waitForSerial(timeOut)) { + int command = Serial.read(); + switch (command) { + case 105: // "i" + invert = true; + Serial.println("Invert"); + break; + + case 100: // "d" + invert = false; + Serial.println("Direct"); + break; + + case 63: // "?" + if (invert) { + Serial.println("Invert"); + } else { + Serial.println("Direct"); + } + break; + } + } + } + } + } - if (counting) { + if (counting) { + if (invert) { + if (inputWas && !(PIND & B00000100)) { + counter++; + inputWas = LOW; + PORTB = 1; + } else if (!inputWas && (PIND & B00000100)) { + counter++; + inputWas = HIGH; + if (counter <= limit) { + PORTB = 0; + } + } + } else { // do not invert output if (inputWas && !(PIND & B00000100)) { - counter++; - inputWas = LOW; - if (counter <= limit) { - PORTB = 0; - } + counter++; + inputWas = LOW; + if (counter <= limit) { + PORTB = 0; + } } else if (!inputWas && (PIND & B00000100)) { - inputWas = HIGH; - PORTB = 1; + inputWas = HIGH; + PORTB = 1; + } + } + } else { // not counting + if (invert) { + PORTB = !(PIND & B00000100); + } else { + PORTB = (PIND & B00000100) >> 2; } - } else { - PORTB = (PIND & B00000100) >> 2; - } + } } -} -bool waitForSerial(unsigned long timeOut) -{ + bool waitForSerial(unsigned long timeOut) { unsigned long startTime = millis(); - while (Serial.available() == 0 && (millis() - startTime < timeOut) ) {} + while (Serial.available() == 0 && (millis() - startTime < timeOut)) {} if (Serial.available() > 0) - return true; + return true; return false; -} + } From 2599940ef2ba69e81ec6d84aa439cb2b010ecdd5 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Tue, 10 Oct 2023 15:58:15 -0700 Subject: [PATCH 050/141] ArduinoCounter firmware: fix brackets. --- .../ArduinoCounter/ArduinoCounter.ino | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino b/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino index 734980465..5da132724 100644 --- a/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino +++ b/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino @@ -99,34 +99,34 @@ void loop() { } } } - } - if (counting) { - if (invert) { - if (inputWas && !(PIND & B00000100)) { - counter++; - inputWas = LOW; - PORTB = 1; - } else if (!inputWas && (PIND & B00000100)) { - counter++; - inputWas = HIGH; - if (counter <= limit) { - PORTB = 0; + + if (counting) { + if (invert) { + if (inputWas && !(PIND & B00000100)) { + counter++; + inputWas = LOW; + PORTB = 1; + } else if (!inputWas && (PIND & B00000100)) { + counter++; + inputWas = HIGH; + if (counter <= limit) { + PORTB = 0; + } } - } - } else { // do not invert output - if (inputWas && !(PIND & B00000100)) { - counter++; - inputWas = LOW; - if (counter <= limit) { - PORTB = 0; + } else { // do not invert output + if (inputWas && !(PIND & B00000100)) { + counter++; + inputWas = LOW; + if (counter <= limit) { + PORTB = 0; + } + } else if (!inputWas && (PIND & B00000100)) { + inputWas = HIGH; + PORTB = 1; } - } else if (!inputWas && (PIND & B00000100)) { - inputWas = HIGH; - PORTB = 1; } - } - } else { // not counting + } else { // not counting if (invert) { PORTB = !(PIND & B00000100); } else { @@ -134,11 +134,12 @@ void loop() { } } } +} - bool waitForSerial(unsigned long timeOut) { - unsigned long startTime = millis(); - while (Serial.available() == 0 && (millis() - startTime < timeOut)) {} - if (Serial.available() > 0) - return true; - return false; - } +bool waitForSerial(unsigned long timeOut) { + unsigned long startTime = millis(); + while (Serial.available() == 0 && (millis() - startTime < timeOut)) {} + if (Serial.available() > 0) + return true; + return false; +} From e94696542da8b5249bdc293d4e41ad258dd9711b Mon Sep 17 00:00:00 2001 From: imcclenahan Date: Wed, 11 Oct 2023 14:07:56 +0100 Subject: [PATCH 051/141] Update AOIProperty.cpp Fix for AOI's for Sona-6 which is now 2048 x 2046 pixels. --- DeviceAdapters/AndorSDK3/AOIProperty.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DeviceAdapters/AndorSDK3/AOIProperty.cpp b/DeviceAdapters/AndorSDK3/AOIProperty.cpp index c6261984d..e7292a04c 100644 --- a/DeviceAdapters/AndorSDK3/AOIProperty.cpp +++ b/DeviceAdapters/AndorSDK3/AOIProperty.cpp @@ -82,10 +82,11 @@ void TAOIProperty::populateWidthMaps(bool fullAoiControl) if (fullAoiControl) { + auto height = sensor_height_->Get(); aoiWidthIndexMap_[2560] = 0; aoiWidthHeightMap_[2560] = 2160; aoiWidthIndexMap_[2048] = 1; - aoiWidthHeightMap_[2048] = 2048; + aoiWidthHeightMap_[2048] = (height > 2046) ? 2048 : 2046 ; aoiWidthIndexMap_[1920] = 2; aoiWidthHeightMap_[1920] = 1080; aoiWidthIndexMap_[1400] = 3; From 9fb2f5245681a6f3fe3f2a7be8676ef41e352992 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Thu, 12 Oct 2023 13:02:18 -0700 Subject: [PATCH 052/141] ArduinoCounter: Fix double counting pulses bug when inverted. --- DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino | 1 - 1 file changed, 1 deletion(-) diff --git a/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino b/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino index 5da132724..2141a03e9 100644 --- a/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino +++ b/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino @@ -104,7 +104,6 @@ void loop() { if (counting) { if (invert) { if (inputWas && !(PIND & B00000100)) { - counter++; inputWas = LOW; PORTB = 1; } else if (!inputWas && (PIND & B00000100)) { From 9237273fe4925031f0475a2ffa9ec27ed1e61825 Mon Sep 17 00:00:00 2001 From: imcclenahan Date: Wed, 18 Oct 2023 17:07:42 +0100 Subject: [PATCH 053/141] Added Zyla support for SRRF --- DeviceAdapters/AndorSDK3/AndorSDK3.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DeviceAdapters/AndorSDK3/AndorSDK3.cpp b/DeviceAdapters/AndorSDK3/AndorSDK3.cpp index 60f465ecb..a0a0a3221 100644 --- a/DeviceAdapters/AndorSDK3/AndorSDK3.cpp +++ b/DeviceAdapters/AndorSDK3/AndorSDK3.cpp @@ -777,8 +777,8 @@ int CAndorSDK3Camera::Initialize() DDGStepWidthMode_property = new TEnumProperty(TAndorSDK3Strings::DDG_STEP_WIDTH_MODE, cameraDevice->GetEnum(L"DDGStepWidthMode"), this, thd_, snapShotController_, false, true); - // SRRF (for Sona only) - if (0 == s_cameraName.compare(0, 4, "Sona")) + // SRRF (for Sona or Zyla) + if ((0 == s_cameraName.compare(0, 4, "Sona")) || (0 == s_cameraName.compare(0, 4, "Zyla"))) { SRRFCamera_ = new SRRFAndorSDK3Camera(this); if (SRRFCamera_) { From 11d1c4449a5a259975bff737a51d224d36267334 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 19 Oct 2023 15:23:36 -0500 Subject: [PATCH 054/141] MMCore: Add getDeviceInitializationState() --- MMCore/Devices/DeviceInstance.h | 3 +++ MMCore/MMCore.cpp | 25 ++++++++++++++++++++++++- MMCore/MMCore.h | 7 +++++++ MMCoreJ_wrap/pom.xml | 2 +- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/MMCore/Devices/DeviceInstance.h b/MMCore/Devices/DeviceInstance.h index 760e46031..0919ca1a4 100644 --- a/MMCore/Devices/DeviceInstance.h +++ b/MMCore/Devices/DeviceInstance.h @@ -90,6 +90,9 @@ class DeviceInstance // Callback API int LogMessage(const char* msg, bool debugOnly); + bool IsInitialized() const { return initialized_; } + bool HasInitializationBeenAttempted() const { return initializeCalled_; } + protected: // The DeviceInstance object owns the raw device pointer (pDevice) as soon // as the constructor is called, even if the constructor throws. diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index f88bb60ec..bf0ace683 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -113,7 +113,7 @@ using namespace std; * (Keep the 3 numbers on one line to make it easier to look at diffs when * merging/rebasing.) */ -const int MMCore_versionMajor = 10, MMCore_versionMinor = 6, MMCore_versionPatch = 0; +const int MMCore_versionMajor = 10, MMCore_versionMinor = 7, MMCore_versionPatch = 0; /////////////////////////////////////////////////////////////////////////////// @@ -963,6 +963,29 @@ void CMMCore::initializeDevice(const char* label ///< the device to initialize } +/** + * Queries the initialization state of the given device. + * + * @param label the device label + */ +DeviceInitializationState +CMMCore::getDeviceInitializationState(const char* label) const throw (CMMError) +{ + std::shared_ptr pDevice = deviceManager_->GetDevice(label); + + mm::DeviceModuleLockGuard guard(pDevice); + if (pDevice->IsInitialized()) + { + return DeviceInitializationState::InitializedSuccessfully; + } + if (pDevice->HasInitializationBeenAttempted()) + { + return DeviceInitializationState::InitializationFailed; + } + return DeviceInitializationState::Uninitialized; +} + + /** * Updates the state of the entire hardware. diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 71addcb64..fa0aaa1f9 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -112,6 +112,12 @@ namespace mm { typedef unsigned int* imgRGB32; +enum DeviceInitializationState { + Uninitialized, + InitializedSuccessfully, + InitializationFailed, +}; + /// The Micro-Manager Core. /** @@ -147,6 +153,7 @@ class CMMCore void unloadAllDevices() throw (CMMError); void initializeAllDevices() throw (CMMError); void initializeDevice(const char* label) throw (CMMError); + DeviceInitializationState getDeviceInitializationState(const char* label) const throw (CMMError); void reset() throw (CMMError); void unloadLibrary(const char* moduleName) throw (CMMError); diff --git a/MMCoreJ_wrap/pom.xml b/MMCoreJ_wrap/pom.xml index 35699836b..8923fec73 100644 --- a/MMCoreJ_wrap/pom.xml +++ b/MMCoreJ_wrap/pom.xml @@ -3,7 +3,7 @@ org.micro-manager.mmcorej MMCoreJ jar - 10.6.0 + 10.7.0 Micro-Manager Java Interface to MMCore Micro-Manager is open source software for control of automated/motorized microscopes. This specific packages provides the Java interface to the device abstractino layer (MMCore) that is written in C++ with a C-interface http://micro-manager.org From b6848b71b463f3af9b5622ee6ff4e320b8854827 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 26 Oct 2023 15:43:20 -0500 Subject: [PATCH 055/141] AlliedVisionCamera: Use headers in 3rdparty Also remove 32-bit from project file. --- .../AlliedVisionCamera.vcxproj | 77 +------------------ 1 file changed, 2 insertions(+), 75 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj index 96b6f5ded..1aa16787b 100644 --- a/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj +++ b/DeviceAdapters/AlliedVisionCamera/AlliedVisionCamera.vcxproj @@ -1,14 +1,6 @@ - - Debug - Win32 - - - Release - Win32 - Debug x64 @@ -44,19 +36,6 @@ 10.0 - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - DynamicLibrary true @@ -75,16 +54,6 @@ - - - - - - - - - - @@ -96,12 +65,6 @@ - - true - - - false - true C:\Program Files\Micro-Manager-2.0\ @@ -109,49 +72,13 @@ false - - - Level3 - true - WIN32;_DEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - $(VIMBA_X_HOME)/api/include;%(AdditionalIncludeDirectories) - - - Windows - true - false - - - - - Level3 - true - true - true - WIN32;NDEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - $(VIMBA_X_HOME)/api/include;%(AdditionalIncludeDirectories) - - - Windows - true - true - true - false - - true _DEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - $(VIMBA_X_HOME)/api/include;%(AdditionalIncludeDirectories) + $(MM_3RDPARTYPRIVATE)\AVT\VimbaX-2023-1-Win64\api\include;%(AdditionalIncludeDirectories) Windows @@ -167,7 +94,7 @@ NDEBUG;ALLIEDVISIONCAMERA_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - $(VIMBA_X_HOME)/api/include;%(AdditionalIncludeDirectories) + $(MM_3RDPARTYPRIVATE)\AVT\VimbaX-2023-1-Win64\api\include;%(AdditionalIncludeDirectories) Windows From 8eb1882c5da7c88c74e165655dfce706e94ccf39 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 26 Oct 2023 18:50:15 -0500 Subject: [PATCH 056/141] AlliedVisionCamera: Set up with ./configure Also fix a bug in Autoconf function MM_LIB_IFELSE() (allow MM_LIB_SIMPLE() to work even if no function to test is given). --- DeviceAdapters/AlliedVisionCamera/Makefile.am | 18 +++++++++++++----- DeviceAdapters/Makefile.am | 5 ++++- DeviceAdapters/configure.ac | 19 +++++++++++++++++++ m4/mm_lib_ifelse.m4 | 2 +- m4/mm_libs.m4 | 11 +++++++++++ 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/DeviceAdapters/AlliedVisionCamera/Makefile.am b/DeviceAdapters/AlliedVisionCamera/Makefile.am index 1f4fa159a..7719d678e 100644 --- a/DeviceAdapters/AlliedVisionCamera/Makefile.am +++ b/DeviceAdapters/AlliedVisionCamera/Makefile.am @@ -1,9 +1,17 @@ - AUTOMAKE_OPTIONS = subdir-objects -AM_CXXFLAGS = $(MMDEVAPI_CXXFLAGS) -I$(VIMBA_X_HOME)/api/include +AM_CXXFLAGS = $(MMDEVAPI_CXXFLAGS) $(VIMBA_X_CPPFLAGS) deviceadapter_LTLIBRARIES = libmmgr_dal_AlliedVisionCamera.la -libmmgr_dal_AlliedVisionCamera_la_SOURCES = AlliedVisionHub.h AlliedVisionHub.cpp AlliedVisionDeviceBase.h AlliedVisionDeviceBase.cpp AlliedVisionCamera.h AlliedVisionCamera.cpp Loader/Constants.h Loader/LibLoader.h Loader/LibLoader.cpp $(VIMBA_X_HOME)/api/include/VmbC/VmbC.h $(VIMBA_X_HOME)/api/include/VmbC/VmbCommonTypes.h $(VIMBA_X_HOME)/api/include/VmbC/VmbConstants.h $(VIMBA_X_HOME)/api/include/VmbC/VmbCTypeDefinitions.h $(VIMBA_X_HOME)/api/include/VmbImageTransform/VmbTransform.h $(VIMBA_X_HOME)/api/include/VmbImageTransform/VmbTransformTypes.h + +libmmgr_dal_AlliedVisionCamera_la_SOURCES = \ + AlliedVisionCamera.cpp \ + AlliedVisionCamera.h \ + AlliedVisionDeviceBase.cpp \ + AlliedVisionDeviceBase.h \ + AlliedVisionHub.cpp \ + AlliedVisionHub.h \ + Loader/Constants.h \ + Loader/LibLoader.cpp \ + Loader/LibLoader.h + libmmgr_dal_AlliedVisionCamera_la_LIBADD = $(MMDEVAPI_LIBADD) libmmgr_dal_AlliedVisionCamera_la_LDFLAGS = $(MMDEVAPI_LDFLAGS) - -EXTRA_DIST = AlliedVisionCamera.vcproj AlliedVisionCamera.vcproj.filters AlliedVisionCamera.vcproj.user diff --git a/DeviceAdapters/Makefile.am b/DeviceAdapters/Makefile.am index 454243648..ec6b8891a 100644 --- a/DeviceAdapters/Makefile.am +++ b/DeviceAdapters/Makefile.am @@ -1,6 +1,9 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I ../m4 +if BUILD_ALLIED_VISION_CAMERA + ALLIED_VISION_CAMERA = AlliedVisionCamera +endif if BUILD_ANDOR ANDOR = Andor endif @@ -71,6 +74,7 @@ endif # Please keep these ASCII-lexically sorted (pass through sort(1)). SUBDIRS = \ + $(ALLIED_VISION_CAMERA) \ $(ANDOR) \ $(ANDORLASERCOMBINER) \ $(ANDORSDK3) \ @@ -95,7 +99,6 @@ SUBDIRS = \ $(V4L) \ $(ZABER) \ AAAOTF \ - AlliedVisionCamera \ AOTF \ ASIFW1000 \ ASIStage \ diff --git a/DeviceAdapters/configure.ac b/DeviceAdapters/configure.ac index b68f9d88c..7424deba3 100644 --- a/DeviceAdapters/configure.ac +++ b/DeviceAdapters/configure.ac @@ -394,6 +394,22 @@ AS_IF([test "x$want_opencv" != xno], AM_CONDITIONAL([BUILD_OPENCV], [test "x$use_opencv" = xyes]) +# Vimba X (Allied Vision) SDK +MM_ARG_WITH_OPTIONAL_LIB([Vimba X], [vimba-x], [VIMBA_X]) +AS_IF([test "x$want_vimba_x" != xno], +[ + MM_LIB_VIMBA_X([$VIMBA_X_PREFIX], + [use_vimba_x=yes], + [ + use_vimba_x=no + AS_IF([test "x$want_vimba_x" = xyes], + [MM_MSG_OPTIONAL_LIB_FAILURE([Vimba X], [vimba-x])]) + ]) +], +[use_vimba_x=no]) + +AM_CONDITIONAL([BUILD_ALLIED_VISION_CAMERA], [test "x$use_vimba_x" = xyes]) + # Zaber Motion Library (hack: only support 3rdpartypublic copy currently) AC_MSG_CHECKING([for Zaber Motion Library in 3rdpartypublic]) zml_header_to_check="${thirdpartypublic}/Zaber/zaber-motion/include/zaber/motion/library.h" @@ -677,4 +693,7 @@ echo "m4_text_wrap([$use_libusb_0_1], echo "m4_text_wrap([$use_opencv], [ ], [ Build with OpenCV: ])" +echo "m4_text_wrap([$use_vimba_x], + [ ], + [ Build with Vimba X: ])" echo "" diff --git a/m4/mm_lib_ifelse.m4 b/m4/mm_lib_ifelse.m4 index 171f2db00..74b8ec6a3 100644 --- a/m4/mm_lib_ifelse.m4 +++ b/m4/mm_lib_ifelse.m4 @@ -64,7 +64,7 @@ AC_DEFUN([MM_LIB_IFELSE], [ ]) ], [ - mm_lib_ifelse_have_$1=no + mm_lib_ifelse_have_$1=yes ]) ]) diff --git a/m4/mm_libs.m4 b/m4/mm_libs.m4 index 4f6296471..603a2aa14 100644 --- a/m4/mm_libs.m4 +++ b/m4/mm_libs.m4 @@ -105,3 +105,14 @@ AC_DEFUN([MM_LIB_USB_0_1], [ [$1], [-lusb], [usb.h], [usb_init], [$2], [$3]) ]) + + +# Check for Allied Vision Vimba X SDK +# +# MM_LIB_VIMBA_X([Vimba X api prefix], [action-if-found], [action-if-not-found]) +# +# Defines variable VIMBA_X_CPPFLAGS. +# +AC_DEFUN([MM_LIB_VIMBA_X], [ + MM_LIB_SIMPLE([VIMBA_X], [Vimba X], [$1], [], [VmbC/VmbC.h], [], [$2], [$3]) +]) From 7943aa5e6edf87a9b6d0bea9e0afba877ddbf796 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 31 Oct 2023 17:02:52 -0500 Subject: [PATCH 057/141] MMCore: Remove deprecated search paths --- MMCore/MMCore.cpp | 37 ----------------------- MMCore/MMCore.h | 2 -- MMCore/PluginManager.cpp | 65 ++-------------------------------------- MMCore/PluginManager.h | 5 ---- 4 files changed, 2 insertions(+), 107 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index bf0ace683..4fc4596a8 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -646,43 +646,6 @@ std::vector CMMCore::getDeviceAdapterNames() throw (CMMError) return pluginManager_->GetAvailableDeviceAdapters(); } -/** - * Add a list of paths to the legacy device adapter search path list. - * - * Do not use in new code. This adds to a global (static) fallback list that is - * only searched when a device adapter is not located in any of the directories - * set by setDeviceAdapterSearchPaths(). The list is initially empty. - * - * @deprecated Use the non-static setDeviceAdapterSearchPaths() instead. - * - * @param path a list of search paths in a single string - */ -void CMMCore::addSearchPath(const char *path) -{ - if (!path) - return; - - CPluginManager::AddLegacyFallbackSearchPath(path); -} - -/** - * Returns a list of library names available in the search path. - * - * Do not use in new code. For backward compatibility, this method returns the - * list of device adapters available in the default search path(s) and the - * paths added via addSearchPath(). For obvious reasons (since this method is - * static), it will not return device adapters found in the search paths set by - * setDeviceAdapterSearchPaths(). Thus, this method will only work as expected - * when called from legacy code that does not make use of - * setDeviceAdapterSearchPaths(). - * - * @deprecated Use the non-static getDeviceAdapterNames() instead. - */ -vector CMMCore::getDeviceLibraries() throw (CMMError) -{ - return CPluginManager::GetModulesInLegacyFallbackSearchPaths(); -} - /** * Loads a device from the plugin library. * @param label assigned name for the device during the core session diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index fa0aaa1f9..ca6ef8b44 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -197,10 +197,8 @@ class CMMCore ///@{ std::vector getDeviceAdapterSearchPaths(); void setDeviceAdapterSearchPaths(const std::vector& paths); - MMCORE_DEPRECATED(static void addSearchPath(const char *path)); std::vector getDeviceAdapterNames() throw (CMMError); - MMCORE_DEPRECATED(static std::vector getDeviceLibraries() throw (CMMError)); std::vector getAvailableDevices(const char* library) throw (CMMError); std::vector getAvailableDeviceDescriptions(const char* library) throw (CMMError); diff --git a/MMCore/PluginManager.cpp b/MMCore/PluginManager.cpp index 2813b9ea5..fda7a3570 100644 --- a/MMCore/PluginManager.cpp +++ b/MMCore/PluginManager.cpp @@ -63,8 +63,6 @@ const char* const LIB_NAME_SUFFIX = ""; // CPluginManager class // -------------------- -std::vector CPluginManager::fallbackSearchPaths_; - CPluginManager::CPluginManager() { const std::vector paths = GetDefaultSearchPaths(); @@ -188,29 +186,6 @@ CPluginManager::UnloadPluginLibrary(const char* moduleName) } -void -CPluginManager::AddLegacyFallbackSearchPath(const std::string& path) -{ - // TODO Should normalize slashes and cases (depending on platform) before - // comparing. - - // When this function is used, the instance search path - // (preferredSearchPaths_) remains equal to the default. Do not add - // duplicate paths. - std::vector defaultPaths(GetDefaultSearchPaths()); - if (std::find(defaultPaths.begin(), defaultPaths.end(), path) != - defaultPaths.end()) - return; - - // Again, do not add duplicate paths. - if (std::find(fallbackSearchPaths_.begin(), fallbackSearchPaths_.end(), path) != - fallbackSearchPaths_.end()) - return; - - fallbackSearchPaths_.push_back(path); -} - - // TODO Use std::filesystem instead of this. // This stop-gap implementation makes the assumption that the argument is in // the format that could be returned from MMCorePrivate::GetPathOfThisModule() @@ -263,9 +238,7 @@ CPluginManager::GetDefaultSearchPaths() std::vector CPluginManager::GetActualSearchPaths() const { - std::vector paths(preferredSearchPaths_); - paths.insert(paths.end(), fallbackSearchPaths_.begin(), fallbackSearchPaths_.end()); - return paths; + return preferredSearchPaths_; } @@ -349,38 +322,4 @@ CPluginManager::GetAvailableDeviceAdapters() } return modules; -} - - -std::vector -CPluginManager::GetModulesInLegacyFallbackSearchPaths() -{ - // Search in default search paths and any that were added to the legacy path - // list. - std::vector paths(GetDefaultSearchPaths()); - for (std::vector::const_iterator it = fallbackSearchPaths_.begin(), - end = fallbackSearchPaths_.end(); - it != end; ++it) - { - if (std::find(paths.begin(), paths.end(), *it) == paths.end()) - paths.push_back(*it); - } - - std::vector modules; - for (std::vector::const_iterator it = paths.begin(), end = paths.end(); - it != end; ++it) - GetModules(modules, it->c_str()); - - // Check for duplicates - // XXX Is this the right place to be doing this checking? Shouldn't it be an - // error to have duplicates even if we're not listing all libraries? - std::set moduleSet; - for (std::vector::const_iterator it = modules.begin(), end = modules.end(); it != end; ++it) { - if (moduleSet.count(*it)) { - std::string msg("Duplicate libraries found with name \"" + *it + "\""); - throw CMMError(msg.c_str(), DEVICE_DUPLICATE_LIBRARY); - } - } - - return modules; -} +} \ No newline at end of file diff --git a/MMCore/PluginManager.h b/MMCore/PluginManager.h index b6b6c497f..175821c4a 100644 --- a/MMCore/PluginManager.h +++ b/MMCore/PluginManager.h @@ -50,10 +50,6 @@ class CPluginManager /* final */ std::vector GetSearchPaths() const { return preferredSearchPaths_; } std::vector GetAvailableDeviceAdapters(); - // Legacy search path support - static void AddLegacyFallbackSearchPath(const std::string& path); - static std::vector GetModulesInLegacyFallbackSearchPaths(); - /** * Return a device adapter module, loading it if necessary */ @@ -69,7 +65,6 @@ class CPluginManager /* final */ std::string FindInSearchPath(std::string filename); std::vector preferredSearchPaths_; - static std::vector fallbackSearchPaths_; std::map< std::string, std::shared_ptr > moduleMap_; }; From b2cb13cea4aceb61647245500b961eacc97e97fa Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 31 Oct 2023 17:09:29 -0500 Subject: [PATCH 058/141] MMCore: Clean up after removed code --- MMCore/PluginManager.cpp | 28 ++++------------------------ MMCore/PluginManager.h | 10 ++++------ 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/MMCore/PluginManager.cpp b/MMCore/PluginManager.cpp index fda7a3570..125057e07 100644 --- a/MMCore/PluginManager.cpp +++ b/MMCore/PluginManager.cpp @@ -84,15 +84,8 @@ CPluginManager::~CPluginManager() std::string CPluginManager::FindInSearchPath(std::string filename) { - std::vector searchPaths = GetActualSearchPaths(); - - // look in search paths, if there are any - if (searchPaths.size() == 0) - return filename; - - std::vector::const_iterator it; - for (it = searchPaths.begin(); it != searchPaths.end(); it++) { - std::string path(*it); + for (const auto& p : searchPaths_) { + std::string path = p; #ifdef WIN32 path += "\\" + filename + ".dll"; #else @@ -104,11 +97,8 @@ CPluginManager::FindInSearchPath(std::string filename) in.close(); if (!in.fail()) - // we found it! return path; } - - // not found! return filename; } @@ -235,13 +225,6 @@ CPluginManager::GetDefaultSearchPaths() } -std::vector -CPluginManager::GetActualSearchPaths() const -{ - return preferredSearchPaths_; -} - - /** * List all modules (device libraries) at a given path. */ @@ -303,12 +286,9 @@ CPluginManager::GetModules(std::vector &modules, const char* search std::vector CPluginManager::GetAvailableDeviceAdapters() { - std::vector searchPaths = GetActualSearchPaths(); - std::vector modules; - - for (std::vector::const_iterator it = searchPaths.begin(), end = searchPaths.end(); it != end; ++it) - GetModules(modules, it->c_str()); + for (const auto& path : searchPaths_) + GetModules(modules, path.c_str()); // Check for duplicates // XXX Is this the right place to be doing this checking? Shouldn't it be an diff --git a/MMCore/PluginManager.h b/MMCore/PluginManager.h index 175821c4a..aa3b1aa45 100644 --- a/MMCore/PluginManager.h +++ b/MMCore/PluginManager.h @@ -42,12 +42,11 @@ class CPluginManager /* final */ void UnloadPluginLibrary(const char* moduleName); - // Device adapter search paths (there are two sets of search paths; see - // CMMCore method documentation) + // Device adapter search paths template void SetSearchPaths(TStringIter begin, TStringIter end) - { preferredSearchPaths_.assign(begin, end); } - std::vector GetSearchPaths() const { return preferredSearchPaths_; } + { searchPaths_.assign(begin, end); } + std::vector GetSearchPaths() const { return searchPaths_; } std::vector GetAvailableDeviceAdapters(); /** @@ -60,11 +59,10 @@ class CPluginManager /* final */ private: static std::vector GetDefaultSearchPaths(); - std::vector GetActualSearchPaths() const; static void GetModules(std::vector &modules, const char *path); std::string FindInSearchPath(std::string filename); - std::vector preferredSearchPaths_; + std::vector searchPaths_; std::map< std::string, std::shared_ptr > moduleMap_; }; From bcf3fa3d2ac525ada41612a8e591f81fb80245d8 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 31 Oct 2023 17:19:28 -0500 Subject: [PATCH 059/141] MMCore: Remove deprecated image synchro This also removes the config file command ImageSynchro, which is now an error. --- MMCore/MMCore.cpp | 100 ++-------------------------------------------- MMCore/MMCore.h | 6 --- 2 files changed, 4 insertions(+), 102 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 4fc4596a8..2c6197b02 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -1302,25 +1302,6 @@ void CMMCore::waitForConfig(const char* group, const char* configName) throw (CM } } -/** - * Wait for the slowest device in the ImageSynchro list. - * - * @deprecated ImageSynchro will not be supported in the future. - */ -void CMMCore::waitForImageSynchro() throw (CMMError) -{ - for (std::vector< std::weak_ptr >::iterator - it = imageSynchroDevices_.begin(), end = imageSynchroDevices_.end(); - it != end; ++it) - { - std::shared_ptr device = it->lock(); - if (device) - { - waitForDevice(device); - } - } -} - /** * Sets the position of the stage in microns. * @param label the stage device label @@ -2372,10 +2353,6 @@ void CMMCore::snapImage() throw (CMMError) int ret = DEVICE_OK; try { - - // wait for all synchronized devices to stop before taking an image - waitForImageSynchro(); - // open the shutter std::shared_ptr shutter = currentShutterDevice_.lock(); @@ -2434,72 +2411,6 @@ void CMMCore::snapImage() throw (CMMError) } } - -// Predicate used by assignImageSynchro() and removeImageSynchro() -namespace -{ - class DeviceWeakPtrInvalidOrMatches - { - std::shared_ptr theDevice_; - public: - explicit DeviceWeakPtrInvalidOrMatches( - std::shared_ptr theDevice) : - theDevice_(theDevice) - {} - - bool operator()(const std::weak_ptr& ptr) - { - std::shared_ptr aDevice = ptr.lock(); - return (!aDevice || aDevice == theDevice_); - } - }; -} // anonymous namespace - -/** - * Add device to the image-synchro list. Image acquisition waits for all devices - * in this list. - * @param label the device label - * - * @deprecated ImageSynchro will not be supported in the future. - */ -void CMMCore::assignImageSynchro(const char* label) throw (CMMError) -{ - std::shared_ptr device = deviceManager_->GetDevice(label); - - imageSynchroDevices_.erase(std::remove_if(imageSynchroDevices_.begin(), - imageSynchroDevices_.end(), DeviceWeakPtrInvalidOrMatches(device)), - imageSynchroDevices_.end()); - - imageSynchroDevices_.push_back(device); - LOG_DEBUG(coreLogger_) << "Added " << label << " to image-synchro list"; -} - -/** - * Removes device from the image-synchro list. - * @param label the device label - * - * @deprecated ImageSynchro will not be supported in the future. - */ -void CMMCore::removeImageSynchro(const char* label) throw (CMMError) -{ - std::shared_ptr device = deviceManager_->GetDevice(label); - imageSynchroDevices_.erase(std::remove_if(imageSynchroDevices_.begin(), - imageSynchroDevices_.end(), DeviceWeakPtrInvalidOrMatches(device)), - imageSynchroDevices_.end()); - LOG_DEBUG(coreLogger_) << "Removed " << label << " from image-synchro list"; -} - -/** - * Clears the image synchro device list. - * - * @deprecated ImageSynchro will not be supported in the future. - */ -void CMMCore::removeImageSynchroAll() -{ - imageSynchroDevices_.clear(); - LOG_DEBUG(coreLogger_) << "Cleared image-synchro list"; -} - /** * If this option is enabled Shutter automatically opens and closes when the image * is acquired. @@ -6994,13 +6905,10 @@ void CMMCore::loadSystemConfigurationImpl(const char* fileName) throw (CMMError) } else if(tokens[0].compare(MM::g_CFGCommand_ImageSynchro) == 0) { - // define image synchro - // -------------------- - if (tokens.size() != 2) - throw CMMError(getCoreErrorText(MMERR_InvalidCFGEntry) + " (" + - ToQuotedString(line) + ")", - MMERR_InvalidCFGEntry); - assignImageSynchro(tokens[1].c_str()); + // ImageSynchro has been removed + throw CMMError(getCoreErrorText(MMERR_InvalidCFGEntry) + " (" + + ToQuotedString(line) + ")", + MMERR_InvalidCFGEntry); } else if(tokens[0].compare(MM::g_CFGCommand_ParentID) == 0) { diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index ca6ef8b44..f755a1b87 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -245,7 +245,6 @@ class CMMCore void waitForConfig(const char* group, const char* configName) throw (CMMError); bool systemBusy() throw (CMMError); void waitForSystem() throw (CMMError); - MMCORE_DEPRECATED(void waitForImageSynchro() throw (CMMError)); bool deviceTypeBusy(MM::DeviceType devType) throw (CMMError); void waitForDeviceType(MM::DeviceType devType) throw (CMMError); @@ -389,10 +388,6 @@ class CMMCore std::string getCameraChannelName(unsigned int channelNr); long getImageBufferSize(); - MMCORE_DEPRECATED(void assignImageSynchro(const char* deviceLabel) throw (CMMError)); - MMCORE_DEPRECATED(void removeImageSynchro(const char* deviceLabel) throw (CMMError)); - MMCORE_DEPRECATED(void removeImageSynchroAll()); - void setAutoShutter(bool state); bool getAutoShutter(); void setShutterOpen(bool state) throw (CMMError); @@ -676,7 +671,6 @@ class CMMCore PixelSizeConfigGroup* pixelSizeGroup_; CircularBuffer* cbuf_; - std::vector< std::weak_ptr > imageSynchroDevices_; std::shared_ptr pluginManager_; std::shared_ptr deviceManager_; std::map errorText_; From 71c99b94a96f6d66fe68a9b90296fed36803e78d Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 31 Oct 2023 17:31:11 -0500 Subject: [PATCH 060/141] MMCore: Remove deprecated property blocks This also removes the config file command Equipment, which is now an error. --- MMCore/Configuration.cpp | 44 +--------- MMCore/Configuration.h | 53 ------------ MMCore/ErrorCodes.h | 2 +- MMCore/MMCore.cpp | 182 ++------------------------------------- MMCore/MMCore.h | 16 ---- 5 files changed, 10 insertions(+), 287 deletions(-) diff --git a/MMCore/Configuration.cpp b/MMCore/Configuration.cpp index b40a4f41d..87ae0b1ee 100644 --- a/MMCore/Configuration.cpp +++ b/MMCore/Configuration.cpp @@ -192,46 +192,4 @@ void Configuration::deleteSetting(const char* device, const char* prop) index_[settings_[i].getKey()] = i; } -} - - -/** - * Returns the property pair with specified index. - */ -PropertyPair PropertyBlock::getPair(size_t index) const throw (CMMError) -{ - std::map::const_iterator it = pairs_.begin(); - if (index >= pairs_.size()) - { - std::ostringstream errTxt; - errTxt << (unsigned int)index << " - invalid property pair index"; - throw CMMError(errTxt.str().c_str(), MMERR_DEVICE_GENERIC); - } - - for (size_t i=0; isecond; -} - -/** - * Adds a new pair to the current contents. - */ -void PropertyBlock::addPair(const PropertyPair& pair) -{ - pairs_[pair.getPropertyName()] = pair; -} - -/** - * Get value of the specified key (property). - */ -std::string PropertyBlock::getValue(const char* key) const throw (CMMError) -{ - std::map::const_iterator it; - it = pairs_.find(key); - if (it != pairs_.end()) - return it->second.getPropertyValue(); - - std::ostringstream errTxt; - errTxt << key << " - invalid property name"; - throw CMMError(errTxt.str().c_str(), MMERR_DEVICE_GENERIC); -} - +} \ No newline at end of file diff --git a/MMCore/Configuration.h b/MMCore/Configuration.h index 51d2727d3..133321259 100644 --- a/MMCore/Configuration.h +++ b/MMCore/Configuration.h @@ -86,36 +86,6 @@ struct PropertySetting bool readOnly_; }; -/** - * Property pair defined as doublet: - * property - value. - */ -struct PropertyPair -{ - /** - * Constructor for the struct specifying the entire contents. - * @param prop - * @param value - */ - PropertyPair(const char* prop, const char* value) : - propertyName_(prop), value_(value) {} - - PropertyPair() {} - ~PropertyPair() {} - /** - * Returns the property name. - */ - std::string getPropertyName() const {return propertyName_;} - /** - * Returns the property value. - */ - std::string getPropertyValue() const {return value_;} - -private: - std::string propertyName_; - std::string value_; -}; - /** * Encapsulation of the configuration information. Designed to be wrapped * by SWIG. A collection of configuration settings. @@ -151,27 +121,4 @@ class Configuration std::map index_; }; -/** - * Encapsulation of the property collection. Designed to be wrapped - * by SWIG. A collection of property pairs. - */ -class PropertyBlock -{ -public: - - PropertyBlock() {} - ~PropertyBlock() {} - - void addPair(const PropertyPair& pair); - PropertyPair getPair(size_t index) const throw (CMMError); - /** - * Returns the number of contained property parts. - */ - size_t size() const {return pairs_.size();} - std::string getValue(const char* key) const throw (CMMError); - -private: - std::map pairs_; -}; - #endif //_CONFIGURATION_H_ diff --git a/MMCore/ErrorCodes.h b/MMCore/ErrorCodes.h index 9e54a7aeb..b63a2e918 100644 --- a/MMCore/ErrorCodes.h +++ b/MMCore/ErrorCodes.h @@ -48,7 +48,7 @@ #define MMERR_NoConfiguration 20 #define MMERR_InvalidConfigurationIndex 21 #define MMERR_DEVICE_GENERIC 22 -#define MMERR_InvalidPropertyBlock 23 +#define MMERR_InvalidPropertyBlock 23 // No longer used #define MMERR_UnhandledException 24 #define MMERR_DevicePollingTimeout 25 #define MMERR_InvalidShutterDevice 26 diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 2c6197b02..b904306f0 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -791,9 +791,6 @@ void CMMCore::unloadAllDevices() throw (CMMError) LOG_INFO(coreLogger_) << "Did unload all devices"; properties_->Refresh(); - - // TODO - // clear equipment definitions ??? } catch (CMMError& err) { logError("MMCore::unloadAllDevices", err.getMsg().c_str()); @@ -802,7 +799,7 @@ void CMMCore::unloadAllDevices() throw (CMMError) } /** - * Unloads all devices from the core, clears all configuration data and property blocks. + * Unloads all devices from the core, clears all configuration data. */ void CMMCore::reset() throw (CMMError) { @@ -829,12 +826,6 @@ void CMMCore::reset() throw (CMMError) // unload devices unloadAllDevices(); - // clear property blocks - CPropBlockMap::const_iterator i; - for (i = propBlocks_.begin(); i != propBlocks_.end(); i++) - delete i->second; - propBlocks_.clear(); - properties_->Refresh(); LOG_INFO(coreLogger_) << "System reset"; @@ -5603,136 +5594,6 @@ bool CMMCore::isGroupDefined(const char* groupName) return configGroups_->isDefined(groupName); } -/** - * Defines a reference for the collection of property-value pairs. - * This construct is useful for defining - * interchangeable equipment features, such as objective magnifications, filter wavelengths, etc. - * - * @deprecated Property blocks will not be supported in the future. - */ -void CMMCore::definePropertyBlock(const char* blockName, const char* propertyName, const char* propertyValue) -{ - CheckPropertyBlockName(blockName); - CheckPropertyName(propertyName); - CheckPropertyValue(propertyValue); - - // check if the block already exists - CPropBlockMap::const_iterator it = propBlocks_.find(blockName); - PropertyBlock* pBlock; - if (it == propBlocks_.end()) - { - pBlock = new PropertyBlock(); - propBlocks_[blockName] = pBlock; // add new block - } - else - pBlock = it->second; - - // add the pair - PropertyPair pair(propertyName, propertyValue); - pBlock->addPair(pair); - - LOG_DEBUG(coreLogger_) << "Property block " << blockName << - ": added setting " << propertyName << " = " << propertyValue; -} - -/** - * Returns all defined property block identifiers. - * - * @deprecated Property blocks will not be supported in the future. - */ -std::vector CMMCore::getAvailablePropertyBlocks() const -{ - vector blkList; - CPropBlockMap::const_iterator it = propBlocks_.begin(); - while(it != propBlocks_.end()) - blkList.push_back(it++->first); - - return blkList; -} - -/** - * Returns the collection of property-value pairs defined in this block. - * - * @deprecated Property blocks will not be supported in the future. - */ -PropertyBlock CMMCore::getPropertyBlockData(const char* blockName) -{ - CheckPropertyBlockName(blockName); - - CPropBlockMap::const_iterator it = propBlocks_.find(blockName); - if (it == propBlocks_.end()) - { - logError(blockName, getCoreErrorText(MMERR_InvalidPropertyBlock).c_str()); - throw CMMError(ToQuotedString(blockName) + ": " + getCoreErrorText(MMERR_InvalidPropertyBlock), - MMERR_InvalidPropertyBlock); - } - return *it->second; -} - -/** - * Returns the collection of property-value pairs defined for the specific device and state label. - * - * @deprecated Property blocks will not be supported in the future. - */ -PropertyBlock CMMCore::getStateLabelData(const char* deviceLabel, const char* stateLabel) -{ - std::shared_ptr pStateDev = - deviceManager_->GetDeviceOfType(deviceLabel); - CheckStateLabel(stateLabel); - - mm::DeviceModuleLockGuard guard(pStateDev); - - // check if corresponding label exists - long pos; - int nRet = pStateDev->GetLabelPosition(stateLabel, pos); - if (nRet != DEVICE_OK) - { - logError(deviceLabel, getDeviceErrorText(nRet, pStateDev).c_str()); - throw CMMError(getDeviceErrorText(nRet, pStateDev)); - } - - PropertyBlock blk; - try { - blk = getPropertyBlockData(stateLabel); - } catch (...) { - ; - // if getting data did not succeed for any reason we will assume - // that there is no connection between state label and property block. - // In this context it is not an error, we'll just say there is no data. - } - return blk; -} - -/** - * Returns the collection of property-value pairs defined for the current state. - * - * @deprecated Property blocks will not be supported in the future. - */ -PropertyBlock CMMCore::getData(const char* deviceLabel) -{ - std::shared_ptr pStateDev = - deviceManager_->GetDeviceOfType(deviceLabel); - - // here we could have written simply: - // return getStateLabelData(deviceLabel, getStateLabel(deviceLabel).c_str()); - // but that would be inefficient because of the multiple index lookup, so we'll - // do it explicitly: - - mm::DeviceModuleLockGuard guard(pStateDev); - - // obtain the current state label - std::string pos = pStateDev->GetPositionLabel(); - - PropertyBlock blk; - try { - blk = getPropertyBlockData(pos.c_str()); - } catch (...) { - ; - // not an error here - there is just no data for this entry. - } - return blk; -} - /** * Sets all com port properties in a single call */ @@ -6483,7 +6344,7 @@ void CMMCore::loadSystemState(const char* fileName) throw (CMMError) /** * Saves the current system configuration to a text file of the MM specific format. * The configuration file records only the information essential to the hardware - * setup: devices, labels, equipment pre-initialization properties, and configurations. + * setup: devices, labels, pre-initialization properties, and configurations. * The file format is the same as for the system state. */ void CMMCore::saveSystemConfiguration(const char* fileName) throw (CMMError) @@ -6516,20 +6377,6 @@ void CMMCore::saveSystemConfiguration(const char* fileName) throw (CMMError) os << MM::g_CFGCommand_Device << "," << *it << "," << pDev->GetAdapterModule()->GetName() << "," << pDev->GetName() << endl; } - // save equipment - os << "# Equipment attributes" << endl; - vector propBlocks = getAvailablePropertyBlocks(); - for (size_t i=0; i getAvailablePropertyBlocks() const); - MMCORE_DEPRECATED(PropertyBlock getPropertyBlockData(const char* blockName)); - ///@} - /** \name Image acquisition. */ ///@{ void setROI(int x, int y, int xSize, int ySize) throw (CMMError); @@ -462,9 +453,6 @@ class CMMCore throw (CMMError); long getStateFromLabel(const char* stateDeviceLabel, const char* stateLabel) throw (CMMError); - MMCORE_DEPRECATED(PropertyBlock getStateLabelData(const char* stateDeviceLabel, - const char* stateLabel)); - MMCORE_DEPRECATED(PropertyBlock getData(const char* stateDeviceLabel)); ///@} /** \name Focus (Z) stage control. */ @@ -639,8 +627,6 @@ class CMMCore CMMCore(const CMMCore&); CMMCore& operator=(const CMMCore&); - typedef std::map CPropBlockMap; - private: // LogManager should be the first data member, so that it is available for // as long as possible during construction and (especially) destruction. @@ -674,7 +660,6 @@ class CMMCore std::shared_ptr pluginManager_; std::shared_ptr deviceManager_; std::map errorText_; - CPropBlockMap propBlocks_; // Must be unlocked when calling MMEventCallback or calling device methods // or acquiring a module lock @@ -695,7 +680,6 @@ class CMMCore static void CheckStateLabel(const char* stateLabel) throw (CMMError); static void CheckConfigGroupName(const char* groupName) throw (CMMError); static void CheckConfigPresetName(const char* presetName) throw (CMMError); - static void CheckPropertyBlockName(const char* blockName) throw (CMMError); bool IsCoreDeviceLabel(const char* label) const throw (CMMError); void applyConfiguration(const Configuration& config) throw (CMMError); From 167fe5a007dd1ebaf3db647db8efe8c956457b23 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 31 Oct 2023 17:56:30 -0500 Subject: [PATCH 061/141] MMCore: Remove deprecated uid/host/MAC functions This also simplifies some OS-specific dependencies. --- MMCore/AppleHost.h | 204 ------------------------------- MMCore/Host.cpp | 223 ---------------------------------- MMCore/Host.h | 41 ------- MMCore/MMCore.cpp | 82 ------------- MMCore/MMCore.h | 7 -- MMCore/MMCore.vcxproj | 6 +- MMCore/MMCore.vcxproj.filters | 6 - MMCore/Makefile.am | 5 - MMCoreJ_wrap/Makefile.am | 1 - 9 files changed, 1 insertion(+), 574 deletions(-) delete mode 100644 MMCore/AppleHost.h delete mode 100644 MMCore/Host.cpp delete mode 100644 MMCore/Host.h diff --git a/MMCore/AppleHost.h b/MMCore/AppleHost.h deleted file mode 100644 index 9511e54d1..000000000 --- a/MMCore/AppleHost.h +++ /dev/null @@ -1,204 +0,0 @@ -//#ifdef APPLEHOSTIMPL - -/* - File: GetPrimaryMACAddress.c - - Description: This sample application demonstrates how to do retrieve the Ethernet MAC - address of the built-in Ethernet interface from the I/O Registry on Mac OS X. - Techniques shown include finding the primary (built-in) Ethernet interface, - finding the parent Ethernet controller, and retrieving properties from the - controller's I/O Registry entry. - - Copyright: © Copyright 2001-2005 Apple Computer, Inc. All rights reserved. - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. - ("Apple") in consideration of your agreement to the following terms, and your - use, installation, modification or redistribution of this Apple software - constitutes acceptance of these terms. If you do not agree with these terms, - please do not use, install, modify or redistribute this Apple software. - - In consideration of your agreement to abide by the following terms, and subject - to these terms, Apple grants you a personal, non-exclusive license, under Apple’s - copyrights in this original Apple software (the "Apple Software"), to use, - reproduce, modify and redistribute the Apple Software, with or without - modifications, in source and/or binary forms; provided that if you redistribute - the Apple Software in its entirety and without modifications, you must retain - this notice and the following text and disclaimers in all such redistributions of - the Apple Software. Neither the name, trademarks, service marks or logos of - Apple Computer, Inc. may be used to endorse or promote products derived from the - Apple Software without specific prior written permission from Apple. Except as - expressly stated in this notice, no other rights or licenses, express or implied, - are granted by Apple herein, including but not limited to any patent rights that - may be infringed by your derivative works or by other works in which the Apple - Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO - WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED - WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN - COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION - OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT - (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Change History (most recent first): - - <3> 09/15/05 Updated to produce a universal binary. Use kIOMasterPortDefault - instead of older IOMasterPort function. Print the MAC address - to stdout in response to . - <2> 04/30/02 Fix bug in creating the matching dictionary that caused the - kIOPrimaryInterface property to be ignored. Clean up comments and add - additional comments about how IOServiceGetMatchingServices operates. - <1> 06/07/01 New sample. - -*/ - -#include - -#include - -#include -#include -#include -#include - -static kern_return_t FindEthernetInterfaces(io_iterator_t *matchingServices); -static kern_return_t GetMACAddress(io_iterator_t intfIterator, UInt8 *MACAddress, UInt8 bufferSize); - -// Returns an iterator containing the primary (built-in) Ethernet interface. The caller is responsible for -// releasing the iterator after the caller is done with it. -static kern_return_t FindEthernetInterfaces(io_iterator_t *matchingServices) -{ - kern_return_t kernResult; - CFMutableDictionaryRef matchingDict; - CFMutableDictionaryRef propertyMatchDict; - - // Ethernet interfaces are instances of class kIOEthernetInterfaceClass. - // IOServiceMatching is a convenience function to create a dictionary with the key kIOProviderClassKey and - // the specified value. - matchingDict = IOServiceMatching(kIOEthernetInterfaceClass); - - // Note that another option here would be: - // matchingDict = IOBSDMatching("en0"); - - if (NULL == matchingDict) { - printf("IOServiceMatching returned a NULL dictionary.\n"); - } - else { - // Each IONetworkInterface object has a Boolean property with the key kIOPrimaryInterface. Only the - // primary (built-in) interface has this property set to TRUE. - - // IOServiceGetMatchingServices uses the default matching criteria defined by IOService. This considers - // only the following properties plus any family-specific matching in this order of precedence - // (see IOService::passiveMatch): - // - // kIOProviderClassKey (IOServiceMatching) - // kIONameMatchKey (IOServiceNameMatching) - // kIOPropertyMatchKey - // kIOPathMatchKey - // kIOMatchedServiceCountKey - // family-specific matching - // kIOBSDNameKey (IOBSDNameMatching) - // kIOLocationMatchKey - - // The IONetworkingFamily does not define any family-specific matching. This means that in - // order to have IOServiceGetMatchingServices consider the kIOPrimaryInterface property, we must - // add that property to a separate dictionary and then add that to our matching dictionary - // specifying kIOPropertyMatchKey. - - propertyMatchDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - - if (NULL == propertyMatchDict) { - printf("CFDictionaryCreateMutable returned a NULL dictionary.\n"); - } - else { - // Set the value in the dictionary of the property with the given key, or add the key - // to the dictionary if it doesn't exist. This call retains the value object passed in. - CFDictionarySetValue(propertyMatchDict, CFSTR(kIOPrimaryInterface), kCFBooleanTrue); - - // Now add the dictionary containing the matching value for kIOPrimaryInterface to our main - // matching dictionary. This call will retain propertyMatchDict, so we can release our reference - // on propertyMatchDict after adding it to matchingDict. - CFDictionarySetValue(matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict); - CFRelease(propertyMatchDict); - } - } - - // IOServiceGetMatchingServices retains the returned iterator, so release the iterator when we're done with it. - // IOServiceGetMatchingServices also consumes a reference on the matching dictionary so we don't need to release - // the dictionary explicitly. - kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, matchingServices); - if (KERN_SUCCESS != kernResult) { - printf("IOServiceGetMatchingServices returned 0x%08x\n", kernResult); - } - - return kernResult; -} - -// Given an iterator across a set of Ethernet interfaces, return the MAC address of the last one. -// If no interfaces are found the MAC address is set to an empty string. -// In this sample the iterator should contain just the primary interface. -static kern_return_t GetMACAddress(io_iterator_t intfIterator, UInt8 *MACAddress, UInt8 bufferSize) -{ - kern_return_t kernResult = KERN_FAILURE; - - // Make sure the caller provided enough buffer space. Protect against buffer overflow problems. - if (bufferSize < kIOEthernetAddressSize) { - return kernResult; - } - - // Initialize the returned address - bzero(MACAddress, bufferSize); - - // IOIteratorNext retains the returned object, so release it when we're done with it. - while (io_object_t intfService = IOIteratorNext(intfIterator)) - { - io_object_t controllerService; - CFTypeRef MACAddressAsCFData; - - // IONetworkControllers can't be found directly by the IOServiceGetMatchingServices call, - // since they are hardware nubs and do not participate in driver matching. In other words, - // registerService() is never called on them. So we've found the IONetworkInterface and will - // get its parent controller by asking for it specifically. - - // IORegistryEntryGetParentEntry retains the returned object, so release it when we're done with it. - kernResult = IORegistryEntryGetParentEntry(intfService, - kIOServicePlane, - &controllerService); - - if (KERN_SUCCESS != kernResult) { - printf("IORegistryEntryGetParentEntry returned 0x%08x\n", kernResult); - } - else { - // Retrieve the MAC address property from the I/O Registry in the form of a CFData - MACAddressAsCFData = IORegistryEntryCreateCFProperty(controllerService, - CFSTR(kIOMACAddress), - kCFAllocatorDefault, - 0); - if (MACAddressAsCFData) { - - // Get the raw bytes of the MAC address from the CFData - CFDataGetBytes((const __CFData*)MACAddressAsCFData, CFRangeMake(0, kIOEthernetAddressSize), MACAddress); - CFRelease(MACAddressAsCFData); - } - - // Done with the parent Ethernet controller object so we release it. - (void) IOObjectRelease(controllerService); - } - - // Done with the Ethernet interface object so we release it. - (void) IOObjectRelease(intfService); - } - - return kernResult; -} - -//#endif // APPLEHOSTIMPL - diff --git a/MMCore/Host.cpp b/MMCore/Host.cpp deleted file mode 100644 index 506fd6293..000000000 --- a/MMCore/Host.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// FILE: Host.cpp -// PROJECT: Micro-Manager -// SUBSYSTEM: MMCore -//----------------------------------------------------------------------------- -// DESCRIPTION: Multi-platform implementation of some simple network facilities -// -// COPYRIGHT: University of California, San Francisco, 2011, -// -// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. -// License text is included with the source distribution. -// -// This file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -// -// AUTHOR: Karl Hoover karl.hoover@gmail.com 2011 - -#include "Host.h" - -#ifdef _WINDOWS -#include -#include "Iphlpapi.h" -#include -#endif //_WINDOWS - -#ifdef __APPLE__ - -#include "AppleHost.h" - -#endif //__APPLE__ - -#ifdef __linux__ -#include -#include -#include -#include -#include -#include -#include -#include -#endif // __linux__ - - -Host::Host(void) -{ -} - -Host::~Host(void) -{ -} - - - -// a function to call into the OS stuff - -std::vector Host::getMACAddresses(long& status) -{ - // so far no problem... - status = 0; - std::vector retval; - -#ifdef _WINDOWS - - // Get the buffer length required for IP_ADAPTER_INFO. - ULONG BufferLength = 0; - BYTE* pBuffer = 0; - long osstatus; - osstatus = GetAdaptersInfo( 0, &BufferLength ); - if( ERROR_BUFFER_OVERFLOW == osstatus) - { - // Now the BufferLength contain the required buffer length. - // Allocate necessary buffer. - pBuffer = new BYTE[ BufferLength ]; - } - else - { - status = (0==osstatus?-1:osstatus); - } - - if( 0 == status) - { - // Get the Adapter Information. - PIP_ADAPTER_INFO pAdapterInfo = reinterpret_cast(pBuffer); - GetAdaptersInfo( pAdapterInfo, &BufferLength ); - - // Iterate the network adapters and print their MAC address. - while( pAdapterInfo ) - { - MACValue v; // it's really a long long - // Assuming pAdapterInfo->AddressLength is 6. - memcpy(&v, pAdapterInfo->Address, 6); - retval.push_back(v); - - // Get next adapter info. - pAdapterInfo = pAdapterInfo->Next; - } - - // deallocate the buffer. - delete[] pBuffer; - } - -#endif //_WINDOWS - - -#ifdef __APPLE__ - - io_iterator_t intfIterator; - UInt8 MACAddress[kIOEthernetAddressSize]; - - int kernResult = FindEthernetInterfaces(&intfIterator); - - if (KERN_SUCCESS != kernResult) { - status = (long)(0==kernResult?-1:kernResult); - } - else { - kernResult = GetMACAddress(intfIterator, MACAddress, sizeof(MACAddress)); - - if (KERN_SUCCESS != kernResult) { - status = (long)(0==kernResult?-1:kernResult); - } - else { - MACValue v; - memcpy(&v, MACAddress, sizeof(v)); - - - retval.push_back(v); - } - } - - (void) IOObjectRelease(intfIterator); // Release the iterator. - //#endif //APPLEHOSTIMPL - - - -#endif // __APPLE__ - -#ifdef __linux__ - - int sock; - sock = socket(PF_INET, SOCK_DGRAM, 0); - if ( -1 == sock ) { - status = static_cast(errno); - } - else { - char buf[2048]; - ifconf ifc; - ifc.ifc_buf = buf; - ifc.ifc_len = sizeof(buf); - int osstatus = ioctl(sock, SIOCGIFCONF, &ifc); - if ( -1 == osstatus ) { - status = static_cast(errno); - } - else { - MACValue macaddr; - for ( ifreq *ifr(ifc.ifc_req), *ifrEnd(ifc.ifc_req + ifc.ifc_len / sizeof(ifreq)); - ifr != ifrEnd; - ++ifr ) - { - osstatus = ioctl(sock, SIOCGIFHWADDR, ifr); - if ( osstatus == 0 ) { - macaddr = 0; - memcpy(&macaddr, ifr->ifr_hwaddr.sa_data, 6); - if(macaddr != 0) { - retval.push_back(macaddr); - } - } - } - // Raise error flag if no mac address could be found for *any* interface. The loopback and some tunnel - // interfaces, for example, may have an IP address but only one peer and therefore no need for a media layer - // address. So, some mac addr retrieval failures and null mac addrs are to be expected. - if ( retval.empty() ) { - status = static_cast(errno); - } - } - close(sock); - } - - -#endif // __linux__ - - - - return retval; -} - - - -std::vector Host::MACAddresses(long& status) -{ - - std::vector retval; - - std::vector values = getMACAddresses(status); - - if( 0 == status) - { - - std::vector::iterator j; - for( j = values.begin(); j!=values.end(); ++j) - { - unsigned char ctmp[6]; - memcpy( ctmp, &(*j), 6); - - char address[19]; - snprintf(address, 19, "%02x-%02x-%02x-%02x-%02x-%02x", - ctmp[0], - ctmp[1], - ctmp[2], - ctmp[3], - ctmp[4], - ctmp[5]); - retval.push_back(std::string(address)); - } - } - - return retval; - -} diff --git a/MMCore/Host.h b/MMCore/Host.h deleted file mode 100644 index 54e8dc5b2..000000000 --- a/MMCore/Host.h +++ /dev/null @@ -1,41 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// FILE: Host.cpp -// PROJECT: Micro-Manager -// SUBSYSTEM: MMCore -//----------------------------------------------------------------------------- -// DESCRIPTION: Multi-platform declaration of some simple network facilities -// -// COPYRIGHT: University of California, San Francisco, 2011, -// -// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. -// License text is included with the source distribution. -// -// This file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -// -// AUTHOR: Karl Hoover karl.hoover@gmail.com 2011 -#ifndef HOST_H -#define HOST_H -#include -#include - -typedef long long MACValue; - - -class Host -{ -public: - Host(void); - ~Host(void); - std::vector MACAddresses(long& status); - std::vector getMACAddresses(long& status); - - -}; - -#endif \ No newline at end of file diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index b904306f0..68db7ef51 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -50,7 +50,6 @@ #include "CoreUtils.h" #include "DeviceManager.h" #include "Devices/DeviceInstances.h" -#include "Host.h" #include "LogManager.h" #include "MMCore.h" #include "MMEventCallback.h" @@ -65,17 +64,6 @@ #include #include - -#ifndef _WINDOWS -// Needed on Unix for getcwd() and gethostname() -#include -#include -#include -#else -// for _getcwd -#include -#endif - using namespace std; /* @@ -309,40 +297,6 @@ void CMMCore::stopSecondaryLogFile(int handle) throw (CMMError) logManager_->RemoveSecondaryLogFile(h); } - -/*! - Displays current user name. - */ -string CMMCore::getUserId() const -{ - char buf[8192]; -#ifndef _WINDOWS - struct passwd* ppw = getpwuid(geteuid()); - strcpy( buf, ppw->pw_name); -#else - DWORD bufCharCount = 8192; - if( !GetUserName( buf, &bufCharCount ) ) - buf[0] = 0; -#endif - return string(buf); -} - -/** - * return current computer name. - */ -string CMMCore::getHostName() const -{ - char buf[8192]; -#ifndef _WINDOWS - gethostname(buf, 8192); -#else - DWORD bufCharCount = 8192; - if( !GetComputerName( buf, &bufCharCount ) ) - buf[0] = 0; -#endif - return string(buf); -} - /** * Displays core version. */ @@ -7575,39 +7529,3 @@ std::string CMMCore::getInstalledDeviceDescription(const char* hubLabel, const c } return description.empty() ? "N/A" : description; } - -// at least on OS X, there is a 'primary' MAC address, so we'll -// assume that is the first one. -/** -* Retrieve vector of MAC addresses for the Ethernet cards in the current computer -* formatted xx-xx-xx-xx-xx-xx -* -*/ -std::vector CMMCore::getMACAddresses(void) -{ - std::vector retv; - try - { - - Host* pHost = new Host(); - if(NULL != pHost) - { - long status; - retv = pHost->MACAddresses(status); - - if( 0 != status) - { - LOG_ERROR(coreLogger_) << "Error " << status << - " while getting MAC address"; - } - delete pHost; - } - } - catch(...) - { - - } - return retv; -} - - diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 07fae9a97..aa355a9b8 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -615,13 +615,6 @@ class CMMCore std::vector getLoadedPeripheralDevices(const char* hubLabel) throw (CMMError); ///@} - /** \name Miscellaneous. */ - ///@{ - MMCORE_DEPRECATED(std::string getUserId() const); - MMCORE_DEPRECATED(std::string getHostName() const); - MMCORE_DEPRECATED(std::vector getMACAddresses(void)); - ///@} - private: // make object non-copyable CMMCore(const CMMCore&); diff --git a/MMCore/MMCore.vcxproj b/MMCore/MMCore.vcxproj index 0b2428216..e639480d5 100644 --- a/MMCore/MMCore.vcxproj +++ b/MMCore/MMCore.vcxproj @@ -59,7 +59,6 @@ $(OutDir)MMCore.lib - Iphlpapi.lib @@ -75,7 +74,6 @@ $(OutDir)MMCore.lib - Iphlpapi.lib @@ -100,7 +98,6 @@ - @@ -143,7 +140,6 @@ - @@ -185,4 +181,4 @@ - \ No newline at end of file + diff --git a/MMCore/MMCore.vcxproj.filters b/MMCore/MMCore.vcxproj.filters index a01ede1fd..d3009c657 100644 --- a/MMCore/MMCore.vcxproj.filters +++ b/MMCore/MMCore.vcxproj.filters @@ -45,9 +45,6 @@ Source Files - - Source Files - Source Files @@ -167,9 +164,6 @@ Header Files - - Header Files - Header Files diff --git a/MMCore/Makefile.am b/MMCore/Makefile.am index f51e065ce..1a2975a6d 100644 --- a/MMCore/Makefile.am +++ b/MMCore/Makefile.am @@ -1,7 +1,5 @@ AUTOMAKE_OPTIONS = foreign subdir-objects -AM_LDFLAGS = $(MMCORE_APPLEHOST_LDFLAGS) - noinst_LTLIBRARIES = libMMCore.la libMMCore_la_LIBADD = ../MMDevice/libMMDevice.la @@ -10,7 +8,6 @@ libMMCore_la_SOURCES = \ ../MMDevice/MMDevice.h \ ../MMDevice/MMDeviceConstants.h \ ../MMDevice/ModuleInterface.h \ - AppleHost.h \ CircularBuffer.cpp \ CircularBuffer.h \ ConfigGroup.h \ @@ -59,8 +56,6 @@ libMMCore_la_SOURCES = \ ErrorCodes.h \ FrameBuffer.cpp \ FrameBuffer.h \ - Host.cpp \ - Host.h \ LibraryInfo/LibraryPaths.h \ LibraryInfo/LibraryPathsUnix.cpp \ LoadableModules/LoadedDeviceAdapter.cpp \ diff --git a/MMCoreJ_wrap/Makefile.am b/MMCoreJ_wrap/Makefile.am index 7b2100005..6e8d61d6d 100644 --- a/MMCoreJ_wrap/Makefile.am +++ b/MMCoreJ_wrap/Makefile.am @@ -27,7 +27,6 @@ swig_sources = MMCoreJ.i \ ../MMCore/CoreUtils.h \ ../MMCore/Error.h \ ../MMCore/ErrorCodes.h \ - ../MMCore/Host.h \ ../MMCore/MMCore.h \ ../MMCore/MMEventCallback.h \ ../MMCore/PluginManager.h \ From 5c5053666cfd20f821568206c696a3a6d87d89a1 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 31 Oct 2023 18:08:31 -0500 Subject: [PATCH 062/141] MMCore: Increment major version to 11 For removal of deprecated CMMCore functions and config file commands. --- MMCore/MMCore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 68db7ef51..a04aaa6ef 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -101,7 +101,7 @@ using namespace std; * (Keep the 3 numbers on one line to make it easier to look at diffs when * merging/rebasing.) */ -const int MMCore_versionMajor = 10, MMCore_versionMinor = 7, MMCore_versionPatch = 0; +const int MMCore_versionMajor = 11, MMCore_versionMinor = 0, MMCore_versionPatch = 0; /////////////////////////////////////////////////////////////////////////////// From c419111eeba3402ada5d29af88638104dd94c857 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 23:41:12 +0000 Subject: [PATCH 063/141] Update secret-device-adapters-commit to latest --- secret-device-adapters-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret-device-adapters-commit b/secret-device-adapters-commit index 2fafea43e..b48fc70eb 100644 --- a/secret-device-adapters-commit +++ b/secret-device-adapters-commit @@ -1 +1 @@ -7ce89b733a655b9901340247190e3cbc8014e4e9 +f5e40313f919bd58bd53e587d1790b6508933a79 From 5361dc5ec6eb93f82b9e748e1e583db6101dc2ef Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 2 Nov 2023 18:17:35 -0500 Subject: [PATCH 064/141] Update MMCoreJ version to match MMCore --- MMCoreJ_wrap/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMCoreJ_wrap/pom.xml b/MMCoreJ_wrap/pom.xml index 8923fec73..a7f089a36 100644 --- a/MMCoreJ_wrap/pom.xml +++ b/MMCoreJ_wrap/pom.xml @@ -3,7 +3,7 @@ org.micro-manager.mmcorej MMCoreJ jar - 10.7.0 + 11.0.0 Micro-Manager Java Interface to MMCore Micro-Manager is open source software for control of automated/motorized microscopes. This specific packages provides the Java interface to the device abstractino layer (MMCore) that is written in C++ with a C-interface http://micro-manager.org From aa6d2b7b9a89e982cb14d73ef604dfc6765d9a05 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 2 Nov 2023 18:08:50 -0500 Subject: [PATCH 065/141] MMCore: Add enableFeature() and isFeatureEnabled() Also add the feature "StrictInitializationChecks". --- MMCore/CoreFeatures.cpp | 140 ++++++++++++++++++++++++++++++ MMCore/CoreFeatures.h | 44 ++++++++++ MMCore/Devices/DeviceInstance.cpp | 27 +++--- MMCore/MMCore.cpp | 53 ++++++++++- MMCore/MMCore.h | 6 ++ MMCore/MMCore.vcxproj | 2 + MMCore/MMCore.vcxproj.filters | 6 ++ MMCore/Makefile.am | 2 + MMCoreJ_wrap/pom.xml | 2 +- 9 files changed, 270 insertions(+), 12 deletions(-) create mode 100644 MMCore/CoreFeatures.cpp create mode 100644 MMCore/CoreFeatures.h diff --git a/MMCore/CoreFeatures.cpp b/MMCore/CoreFeatures.cpp new file mode 100644 index 000000000..f05beb0ab --- /dev/null +++ b/MMCore/CoreFeatures.cpp @@ -0,0 +1,140 @@ +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +// +// COPYRIGHT: 2023, Board of Regents of the University of Wisconsin System +// All Rights reserved +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Mark Tsuchida + +#include "CoreFeatures.h" + +#include "Error.h" + +#include +#include +#include + +// Core Features (inspired by chrome://flags) +// +// Core features are named boolean flags that control API behavior. They can be +// used for these purposes: +// - Providing a migration path for API changes, especially related to error +// handling (stricter checks or reporting previously ignored errors). +// - Providing a way to develop new features (especially complex ones) without +// forking or branching MMCore. The new feature can be disabled by default +// until its API is well-vetted and stable, preventing casual users from +// accidentally using the experimental feature without realizing. Trying to +// access the new feature without enabling it would generally result in an +// exception. +// - Possibly other purposes, provided that care is taken to ensure that +// switching the feature does _not_ result in effectively two mutually +// incompatible variants of MMCore. +// +// Importantly, feature switches are a migration strategy, _not_ a permanent +// configuration mechanism. Every feature must have the property: one or the +// other setting of the feature allows old and new user code to run. (When used +// for the introduction of better error checking, disabling the feature should +// not break user code that is compatible with the new error checking. When +// used for the introduction of new functionality, enabling the feature should +// not break compatibility with existing user code.) +// +// Typically, switching features is done manually and locally (for testing new +// behavior), or is done once during initialization by the application/library +// providing the overall environment (such as MMStudio or its analogue). +// +// How to add a new feature: +// - Add a bool flag to struct mm::features::Flags (in the .h file), with its +// default value (usually false for a brand-new feature) +// - Add the feature name and getter/setter lambdas in the map inside +// featureMap() (below) +// - Document the feature in the Doxygen comment for CMMCore::enableFeature() +// (internal notes about the feature that are not useful to the user should +// be documented in featureMap()) +// - In Core code, query the feature state with: mm::features::flags().name +// +// Lifecycle of a feature: +// - Features should generally be disabled by default when first added. When +// the feature represents experimental functionality, breaking changes can be +// made to the new functionality while it remains in this stage. +// - When the feature is ready for widespread use, it should be enabled by +// default. If this causes a backward-incompatible change in default Core API +// behavior, this change requires MMCore's major version to be incremented; +// disabling the feature should usually be deprecated. If it causes new +// functions to be available by default, MMCore's minor version should be +// incremented; disabling the feature may be forbidden. +// - When the old behavior (i.e., feature disabled) is no longer needed, the +// feature should be permanently enabled: the getter should then always +// return true, the setter should throw an exception, and the corresponding +// flag in mm::features::Flags should be removed. However, the feature name +// should never be removed. +// - There may be cases where a feature is abandoned before becoming enabled by +// default. In this case, it should be permanently disabled. + +namespace mm { +namespace features { + +namespace internal { + +Flags g_flags{}; + +} + +namespace { + +const auto& featureMap() { + // Here we define the mapping from feature names to what they do. + // Use functions (lambdas) to get/set the flags, so that we have the + // possibility of having feature names that enable sets of features. + using GetFunc = bool(*)(); + using SetFunc = void(*)(bool); + using internal::g_flags; + static const std::map> map = { + { + "StrictInitializationChecks", { + [] { return g_flags.strictInitializationChecks; }, + [](bool e) { g_flags.strictInitializationChecks = e; } + // This is made switchable to give user code (mainly the MMStudio + // Hardware Configuration Wizard) time to be fixed and tested, + // while allowing other environments to benefit from safer + // behavior. It should be enabled by default when we no longer have + // prominent users that require the old behavior, and permanently + // enabled perhaps a few years later. + } + }, + // How to add a new Core feature: see the comment at the top of this file. + // Features (the string names) must never be removed once added! + }; + return map; +} + +} + +void enableFeature(const std::string& name, bool enable) { + try { + featureMap().at(name).second(enable); + } catch (const std::out_of_range&) { + throw CMMError("No such feature: " + name); + } +} + +bool isFeatureEnabled(const std::string& name) { + try { + return featureMap().at(name).first(); + } catch (const std::out_of_range&) { + throw CMMError("No such feature: " + name); + } +} + +} // namespace features +} // namespace mm diff --git a/MMCore/CoreFeatures.h b/MMCore/CoreFeatures.h new file mode 100644 index 000000000..3cd04b19d --- /dev/null +++ b/MMCore/CoreFeatures.h @@ -0,0 +1,44 @@ +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +// +// COPYRIGHT: 2023, Board of Regents of the University of Wisconsin System +// All Rights reserved +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Mark Tsuchida + +#pragma once + +#include + +namespace mm { +namespace features { + +struct Flags { + bool strictInitializationChecks = false; + // How to add a new Core feature: see the comment in the .cpp file. +}; + +namespace internal { + +extern Flags g_flags; + +} + +inline const Flags& flags() { return internal::g_flags; } + +void enableFeature(const std::string& name, bool enable); +bool isFeatureEnabled(const std::string& name); + +} // namespace features +} // namespace mm diff --git a/MMCore/Devices/DeviceInstance.cpp b/MMCore/Devices/DeviceInstance.cpp index caa6d0e31..2b800d8cd 100644 --- a/MMCore/Devices/DeviceInstance.cpp +++ b/MMCore/Devices/DeviceInstance.cpp @@ -22,6 +22,7 @@ #include "DeviceInstance.h" #include "../../MMDevice/MMDevice.h" +#include "../CoreFeatures.h" #include "../CoreUtils.h" #include "../Error.h" #include "../LoadableModules/LoadedDeviceAdapter.h" @@ -122,16 +123,22 @@ DeviceInstance::ThrowIfError(int code, const std::string& message) const } void -DeviceInstance::RequireInitialized(const char *operation) const -{ - if (!initialized_) { - // This is an error, but existing application code (in particular, - // the Hardware Configuration Wizard) breaks if we enforce it strictly. - // Until such code is fixed, we only log. - LOG_WARNING(Logger()) << "Operation (" << operation << - ") not permitted on uninitialized device (this will be an error in a future version of MMCore; for now we continue with the operation anyway, even though it might not be safe)"; - // Eventually to be replaced with: - // ThrowError("Operation not permitted on uninitialized device"); +DeviceInstance::RequireInitialized(const char* operation) const +{ + if (!initialized_) + { + if (mm::features::flags().strictInitializationChecks) + { + std::ostringstream stream; + stream << "Operation (" << operation << + ") not permitted on uninitialized device"; + ThrowError(stream.str()); + } + else + { + LOG_WARNING(Logger()) << "Operation (" << operation << + ") not permitted on uninitialized device (this will be an error in a future version of MMCore; for now we continue with the operation anyway, even though it might not be safe)"; + } } } diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index a04aaa6ef..b458f92cf 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -46,6 +46,7 @@ #include "ConfigGroup.h" #include "Configuration.h" #include "CoreCallback.h" +#include "CoreFeatures.h" #include "CoreProperty.h" #include "CoreUtils.h" #include "DeviceManager.h" @@ -101,7 +102,7 @@ using namespace std; * (Keep the 3 numbers on one line to make it easier to look at diffs when * merging/rebasing.) */ -const int MMCore_versionMajor = 11, MMCore_versionMinor = 0, MMCore_versionPatch = 0; +const int MMCore_versionMajor = 11, MMCore_versionMinor = 1, MMCore_versionPatch = 0; /////////////////////////////////////////////////////////////////////////////// @@ -177,6 +178,56 @@ CMMCore::~CMMCore() LOG_INFO(coreLogger_) << "Core session ended"; } +/** + * Enable or disable the given Core feature. + * + * Core features control whether experimental functionality (which is subject + * to breaking changes) is exposed, or whether stricter API usage is enforced. + * + * Currently switchable features: + * - "StrictInitializationChecks" (default: disabled) When enabled, an + * exception is thrown when an operation requiring an initialized device is + * attempted on a device that is not successfully initialized. When disabled, + * no exception is thrown and a warning is logged (and the operation may + * potentially cause incorrect behavior or a crash). + * + * Permanently enabled features: + * - None so far. + * + * Permanently disabled features: + * - None so far. + * + * @param name the feature name. + * @param enable whether to enable or disable the feature. + * + * @throws CMMError if the feature name is null or unknown, or attempting to + * disable a permanently enabled feature, or attempting to enable a permanently + * disabled feature. + */ +void CMMCore::enableFeature(const char* name, bool enable) throw (CMMError) +{ + if (name == nullptr) + throw CMMError("Null feature name", MMERR_NullPointerException); + mm::features::enableFeature(name, enable); +} + +/** + * Return whether the given Core feature is currently enabled. + * + * See enableFeature() for the available features. + * + * @param name the feature name. + * @returns whether the feature is enabled. + * + * @throws CMMError if the feature name is null or unknown. + */ +bool CMMCore::isFeatureEnabled(const char* name) throw (CMMError) +{ + if (name == nullptr) + throw CMMError("Null feature name", MMERR_NullPointerException); + return mm::features::isFeatureEnabled(name); +} + /** * Set the primary Core log file. * diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index aa355a9b8..79b2af386 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -144,6 +144,12 @@ class CMMCore */ static void noop() {} + /** \name Core feature control. */ + ///@{ + static void enableFeature(const char* name, bool enable) throw (CMMError); + static bool isFeatureEnabled(const char* name) throw (CMMError); + ///@} + /** \name Initialization and setup. */ ///@{ void loadDevice(const char* label, const char* moduleName, diff --git a/MMCore/MMCore.vcxproj b/MMCore/MMCore.vcxproj index e639480d5..d5e4c2f28 100644 --- a/MMCore/MMCore.vcxproj +++ b/MMCore/MMCore.vcxproj @@ -80,6 +80,7 @@ + @@ -118,6 +119,7 @@ + diff --git a/MMCore/MMCore.vcxproj.filters b/MMCore/MMCore.vcxproj.filters index d3009c657..1920583b6 100644 --- a/MMCore/MMCore.vcxproj.filters +++ b/MMCore/MMCore.vcxproj.filters @@ -42,6 +42,9 @@ Source Files + + Source Files + Source Files @@ -152,6 +155,9 @@ Header Files + + Header Files + Header Files diff --git a/MMCore/Makefile.am b/MMCore/Makefile.am index 1a2975a6d..d56ad5273 100644 --- a/MMCore/Makefile.am +++ b/MMCore/Makefile.am @@ -15,6 +15,8 @@ libMMCore_la_SOURCES = \ Configuration.h \ CoreCallback.cpp \ CoreCallback.h \ + CoreFeatures.cpp \ + CoreFeatures.h \ CoreProperty.cpp \ CoreProperty.h \ CoreUtils.h \ diff --git a/MMCoreJ_wrap/pom.xml b/MMCoreJ_wrap/pom.xml index a7f089a36..38d1ba920 100644 --- a/MMCoreJ_wrap/pom.xml +++ b/MMCoreJ_wrap/pom.xml @@ -3,7 +3,7 @@ org.micro-manager.mmcorej MMCoreJ jar - 11.0.0 + 11.1.0 Micro-Manager Java Interface to MMCore Micro-Manager is open source software for control of automated/motorized microscopes. This specific packages provides the Java interface to the device abstractino layer (MMCore) that is written in C++ with a C-interface http://micro-manager.org From 1d95987a75f20391f110f173640a1a828c0164ab Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Fri, 10 Nov 2023 15:18:28 -0600 Subject: [PATCH 066/141] ZeissCAN: Fix x-y typo in Y_um property --- DeviceAdapters/ZeissCAN/mcu28.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DeviceAdapters/ZeissCAN/mcu28.cpp b/DeviceAdapters/ZeissCAN/mcu28.cpp index 68e87fe6f..ec6e49065 100644 --- a/DeviceAdapters/ZeissCAN/mcu28.cpp +++ b/DeviceAdapters/ZeissCAN/mcu28.cpp @@ -389,7 +389,7 @@ int XYStage::OnY(MM::PropertyBase* pProp, MM::ActionType eAct) { double yUm; pProp->Get(yUm); - SetPositionUm(GetCachedYUm(), yUm); + SetPositionUm(GetCachedXUm(), yUm); } return DEVICE_OK; From 6e34450e82cd3c4184dc58f1fcec7ef17a132f2e Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Fri, 10 Nov 2023 16:17:44 -0600 Subject: [PATCH 067/141] Make pre-init property settability conditional Before this change: Setting a pre-init property after initialization is an error. After this change: Setting a pre-init property after initialization is an error if Core feature StrictInitializationChecks is enabled. Otherwise it is a warning. Bump MMCore version to 11.1.1. --- MMCore/Devices/DeviceInstance.cpp | 16 ++++++++++++++-- MMCore/MMCore.cpp | 2 +- MMCoreJ_wrap/pom.xml | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/MMCore/Devices/DeviceInstance.cpp b/MMCore/Devices/DeviceInstance.cpp index 2b800d8cd..a36ea75b7 100644 --- a/MMCore/Devices/DeviceInstance.cpp +++ b/MMCore/Devices/DeviceInstance.cpp @@ -180,8 +180,20 @@ void DeviceInstance::SetProperty(const std::string& name, const std::string& value) const { - if (initialized_ && GetPropertyInitStatus(name.c_str())) - ThrowError("Cannot set pre-init property after initialization"); + if (initialized_ && GetPropertyInitStatus(name.c_str())) { + // Note: Some features (port scanning) may depend on setting serial port + // properties post-init. We may want to exclude SerialManager from this + // check (regardless of whether strictInitializationChecks is enabled). + if (mm::features::flags().strictInitializationChecks) + { + ThrowError("Cannot set pre-init property after initialization"); + } + else + { + LOG_WARNING(Logger()) << "Setting of pre-init property (" << name << + ") not permitted on initialized device (this will be an error in a future version of MMCore; for now we continue with the operation anyway, even though it might not be safe)"; + } + } LOG_DEBUG(Logger()) << "Will set property \"" << name << "\" to \"" << value << "\""; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index b458f92cf..e2c7107f6 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -102,7 +102,7 @@ using namespace std; * (Keep the 3 numbers on one line to make it easier to look at diffs when * merging/rebasing.) */ -const int MMCore_versionMajor = 11, MMCore_versionMinor = 1, MMCore_versionPatch = 0; +const int MMCore_versionMajor = 11, MMCore_versionMinor = 1, MMCore_versionPatch = 1; /////////////////////////////////////////////////////////////////////////////// diff --git a/MMCoreJ_wrap/pom.xml b/MMCoreJ_wrap/pom.xml index 38d1ba920..6be3fecd5 100644 --- a/MMCoreJ_wrap/pom.xml +++ b/MMCoreJ_wrap/pom.xml @@ -3,7 +3,7 @@ org.micro-manager.mmcorej MMCoreJ jar - 11.1.0 + 11.1.1 Micro-Manager Java Interface to MMCore Micro-Manager is open source software for control of automated/motorized microscopes. This specific packages provides the Java interface to the device abstractino layer (MMCore) that is written in C++ with a C-interface http://micro-manager.org From 116e3480b2cf35aca38376dedb1341bac9389f24 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Wed, 22 Nov 2023 13:13:49 -0800 Subject: [PATCH 068/141] ArduinoCounter: change type from int to long to enable sequences longer than 35000 --- .../ArduinoCounter/ArduinoCounter/ArduinoCounter.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino b/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino index 2141a03e9..40bd5201e 100644 --- a/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino +++ b/DeviceAdapters/ArduinoCounter/ArduinoCounter/ArduinoCounter.ino @@ -25,8 +25,8 @@ const int outPin = 8; const unsigned long timeOut = 1000; -unsigned int counter = 0; -unsigned int limit; +unsigned long counter = 0; +unsigned long limit; boolean counting = false; boolean inputWas; boolean invert = false; From 6a667293122965d571a4228bc7d16e5f493de89b Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 28 Nov 2023 15:24:22 -0600 Subject: [PATCH 069/141] Add Andor Dragonfly DLL to Windows install By request from Andor. --- DeviceAdapters/Dragonfly/build.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 DeviceAdapters/Dragonfly/build.xml diff --git a/DeviceAdapters/Dragonfly/build.xml b/DeviceAdapters/Dragonfly/build.xml new file mode 100644 index 000000000..6a8febb93 --- /dev/null +++ b/DeviceAdapters/Dragonfly/build.xml @@ -0,0 +1,10 @@ + + + + + + + + + + From e59a350e3bf55dc5e830376b4ebc73a2ecd7b492 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Wed, 29 Nov 2023 14:55:29 -0800 Subject: [PATCH 070/141] mvn build: Update path to Swig generated file, as it appears to have been changed. --- MMCoreJ_wrap/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMCoreJ_wrap/pom.xml b/MMCoreJ_wrap/pom.xml index 6be3fecd5..119c3ec42 100644 --- a/MMCoreJ_wrap/pom.xml +++ b/MMCoreJ_wrap/pom.xml @@ -66,7 +66,7 @@ - ../../build/intermediates/Swig + ../build/intermediates/Swig From 061cdcbde7cf3a5eda4cd5b53b71bacdb33b7938 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Wed, 29 Nov 2023 15:11:08 -0800 Subject: [PATCH 071/141] MMCoreJ build: Move TaggeImage to a more logical place so that it does not need to be copied on each and every build. --- MMCoreJ_wrap/build.xml | 4 +--- MMCoreJ_wrap/{ => src/main/java/mmcorej}/TaggedImage.java | 0 2 files changed, 1 insertion(+), 3 deletions(-) rename MMCoreJ_wrap/{ => src/main/java/mmcorej}/TaggedImage.java (100%) diff --git a/MMCoreJ_wrap/build.xml b/MMCoreJ_wrap/build.xml index d2f0b5c8b..994bb5cd6 100644 --- a/MMCoreJ_wrap/build.xml +++ b/MMCoreJ_wrap/build.xml @@ -10,12 +10,10 @@ - - + - diff --git a/MMCoreJ_wrap/TaggedImage.java b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java similarity index 100% rename from MMCoreJ_wrap/TaggedImage.java rename to MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java From d0fee249c3c512afcb31d1b2a5fcd3b7410b08f2 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Fri, 1 Dec 2023 12:08:02 -0600 Subject: [PATCH 072/141] Fix Automake build of MMCoreJ --- MMCoreJ_wrap/Makefile.am | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/MMCoreJ_wrap/Makefile.am b/MMCoreJ_wrap/Makefile.am index 6e8d61d6d..83fc9e744 100644 --- a/MMCoreJ_wrap/Makefile.am +++ b/MMCoreJ_wrap/Makefile.am @@ -61,13 +61,9 @@ libMMCoreJ_wrap_la_LIBADD = ../MMCore/libMMCore.la jar_DATA = MMCoreJ.jar -gensrc/mmcorej/TaggedImage.java: TaggedImage.java - $(MKDIR_P) gensrc/mmcorej - cp $(srcdir)/TaggedImage.java gensrc/mmcorej - # Use MMCoreJ_wrap.{h,cxx} to ensure SWIG has been run, but use the phony # target FORCE to always run Ant so that the Java source mtime is checked -MMCoreJ.jar: gensrc/mmcorej/TaggedImage.java MMCoreJ_wrap.h MMCoreJ_wrap.cxx FORCE +MMCoreJ.jar: MMCoreJ_wrap.h MMCoreJ_wrap.cxx FORCE $(ANT) -Dmm.javacflags="$(JAVACFLAGS)" $(ANTFLAGS) -Dsrcdir=gensrc jar .PHONY: FORCE From d253b2f6465363262f8db015a2a399cb1e9190ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:32:08 +0000 Subject: [PATCH 073/141] Update secret-device-adapters-commit to latest --- secret-device-adapters-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret-device-adapters-commit b/secret-device-adapters-commit index b48fc70eb..9a4653f16 100644 --- a/secret-device-adapters-commit +++ b/secret-device-adapters-commit @@ -1 +1 @@ -f5e40313f919bd58bd53e587d1790b6508933a79 +f27c3b550436b08c85ef7e0794dd1736ccd4dc06 From cae165265d0f699cd446fe67c4ddc2d1d1661f49 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 4 Dec 2023 12:32:47 -0600 Subject: [PATCH 074/141] Enable parallel build for each VS project (As opposed to, and in addition to, parallel building of projects.) --- buildscripts/VisualStudio/MMCommon.props | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildscripts/VisualStudio/MMCommon.props b/buildscripts/VisualStudio/MMCommon.props index d4a0f22dd..8c42b9a2a 100644 --- a/buildscripts/VisualStudio/MMCommon.props +++ b/buildscripts/VisualStudio/MMCommon.props @@ -26,6 +26,7 @@ 4127;4290;%(DisableSpecificWarnings) Sync _CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true true @@ -68,4 +69,4 @@ $(MM_BUILDDIR) - + \ No newline at end of file From 5602d991aae26ae6770c25462c19bbfb8b29a01e Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 6 Dec 2023 11:22:14 -0600 Subject: [PATCH 075/141] MMDevice: Add ActionLambda This is like CPropertyAction[Ex], but takes an arbitrary copyable callable (such as a lambda). Defining a property action using a lambda allows one to put all the code related to a single property in one place. --- MMDevice/Property.h | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/MMDevice/Property.h b/MMDevice/Property.h index 39e865be8..cbc669822 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -24,11 +24,13 @@ #define _MMPROPERTY_H_ #include "MMDeviceConstants.h" -#include -#include + #include -#include +#include +#include #include +#include +#include namespace MM { @@ -120,6 +122,24 @@ class ActionEx : public ActionFunctor { return (*pObj_.*fpt_)(pProp, eAct, param_);}; }; +/** + * Action implementation using std::function to wrap arbitrary callables. + * + * (It is named "lambda" after its intended use, but can wrap any C++ + * callable.) + */ +class ActionLambda final : public ActionFunctor +{ + std::function func_; + +public: + template explicit ActionLambda(F func) : func_(func) {} + + int Execute(PropertyBase* pProp, ActionType eAct) final { + return func_(pProp, eAct); + } +}; + /** * Property API with most of the Property mechanism implemented. */ From 8459a7c920f6808c52e963698bf48fff4ecdae65 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 6 Dec 2023 11:24:18 -0600 Subject: [PATCH 076/141] BH_DCC_DCU: Use ActionLambda from MMDevice --- DeviceAdapters/BH_DCC_DCU/DCCDCUDevices.h | 51 ++++++++++------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/DeviceAdapters/BH_DCC_DCU/DCCDCUDevices.h b/DeviceAdapters/BH_DCC_DCU/DCCDCUDevices.h index 99c94222a..4c9656805 100644 --- a/DeviceAdapters/BH_DCC_DCU/DCCDCUDevices.h +++ b/DeviceAdapters/BH_DCC_DCU/DCCDCUDevices.h @@ -27,18 +27,6 @@ #include #include -// This can be moved to DeviceBase.h to make available generally. -class ActionLambda final : public MM::ActionFunctor { - std::function f_; - - public: - template explicit ActionLambda(F f) : f_(f) {} - - auto Execute(MM::PropertyBase* pProp, MM::ActionType eAct) -> int { - return f_(pProp, eAct); - } -}; - template inline auto ModelName() -> std::string { if (Model == DCCOrDCU::DCC) { return "DCC"; @@ -426,8 +414,9 @@ class DCCDCUModuleDevice : public CGenericBase> { const std::string& name) { this->CreateStringProperty( name.c_str(), "Off", false, - new ActionLambda([this, connNo, feature, name]( - MM::PropertyBase* pProp, MM::ActionType eAct) { + new MM::ActionLambda([this, connNo, feature, + name](MM::PropertyBase* pProp, + MM::ActionType eAct) { if (eAct == MM::BeforeGet) { short err{}; const bool flag = @@ -467,8 +456,9 @@ class DCCDCUModuleDevice : public CGenericBase> { float maxValue) { this->CreateFloatProperty( name.c_str(), minValue, false, - new ActionLambda([this, connNo, feature, name]( - MM::PropertyBase* pProp, MM::ActionType eAct) { + new MM::ActionLambda([this, connNo, feature, + name](MM::PropertyBase* pProp, + MM::ActionType eAct) { if (eAct == MM::BeforeGet) { short err{}; const float value = @@ -506,8 +496,9 @@ class DCCDCUModuleDevice : public CGenericBase> { const std::string& name) { this->CreateIntegerProperty( name.c_str(), 0, false, - new ActionLambda([this, connNo, feature, name]( - MM::PropertyBase* pProp, MM::ActionType eAct) { + new MM::ActionLambda([this, connNo, feature, + name](MM::PropertyBase* pProp, + MM::ActionType eAct) { if (eAct == MM::BeforeGet) { short err{}; const unsigned value = @@ -547,8 +538,8 @@ class DCCDCUModuleDevice : public CGenericBase> { const std::string& name) { this->CreateStringProperty( name.c_str(), "Off", false, - new ActionLambda([this, connNo](MM::PropertyBase* pProp, - MM::ActionType eAct) { + new MM::ActionLambda([this, connNo](MM::PropertyBase* pProp, + MM::ActionType eAct) { // There is no readout for enable_outputs, so we rely on the // last-set value. if (eAct == MM::AfterSet) { @@ -577,8 +568,8 @@ class DCCDCUModuleDevice : public CGenericBase> { void CreateEnableAllOutputsProperty() { this->CreateStringProperty( "EnableOutputs", "Off", false, - new ActionLambda([this](MM::PropertyBase* pProp, - MM::ActionType eAct) { + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { // There is no readout for enable_outputs, so we rely on the // last-set value. if (eAct == MM::AfterSet) { @@ -609,8 +600,8 @@ class DCCDCUModuleDevice : public CGenericBase> { const std::string& name) { this->CreateStringProperty( name.c_str(), "", false, - new ActionLambda([this, connNo](MM::PropertyBase* pProp, - MM::ActionType eAct) { + new MM::ActionLambda([this, connNo](MM::PropertyBase* pProp, + MM::ActionType eAct) { if (eAct == MM::AfterSet) { std::string propVal; pProp->Get(propVal); @@ -634,8 +625,8 @@ class DCCDCUModuleDevice : public CGenericBase> { void CreateClearAllOverloadsProperty() { this->CreateStringProperty( "ClearOverloads", "", false, - new ActionLambda([this](MM::PropertyBase* pProp, - MM::ActionType eAct) { + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { if (eAct == MM::AfterSet) { std::string propVal; pProp->Get(propVal); @@ -660,8 +651,8 @@ class DCCDCUModuleDevice : public CGenericBase> { const std::string& name) { this->CreateStringProperty( name.c_str(), "No", true, - new ActionLambda([this, connNo](MM::PropertyBase* pProp, - MM::ActionType eAct) { + new MM::ActionLambda([this, connNo](MM::PropertyBase* pProp, + MM::ActionType eAct) { if (eAct == MM::BeforeGet) { short err{}; const bool flag = module_->IsOverloaded(connNo, err); @@ -691,8 +682,8 @@ class DCCDCUModuleDevice : public CGenericBase> { const std::string& name) { this->CreateStringProperty( name.c_str(), "No", true, - new ActionLambda([this, connNo](MM::PropertyBase* pProp, - MM::ActionType eAct) { + new MM::ActionLambda([this, connNo](MM::PropertyBase* pProp, + MM::ActionType eAct) { if (eAct == MM::BeforeGet) { short err{}; const bool flag = From e0154e785898498f32bc2aa6b11fbb4790cef9ee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 23:43:29 +0000 Subject: [PATCH 077/141] Update secret-device-adapters-commit to latest --- secret-device-adapters-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret-device-adapters-commit b/secret-device-adapters-commit index 9a4653f16..76810a726 100644 --- a/secret-device-adapters-commit +++ b/secret-device-adapters-commit @@ -1 +1 @@ -f27c3b550436b08c85ef7e0794dd1736ccd4dc06 +bf0e2813637a519b3f1348788f9526b1590c5813 From e9df746687e70ab5f56880f24c089fe7bfe12d88 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Mon, 11 Dec 2023 11:35:54 -0500 Subject: [PATCH 078/141] remove print statements --- DeviceAdapters/HIDManager/HIDManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DeviceAdapters/HIDManager/HIDManager.cpp b/DeviceAdapters/HIDManager/HIDManager.cpp index 297cddddc..481916433 100755 --- a/DeviceAdapters/HIDManager/HIDManager.cpp +++ b/DeviceAdapters/HIDManager/HIDManager.cpp @@ -603,7 +603,7 @@ void HIDDeviceLister::ListHIDDevices(std::vector &availableDevices) // lists all HID Devices on this system that we know about void HIDDeviceLister::FindHIDDevices(std::vector &availableDevices) { - printf("Discovering HID Devices......\n"); + // printf("Discovering HID Devices......\n"); struct hid_device_info *devs, *curDev; @@ -613,14 +613,14 @@ void HIDDeviceLister::FindHIDDevices(std::vector &availableDevices) while (curDev) { - printf ("HID Device pid: %04hx vid: %04hx\n", curDev->product_id, curDev->product_id); + // printf ("HID Device pid: %04hx vid: %04hx\n", curDev->product_id, curDev->product_id); for (int i=0; ivendor_id == g_knownDevices[i].idVendor) && (curDev->product_id == g_knownDevices[i].idProduct) ) { availableDevices.push_back(g_knownDevices[i].name); - printf ("HID Device found: %s\n", g_knownDevices[i].name.c_str()); + // printf ("HID Device found: %s\n", g_knownDevices[i].name.c_str()); } } curDev = curDev->next; From 67f5585983578e9d7fdc0cd96fc9898692d1fc79 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Mon, 11 Dec 2023 11:36:16 -0500 Subject: [PATCH 079/141] add comment --- DeviceAdapters/HIDManager/HIDManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DeviceAdapters/HIDManager/HIDManager.cpp b/DeviceAdapters/HIDManager/HIDManager.cpp index 481916433..f2fb48802 100755 --- a/DeviceAdapters/HIDManager/HIDManager.cpp +++ b/DeviceAdapters/HIDManager/HIDManager.cpp @@ -603,6 +603,7 @@ void HIDDeviceLister::ListHIDDevices(std::vector &availableDevices) // lists all HID Devices on this system that we know about void HIDDeviceLister::FindHIDDevices(std::vector &availableDevices) { + // see https://github.com/micro-manager/mmCoreAndDevices/issues/402 // printf("Discovering HID Devices......\n"); struct hid_device_info *devs, *curDev; From 9e54a1abcae0e710b6bf187525780f0a0bee3fd2 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 11 Dec 2023 15:20:40 -0600 Subject: [PATCH 080/141] Add IDSPeak to micromanager.sln Remove unused header and #include. Change include/library directories to point to 3rdparty. --- DeviceAdapters/IDSPeak/IDSPeak.cpp | 1 - DeviceAdapters/IDSPeak/IDSPeak.vcxproj | 7 ++++--- DeviceAdapters/IDSPeak/IDSPeak.vcxproj.filters | 3 --- micromanager.sln | 6 ++++++ 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/DeviceAdapters/IDSPeak/IDSPeak.cpp b/DeviceAdapters/IDSPeak/IDSPeak.cpp index cfc6b1d7b..0eb68067c 100644 --- a/DeviceAdapters/IDSPeak/IDSPeak.cpp +++ b/DeviceAdapters/IDSPeak/IDSPeak.cpp @@ -35,7 +35,6 @@ #include "ModuleInterface.h" #include #include -#include "WriteCompactTiffRGB.h" #include #include diff --git a/DeviceAdapters/IDSPeak/IDSPeak.vcxproj b/DeviceAdapters/IDSPeak/IDSPeak.vcxproj index 5ef605e43..95c7f99e3 100644 --- a/DeviceAdapters/IDSPeak/IDSPeak.vcxproj +++ b/DeviceAdapters/IDSPeak/IDSPeak.vcxproj @@ -17,7 +17,6 @@ - @@ -74,12 +73,13 @@ _DEBUG;IDSPEAK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true pch.h - C:\Program Files\IDS\ids_peak\comfort_sdk\api\lib\x86_64;C:\Program Files\IDS\ids_peak\comfort_sdk\api\include;%(AdditionalIncludeDirectories) + $(MM_3RDPARTYPRIVATE)\IDS\ids_peak-2.6.2.0\comfort_sdk\api\include;%(AdditionalIncludeDirectories) Windows true false + $(MM_3RDPARTYPRIVATE)\IDS\ids_peak-2.6.2.0\comfort_sdk\api\lib\x86_64;%(AdditionalLibraryDirectories) @@ -90,7 +90,7 @@ NDEBUG;IDSPEAK_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true pch.h - C:\Program Files\IDS\ids_peak\comfort_sdk\api\lib\x86_64;C:\Program Files\IDS\ids_peak\comfort_sdk\api\include;%(AdditionalIncludeDirectories) + $(MM_3RDPARTYPRIVATE)\IDS\ids_peak-2.6.2.0\comfort_sdk\api\include;%(AdditionalIncludeDirectories) Windows @@ -98,6 +98,7 @@ true true false + $(MM_3RDPARTYPRIVATE)\IDS\ids_peak-2.6.2.0\comfort_sdk\api\lib\x86_64;%(AdditionalLibraryDirectories) diff --git a/DeviceAdapters/IDSPeak/IDSPeak.vcxproj.filters b/DeviceAdapters/IDSPeak/IDSPeak.vcxproj.filters index 1ae2eaf2d..f1e6401b2 100644 --- a/DeviceAdapters/IDSPeak/IDSPeak.vcxproj.filters +++ b/DeviceAdapters/IDSPeak/IDSPeak.vcxproj.filters @@ -18,9 +18,6 @@ Header Files - - Header Files - diff --git a/micromanager.sln b/micromanager.sln index 0874f84bf..31669dc12 100644 --- a/micromanager.sln +++ b/micromanager.sln @@ -476,6 +476,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ESP32", "DeviceAdapters\ESP EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WOSM", "DeviceAdapters\WOSM\WOSM.vcxproj", "{64E94A9E-B3AE-47EF-8F5A-0356B0E6B9DA}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IDSPeak", "DeviceAdapters\IDSPeak\IDSPeak.vcxproj", "{823CF77E-8120-41B1-9B25-823A79C93198}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -1430,6 +1432,10 @@ Global {64E94A9E-B3AE-47EF-8F5A-0356B0E6B9DA}.Debug|x64.Build.0 = Debug|x64 {64E94A9E-B3AE-47EF-8F5A-0356B0E6B9DA}.Release|x64.ActiveCfg = Release|x64 {64E94A9E-B3AE-47EF-8F5A-0356B0E6B9DA}.Release|x64.Build.0 = Release|x64 + {823CF77E-8120-41B1-9B25-823A79C93198}.Debug|x64.ActiveCfg = Debug|x64 + {823CF77E-8120-41B1-9B25-823A79C93198}.Debug|x64.Build.0 = Debug|x64 + {823CF77E-8120-41B1-9B25-823A79C93198}.Release|x64.ActiveCfg = Release|x64 + {823CF77E-8120-41B1-9B25-823A79C93198}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 7606a03bc48d57dee1187ce6f33a280be86d1b9d Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Fri, 8 Dec 2023 16:24:39 -0600 Subject: [PATCH 081/141] MMDevice: Avoid using namespace std --- DeviceAdapters/Andor/SRRFControl.cpp | 1 + MMDevice/Debayer.cpp | 5 +-- MMDevice/Debayer.h | 3 ++ MMDevice/ImgBuffer.cpp | 16 ++++---- MMDevice/ImgBuffer.h | 4 +- MMDevice/Property.cpp | 58 ++++++++++++++-------------- MMDevice/Property.h | 2 - 7 files changed, 44 insertions(+), 45 deletions(-) diff --git a/DeviceAdapters/Andor/SRRFControl.cpp b/DeviceAdapters/Andor/SRRFControl.cpp index eddb62163..13b95cc3c 100644 --- a/DeviceAdapters/Andor/SRRFControl.cpp +++ b/DeviceAdapters/Andor/SRRFControl.cpp @@ -1,6 +1,7 @@ #include "SRRFControl.h" #include #include "boost/date_time/posix_time/posix_time.hpp" +#include "DeviceUtils.h" #include "ImgBuffer.h" #ifdef WIN32 diff --git a/MMDevice/Debayer.cpp b/MMDevice/Debayer.cpp index 3c9cefdd9..2cf606494 100644 --- a/MMDevice/Debayer.cpp +++ b/MMDevice/Debayer.cpp @@ -24,9 +24,8 @@ /////////////////////////////////////////////////////////////////////////////// #include "Debayer.h" -#include -#include -using namespace std; + +#include /////////////////////////////////////////////////////////////////////////////// // Debayer class implementation diff --git a/MMDevice/Debayer.h b/MMDevice/Debayer.h index 84365bf9f..80550bd72 100644 --- a/MMDevice/Debayer.h +++ b/MMDevice/Debayer.h @@ -28,6 +28,9 @@ #include "ImgBuffer.h" +#include +#include + /** * Utility class to build color image from the Bayer grayscale image * Based on the Debayer_Image plugin for ImageJ, by Jennifer West, University of Manitoba diff --git a/MMDevice/ImgBuffer.cpp b/MMDevice/ImgBuffer.cpp index ae67c6809..5c48a74b7 100644 --- a/MMDevice/ImgBuffer.cpp +++ b/MMDevice/ImgBuffer.cpp @@ -19,9 +19,9 @@ // NOTE: Imported from ADVI for use in Micro-Manager /////////////////////////////////////////////////////////////////////////////// #include "ImgBuffer.h" -#include -#include -using namespace std; + +#include +#include /////////////////////////////////////////////////////////////////////////////// // ImgBuffer class @@ -31,7 +31,7 @@ ImgBuffer::ImgBuffer(unsigned xSize, unsigned ySize, unsigned pixDepth) : { pixels_ = new unsigned char[xSize * ySize * pixDepth]; assert(pixels_); - memset(pixels_, 0, xSize * ySize * pixDepth); + std::memset(pixels_, 0, xSize * ySize * pixDepth); } ImgBuffer::ImgBuffer() : @@ -65,7 +65,7 @@ unsigned char* ImgBuffer::GetPixelsRW() void ImgBuffer::SetPixels(const void* pix) { - memcpy((void*)pixels_, pix, width_ * height_ * pixDepth_); + std::memcpy((void*)pixels_, pix, width_ * height_ * pixDepth_); } // Set pixels, from a source that has extra bytes at the end of each scanline @@ -78,7 +78,7 @@ void ImgBuffer::SetPixelsPadded(const void* pixArray, int paddingBytesPerLine) for(size_t i = 0; i < height_; i++) { - memcpy(dst, src, lineSize); + std::memcpy(dst, src, lineSize); src += lineSize + paddingBytesPerLine; dst += lineSize; } @@ -87,7 +87,7 @@ void ImgBuffer::SetPixelsPadded(const void* pixArray, int paddingBytesPerLine) void ImgBuffer::ResetPixels() { if (pixels_) - memset(pixels_, 0, width_ * height_ * pixDepth_); + std::memset(pixels_, 0, width_ * height_ * pixDepth_); } bool ImgBuffer::Compatible(const ImgBuffer& img) const @@ -127,7 +127,7 @@ void ImgBuffer::Resize(unsigned xSize, unsigned ySize) width_ = xSize; height_ = ySize; - memset(pixels_, 0, width_ * height_ * pixDepth_); + std::memset(pixels_, 0, width_ * height_ * pixDepth_); } void ImgBuffer::Copy(const ImgBuffer& right) diff --git a/MMDevice/ImgBuffer.h b/MMDevice/ImgBuffer.h index f53c417dd..ce31dd11e 100644 --- a/MMDevice/ImgBuffer.h +++ b/MMDevice/ImgBuffer.h @@ -24,9 +24,7 @@ #define _IMG_BUFFER_ #include -#include -#include -#include "MMDevice.h" + #include "ImageMetadata.h" /////////////////////////////////////////////////////////////////////////////// diff --git a/MMDevice/Property.cpp b/MMDevice/Property.cpp index 1e7a3b5a1..b7f807ccd 100644 --- a/MMDevice/Property.cpp +++ b/MMDevice/Property.cpp @@ -21,18 +21,18 @@ #include "Property.h" +#include +#include +#include #include -#include - -using namespace std; -const int BUFSIZE = 60; // For number-to-string conversion +static const int BUFSIZE = 60; // For number-to-string conversion -vector MM::Property::GetAllowedValues() const +std::vector MM::Property::GetAllowedValues() const { - vector vals; - map::const_iterator it; + std::vector vals; + std::map::const_iterator it; for (it=values_.begin(); it != values_.end(); it++) vals.push_back(it->first); return vals; @@ -40,13 +40,13 @@ vector MM::Property::GetAllowedValues() const void MM::Property::AddAllowedValue(const char* value) { - values_.insert(make_pair(value, 0L)); + values_.insert(std::make_pair(value, 0L)); limits_ = false; } void MM::Property::AddAllowedValue(const char* value, long data) { - values_.insert(make_pair(value, data)); + values_.insert(std::make_pair(value, data)); hasData_ = true; limits_ = false; } @@ -57,7 +57,7 @@ bool MM::Property::IsAllowed(const char* value) const if (values_.size() == 0) return true; // any value is allowed - map::const_iterator it = values_.find(value); + std::map::const_iterator it = values_.find(value); if (it == values_.end()) return false; // not found else @@ -69,7 +69,7 @@ bool MM::Property::GetData(const char* value, long& data) const if (!hasData_) return false; - map::const_iterator it = values_.find(value); + std::map::const_iterator it = values_.find(value); if (it == values_.end()) return false; // not found else @@ -93,7 +93,7 @@ void MM::Property::SetSequenceable(long sequenceMaxSize) bool MM::StringProperty::Set(double val) { char buf[BUFSIZE]; - snprintf(buf, BUFSIZE, "%.2g", val); + std::snprintf(buf, BUFSIZE, "%.2g", val); value_ = buf; return true; } @@ -101,7 +101,7 @@ bool MM::StringProperty::Set(double val) bool MM::StringProperty::Set(long val) { char buf[BUFSIZE]; - snprintf(buf, BUFSIZE, "%ld", val); + std::snprintf(buf, BUFSIZE, "%ld", val); value_ = buf; return true; } @@ -114,13 +114,13 @@ bool MM::StringProperty::Set(const char* val) bool MM::StringProperty::Get(double& val) const { - val = atof(value_.c_str()); + val = std::atof(value_.c_str()); return true; } bool MM::StringProperty::Get(long& val) const { - val = atol(value_.c_str()); + val = std::atol(value_.c_str()); return true; } @@ -138,19 +138,19 @@ bool MM::StringProperty::Get(std::string& strVal) const double MM::FloatProperty::Truncate(double dVal) { if (dVal >= 0) - return floor(dVal * reciprocalMinimalStep_ + 0.5) / reciprocalMinimalStep_; + return std::floor(dVal * reciprocalMinimalStep_ + 0.5) / reciprocalMinimalStep_; else - return ceil(dVal * reciprocalMinimalStep_ - 0.5) / reciprocalMinimalStep_; + return std::ceil(dVal * reciprocalMinimalStep_ - 0.5) / reciprocalMinimalStep_; } double MM::FloatProperty::TruncateDown(double dVal) { - return floor(dVal * reciprocalMinimalStep_) / reciprocalMinimalStep_; + return std::floor(dVal * reciprocalMinimalStep_) / reciprocalMinimalStep_; } double MM::FloatProperty::TruncateUp(double dVal) { - return ceil(dVal * reciprocalMinimalStep_) / reciprocalMinimalStep_; + return std::ceil(dVal * reciprocalMinimalStep_) / reciprocalMinimalStep_; } bool MM::FloatProperty::Set(double dVal) @@ -191,8 +191,8 @@ bool MM::FloatProperty::Get(std::string& strVal) const { char fmtStr[20]; char buf[BUFSIZE]; - sprintf(fmtStr, "%%.%df", decimalPlaces_); - snprintf(buf, BUFSIZE, fmtStr, value_); + std::sprintf(fmtStr, "%%.%df", decimalPlaces_); + std::snprintf(buf, BUFSIZE, fmtStr, value_); strVal = buf; return true; } @@ -225,7 +225,7 @@ bool MM::IntegerProperty::Set(long lVal) bool MM::IntegerProperty::Set(const char* pszVal) { - return Set(atol(pszVal)); + return Set(std::atol(pszVal)); } bool MM::IntegerProperty::Get(double& dVal) const @@ -243,7 +243,7 @@ bool MM::IntegerProperty::Get(long& lVal) const bool MM::IntegerProperty::Get(std::string& strVal) const { char pszBuf[BUFSIZE]; - snprintf(pszBuf, BUFSIZE, "%ld", value_); + std::snprintf(pszBuf, BUFSIZE, "%ld", value_); strVal = pszBuf; return true; } @@ -286,7 +286,7 @@ int MM::PropertyCollection::Set(const char* pszPropName, const char* pszValue) return DEVICE_INVALID_PROPERTY_VALUE; } -int MM::PropertyCollection::Get(const char* pszPropName, string& strValue) const +int MM::PropertyCollection::Get(const char* pszPropName, std::string& strValue) const { MM::Property* pProp = Find(pszPropName); if (!pProp) @@ -310,9 +310,9 @@ MM::Property* MM::PropertyCollection::Find(const char* pszName) const return it->second; } -vector MM::PropertyCollection::GetNames() const +std::vector MM::PropertyCollection::GetNames() const { - vector nameList; + std::vector nameList; CPropArray::const_iterator it; for (it = properties_.begin(); it != properties_.end(); it++) @@ -363,7 +363,7 @@ int MM::PropertyCollection::CreateProperty(const char* pszName, const char* pszV return DEVICE_OK; } -int MM::PropertyCollection::SetAllowedValues(const char* pszName, vector& values) +int MM::PropertyCollection::SetAllowedValues(const char* pszName, std::vector& values) { MM::Property* pProp = Find(pszName); if (!pProp) @@ -425,7 +425,7 @@ int MM::PropertyCollection::GetCurrentPropertyData(const char* name, long& data) if (!pProp) return DEVICE_INVALID_PROPERTY; // name not found - string value; + std::string value; pProp->Get(value); if (!pProp->GetData(value.c_str(), data)) return DEVICE_NO_PROPERTY_DATA; @@ -433,7 +433,7 @@ int MM::PropertyCollection::GetCurrentPropertyData(const char* name, long& data) return DEVICE_OK; } -bool MM::PropertyCollection::GetName(unsigned uIdx, string& strName) const +bool MM::PropertyCollection::GetName(unsigned uIdx, std::string& strName) const { if (uIdx >= properties_.size()) return false; // unknown index diff --git a/MMDevice/Property.h b/MMDevice/Property.h index cbc669822..bc2acfb0f 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -25,8 +25,6 @@ #include "MMDeviceConstants.h" -#include -#include #include #include #include From 31d87dfcf9ccf364528ace558426d7adecc1289f Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Fri, 8 Dec 2023 16:36:19 -0600 Subject: [PATCH 082/141] MMDevice: Use #pragma once consistently --- MMDevice/Debayer.h | 6 +----- MMDevice/DeviceBase.h | 9 +-------- MMDevice/DeviceUtils.h | 9 ++------- MMDevice/ImageMetadata.h | 10 +++------- MMDevice/ImgBuffer.h | 6 +----- MMDevice/MMDevice.h | 10 +--------- MMDevice/MMDeviceConstants.h | 7 +------ MMDevice/ModuleInterface.h | 6 +----- MMDevice/Property.h | 5 +---- 9 files changed, 12 insertions(+), 56 deletions(-) diff --git a/MMDevice/Debayer.h b/MMDevice/Debayer.h index 80550bd72..69a1b6013 100644 --- a/MMDevice/Debayer.h +++ b/MMDevice/Debayer.h @@ -21,10 +21,8 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. // -/////////////////////////////////////////////////////////////////////////////// -#if !defined(_DEBAYER_) -#define _DEBAYER_ +#pragma once #include "ImgBuffer.h" @@ -74,5 +72,3 @@ class Debayer int orderIndex; int algoIndex; }; - -#endif // !defined(_DEBAYER_) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 4321f5945..fc3d80c68 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -20,12 +20,8 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. // -// - -#ifndef _DEVICE_BASE_H_ -#define _DEVICE_BASE_H_ - +#pragma once #include "MMDevice.h" #include "MMDeviceConstants.h" @@ -2500,6 +2496,3 @@ class CStateDeviceBase : public CDeviceBase _end_time = GetCurrentMMTime(); \ LogTimeDiff(_start_time,_end_time, true); \ } - -#endif //_DEVICE_BASE_H_ - diff --git a/MMDevice/DeviceUtils.h b/MMDevice/DeviceUtils.h index 8d5e1ac0f..bee2f5ce1 100644 --- a/MMDevice/DeviceUtils.h +++ b/MMDevice/DeviceUtils.h @@ -17,13 +17,10 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. // -// CVS: $Id$ -// -#ifndef _DEVICEUTILS_H_ -#define _DEVICEUTILS_H_ +#pragma once -#include "../MMDevice/MMDeviceConstants.h" +#include "MMDeviceConstants.h" #include #include #ifdef _WIN32 @@ -69,5 +66,3 @@ class CDeviceUtils private: static char m_pszBuffer[MM::MaxStrLength]; }; - -#endif //_DEVICEUTILS_H_ diff --git a/MMDevice/ImageMetadata.h b/MMDevice/ImageMetadata.h index 651d74c28..73f375e92 100644 --- a/MMDevice/ImageMetadata.h +++ b/MMDevice/ImageMetadata.h @@ -19,10 +19,8 @@ // IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -// CVS: $Id: Configuration.h 2 2007-02-27 23:33:17Z nenad $ -// -#ifndef _IMAGE_METADATA_H_ -#define _IMAGE_METADATA_H_ + +#pragma once #ifdef WIN32 // disable exception scpecification warnings in MSVC @@ -494,6 +492,4 @@ class Metadata std::map tags_; typedef std::map::iterator TagIter; typedef std::map::const_iterator TagConstIter; -}; - -#endif //_IMAGE_METADATA_H_ \ No newline at end of file +}; \ No newline at end of file diff --git a/MMDevice/ImgBuffer.h b/MMDevice/ImgBuffer.h index ce31dd11e..6c3badac8 100644 --- a/MMDevice/ImgBuffer.h +++ b/MMDevice/ImgBuffer.h @@ -18,10 +18,8 @@ // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. // // NOTE: Imported from ADVI for use in Micro-Manager -/////////////////////////////////////////////////////////////////////////////// -#if !defined(_IMG_BUFFER_) -#define _IMG_BUFFER_ +#pragma once #include @@ -71,5 +69,3 @@ class ImgBuffer std::string name_; Metadata metadata_; }; - -#endif // !defined(_IMG_BUFFER_) diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index 6b53c047b..e4be5ebe3 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -22,6 +22,7 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +#pragma once /////////////////////////////////////////////////////////////////////////////// // Header version @@ -30,7 +31,6 @@ #define DEVICE_INTERFACE_VERSION 71 /////////////////////////////////////////////////////////////////////////////// - // N.B. // // Never add parameters or return values that are not POD @@ -39,11 +39,6 @@ // is not acceptable (use const char*). This is to prevent inter-DLL // incompatibilities. - -#pragma once -#ifndef MMMMDEVICE_H -#define MMMMDEVICE_H - #include "MMDeviceConstants.h" #include "DeviceUtils.h" #include "ImageMetadata.h" @@ -1364,6 +1359,3 @@ namespace MM { }; } // namespace MM - -#endif //MMMMDEVICE_H - diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index 058572b91..23c8cbc18 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -18,11 +18,8 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. // -// CVS: $Id$ -// -#ifndef _MMDEVICE_CONSTANTS_H_ -#define _MMDEVICE_CONSTANTS_H_ +#pragma once /////////////////////////////////////////////////////////////////////////////// // Global error codes @@ -267,5 +264,3 @@ namespace MM { }; } // namespace MM - -#endif //_MMDEVICE_CONSTANTS_H_ diff --git a/MMDevice/ModuleInterface.h b/MMDevice/ModuleInterface.h index 465f56afb..5faef75f6 100644 --- a/MMDevice/ModuleInterface.h +++ b/MMDevice/ModuleInterface.h @@ -25,8 +25,7 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -#ifndef _MODULE_INTERFACE_H_ -#define _MODULE_INTERFACE_H_ +#pragma once #include "MMDevice.h" @@ -136,6 +135,3 @@ extern "C" { * \see InitializeModuleData() */ void RegisterDevice(const char* deviceName, MM::DeviceType deviceType, const char* description); - - -#endif //_MODULE_INTERFACE_H_ diff --git a/MMDevice/Property.h b/MMDevice/Property.h index bc2acfb0f..24c1a8920 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -17,11 +17,9 @@ // IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -// CVS: $Id$ // -#ifndef _MMPROPERTY_H_ -#define _MMPROPERTY_H_ +#pragma once #include "MMDeviceConstants.h" @@ -465,4 +463,3 @@ class PropertyCollection } // namespace MM -#endif //_MMPROPERTY_H_ From 4b88884f946ef748ce2b11adf54d14fc14a2a76e Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Fri, 8 Dec 2023 17:01:24 -0600 Subject: [PATCH 083/141] MMCore: Avoid 'using namespace' Mostly by adding 'std::', but as an exception, 'endl' has been replaced with \n (which is exactly equivalent but without flushing). --- MMCore/Configuration.cpp | 28 +++-- MMCore/CoreProperty.cpp | 40 +++---- MMCore/LogManager.cpp | 64 +++++------ MMCore/MMCore.cpp | 240 +++++++++++++++++++-------------------- 4 files changed, 183 insertions(+), 189 deletions(-) diff --git a/MMCore/Configuration.cpp b/MMCore/Configuration.cpp index 87ae0b1ee..32c8fa8ae 100644 --- a/MMCore/Configuration.cpp +++ b/MMCore/Configuration.cpp @@ -21,17 +21,15 @@ #include "Configuration.h" #include "../MMDevice/MMDevice.h" #include "Error.h" -#include -#include -#include + #include #include +#include +#include -using namespace std; - -string PropertySetting::generateKey(const char* device, const char* prop) +std::string PropertySetting::generateKey(const char* device, const char* prop) { - string key(device); + std::string key(device); key += "-"; key += prop; return key; @@ -40,9 +38,9 @@ string PropertySetting::generateKey(const char* device, const char* prop) /** * Returns verbose description of the object's contents. */ -string PropertySetting::getVerbose() const +std::string PropertySetting::getVerbose() const { - ostringstream txt; + std::ostringstream txt; txt << deviceLabel_ << ":" << propertyName_ << "=" << value_; return txt.str(); } @@ -93,7 +91,7 @@ PropertySetting Configuration::getSetting(size_t index) const throw (CMMError) bool Configuration::isPropertyIncluded(const char* device, const char* prop) { - map::iterator it = index_.find(PropertySetting::generateKey(device, prop)); + std::map::iterator it = index_.find(PropertySetting::generateKey(device, prop)); if (it != index_.end()) return true; else @@ -106,7 +104,7 @@ bool Configuration::isPropertyIncluded(const char* device, const char* prop) PropertySetting Configuration::getSetting(const char* device, const char* prop) { - map::iterator it = index_.find(PropertySetting::generateKey(device, prop)); + std::map::iterator it = index_.find(PropertySetting::generateKey(device, prop)); if (it == index_.end()) { std::ostringstream errTxt; @@ -128,7 +126,7 @@ PropertySetting Configuration::getSetting(const char* device, const char* prop) bool Configuration::isSettingIncluded(const PropertySetting& ps) { - map::iterator it = index_.find(ps.getKey()); + std::map::iterator it = index_.find(ps.getKey()); if (it != index_.end() && settings_[it->second].getPropertyValue().compare(ps.getPropertyValue()) == 0) return true; else @@ -143,7 +141,7 @@ bool Configuration::isSettingIncluded(const PropertySetting& ps) bool Configuration::isConfigurationIncluded(const Configuration& cfg) { - vector::const_iterator it; + std::vector::const_iterator it; for (it=cfg.settings_.begin(); it!=cfg.settings_.end(); ++it) if (!isSettingIncluded(*it)) return false; @@ -156,7 +154,7 @@ bool Configuration::isConfigurationIncluded(const Configuration& cfg) */ void Configuration::addSetting(const PropertySetting& setting) { - map::iterator it = index_.find(setting.getKey()); + std::map::iterator it = index_.find(setting.getKey()); if (it != index_.end()) { // replace @@ -175,7 +173,7 @@ void Configuration::addSetting(const PropertySetting& setting) */ void Configuration::deleteSetting(const char* device, const char* prop) { - map::iterator it = index_.find(PropertySetting::generateKey(device, prop)); + std::map::iterator it = index_.find(PropertySetting::generateKey(device, prop)); if (it == index_.end()) { std::ostringstream errTxt; diff --git a/MMCore/CoreProperty.cpp b/MMCore/CoreProperty.cpp index ad08fb44d..ce5a43f5d 100644 --- a/MMCore/CoreProperty.cpp +++ b/MMCore/CoreProperty.cpp @@ -29,14 +29,14 @@ #include "MMCore.h" #include "Error.h" #include "../MMDevice/DeviceUtils.h" -#include -#include -using namespace std; -vector CoreProperty::GetAllowedValues() const +#include +#include + +std::vector CoreProperty::GetAllowedValues() const { - vector allowedVals; - for (set::const_iterator it=values_.begin(); it!=values_.end(); ++it) + std::vector allowedVals; + for (std::set::const_iterator it=values_.begin(); it!=values_.end(); ++it) allowedVals.push_back(*it); return allowedVals; } @@ -51,7 +51,7 @@ bool CoreProperty::IsAllowed(const char* value) const if (values_.size() == 0) return true; - set::const_iterator it; + std::set::const_iterator it; if (values_.find(value) == values_.end()) return false; else @@ -67,14 +67,14 @@ bool CoreProperty::Set(const char* value) return true; } -string CoreProperty::Get() const +std::string CoreProperty::Get() const { return value_; } void CorePropertyCollection::Set(const char* propName, const char* value) { - map::iterator it = properties_.find(propName); + std::map::iterator it = properties_.find(propName); if (it == properties_.end()) throw CMMError("Cannot set invalid Core property (" + ToString(propName) + ") to value \"" + ToString(value) + "\"", @@ -173,9 +173,9 @@ void CorePropertyCollection::Execute(const char* propName, const char* value) } } -string CorePropertyCollection::Get(const char* propName) const +std::string CorePropertyCollection::Get(const char* propName) const { - map::const_iterator it = properties_.find(propName); + std::map::const_iterator it = properties_.find(propName); if (it == properties_.end()) throw CMMError("Cannot get value of invalid Core property (" + ToString(propName) + ")", @@ -186,17 +186,17 @@ string CorePropertyCollection::Get(const char* propName) const bool CorePropertyCollection::Has(const char* propName) const { - map::const_iterator it = properties_.find(propName); + std::map::const_iterator it = properties_.find(propName); if (it == properties_.end()) return false; // not defined return true; } -vector CorePropertyCollection::GetNames() const +std::vector CorePropertyCollection::GetNames() const { - vector names; - for (map::const_iterator it=properties_.begin(); it!=properties_.end(); ++it) + std::vector names; + for (std::map::const_iterator it=properties_.begin(); it!=properties_.end(); ++it) names.push_back(it->first); return names; } @@ -245,7 +245,7 @@ void CorePropertyCollection::Refresh() bool CorePropertyCollection::IsReadOnly(const char* propName) const { - map::const_iterator it = properties_.find(propName); + std::map::const_iterator it = properties_.find(propName); if (it == properties_.end()) throw CMMError("Invalid Core property (" + ToString(propName) + ")", MMERR_InvalidCoreProperty); @@ -253,9 +253,9 @@ bool CorePropertyCollection::IsReadOnly(const char* propName) const return it->second.IsReadOnly(); } -vector CorePropertyCollection::GetAllowedValues(const char* propName) const +std::vector CorePropertyCollection::GetAllowedValues(const char* propName) const { - map::const_iterator it = properties_.find(propName); + std::map::const_iterator it = properties_.find(propName); if (it == properties_.end()) throw CMMError("Invalid Core property (" + ToString(propName) + ")", MMERR_InvalidCoreProperty); @@ -265,7 +265,7 @@ vector CorePropertyCollection::GetAllowedValues(const char* propName) co void CorePropertyCollection::ClearAllowedValues(const char* propName) { - map::iterator it = properties_.find(propName); + std::map::iterator it = properties_.find(propName); if (it == properties_.end()) throw CMMError("Invalid Core property (" + ToString(propName) + ")", MMERR_InvalidCoreProperty); @@ -275,7 +275,7 @@ void CorePropertyCollection::ClearAllowedValues(const char* propName) void CorePropertyCollection::AddAllowedValue(const char* propName, const char* value) { - map::iterator it = properties_.find(propName); + std::map::iterator it = properties_.find(propName); if (it == properties_.end()) throw CMMError("Invalid Core property (" + ToString(propName) + ")", MMERR_InvalidCoreProperty); diff --git a/MMCore/LogManager.cpp b/MMCore/LogManager.cpp index 2b2ae5ca3..bee41e1a2 100644 --- a/MMCore/LogManager.cpp +++ b/MMCore/LogManager.cpp @@ -11,21 +11,19 @@ namespace mm { -using namespace mm::logging; - namespace { -const char* StringForLogLevel(LogLevel level) +const char* StringForLogLevel(logging::LogLevel level) { switch (level) { - case LogLevelTrace: return "trace"; - case LogLevelDebug: return "debug"; - case LogLevelInfo: return "info"; - case LogLevelWarning: return "warning"; - case LogLevelError: return "error"; - case LogLevelFatal: return "fatal"; + case logging::LogLevelTrace: return "trace"; + case logging::LogLevelDebug: return "debug"; + case logging::LogLevelInfo: return "info"; + case logging::LogLevelWarning: return "warning"; + case logging::LogLevelError: return "error"; + case logging::LogLevelFatal: return "fatal"; default: return "(unknown)"; } } @@ -35,9 +33,9 @@ const char* StringForLogLevel(LogLevel level) const logging::SinkMode LogManager::PrimarySinkMode = logging::SinkModeAsynchronous; LogManager::LogManager() : - loggingCore_(std::make_shared()), + loggingCore_(std::make_shared()), internalLogger_(loggingCore_->NewLogger("LogManager")), - primaryLogLevel_(LogLevelInfo), + primaryLogLevel_(logging::LogLevelInfo), usingStdErr_(false), nextSecondaryHandle_(0) {} @@ -56,9 +54,9 @@ LogManager::SetUseStdErr(bool flag) { if (!stdErrSink_) { - stdErrSink_ = std::make_shared(); + stdErrSink_ = std::make_shared(); stdErrSink_->SetFilter( - std::make_shared(primaryLogLevel_)); + std::make_shared(primaryLogLevel_)); } loggingCore_->AddSink(stdErrSink_, PrimarySinkMode); @@ -102,12 +100,12 @@ LogManager::SetPrimaryLogFilename(const std::string& filename, bool truncate) return; } - std::shared_ptr newSink; + std::shared_ptr newSink; try { - newSink = std::make_shared(primaryFilename_, !truncate); + newSink = std::make_shared(primaryFilename_, !truncate); } - catch (const CannotOpenFileException&) + catch (const logging::CannotOpenFileException&) { LOG_ERROR(internalLogger_) << "Failed to open file " << filename << " as primary log file"; @@ -121,7 +119,7 @@ LogManager::SetPrimaryLogFilename(const std::string& filename, bool truncate) throw CMMError("Cannot open file " + ToQuotedString(filename)); } - newSink->SetFilter(std::make_shared(primaryLogLevel_)); + newSink->SetFilter(std::make_shared(primaryLogLevel_)); if (!primaryFileSink_) { @@ -137,8 +135,8 @@ LogManager::SetPrimaryLogFilename(const std::string& filename, bool truncate) // rotation. LOG_INFO(internalLogger_) << "Switching primary log file"; - std::vector< std::pair, SinkMode> > toRemove; - std::vector< std::pair, SinkMode> > toAdd; + std::vector, logging::SinkMode>> toRemove; + std::vector, logging::SinkMode>> toAdd; toRemove.push_back( std::make_pair(primaryFileSink_, PrimarySinkMode)); toAdd.push_back(std::make_pair(newSink, PrimarySinkMode)); @@ -169,26 +167,26 @@ LogManager::IsUsingPrimaryLogFile() const void -LogManager::SetPrimaryLogLevel(LogLevel level) +LogManager::SetPrimaryLogLevel(logging::LogLevel level) { std::lock_guard lock(mutex_); if (level == primaryLogLevel_) return; - LogLevel oldLevel = primaryLogLevel_; + logging::LogLevel oldLevel = primaryLogLevel_; primaryLogLevel_ = level; LOG_INFO(internalLogger_) << "Switching primary log level from " << StringForLogLevel(oldLevel) << " to " << StringForLogLevel(level); - std::shared_ptr filter = - std::make_shared(level); + std::shared_ptr filter = + std::make_shared(level); std::vector< std::pair< - std::pair, SinkMode>, - std::shared_ptr + std::pair, logging::SinkMode>, + std::shared_ptr > > changes; if (stdErrSink_) @@ -211,7 +209,7 @@ LogManager::SetPrimaryLogLevel(LogLevel level) } -LogLevel +logging::LogLevel LogManager::GetPrimaryLogLevel() const { std::lock_guard lock(mutex_); @@ -220,24 +218,24 @@ LogManager::GetPrimaryLogLevel() const LogManager::LogFileHandle -LogManager::AddSecondaryLogFile(LogLevel level, - const std::string& filename, bool truncate, SinkMode mode) +LogManager::AddSecondaryLogFile(logging::LogLevel level, + const std::string& filename, bool truncate, logging::SinkMode mode) { std::lock_guard lock(mutex_); - std::shared_ptr sink; + std::shared_ptr sink; try { - sink = std::make_shared(filename, !truncate); + sink = std::make_shared(filename, !truncate); } - catch (const CannotOpenFileException&) + catch (const logging::CannotOpenFileException&) { LOG_ERROR(internalLogger_) << "Failed to open file " << filename << " as secondary log file"; throw CMMError("Cannot open file " + ToQuotedString(filename)); } - sink->SetFilter(std::make_shared(level)); + sink->SetFilter(std::make_shared(level)); LogFileHandle handle = nextSecondaryHandle_++; secondaryLogFiles_.insert(std::make_pair(handle, @@ -273,7 +271,7 @@ LogManager::RemoveSecondaryLogFile(LogManager::LogFileHandle handle) } -Logger +logging::Logger LogManager::NewLogger(const std::string& label) { return loggingCore_->NewLogger(label); diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index e2c7107f6..5d01bf21b 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -65,8 +65,6 @@ #include #include -using namespace std; - /* * Important! Read this before changing this file: * @@ -351,10 +349,10 @@ void CMMCore::stopSecondaryLogFile(int handle) throw (CMMError) /** * Displays core version. */ -string CMMCore::getVersionInfo() const +std::string CMMCore::getVersionInfo() const { - ostringstream txt; - string debug; + std::ostringstream txt; + std::string debug; txt << "MMCore version " << MMCore_versionMajor << "." << MMCore_versionMinor << "." << MMCore_versionPatch; #ifdef _DEBUG txt << " (debug)"; @@ -419,9 +417,9 @@ CMMCore::getAvailableDeviceTypes(const char* moduleName) throw (CMMError) /** * Returns the module and device interface versions. */ -string CMMCore::getAPIVersionInfo() const +std::string CMMCore::getAPIVersionInfo() const { - ostringstream txt; + std::ostringstream txt; txt << "Device API version " << DEVICE_INTERFACE_VERSION << ", " << "Module API version " << MODULE_INTERFACE_VERSION; return txt.str(); } @@ -438,8 +436,8 @@ string CMMCore::getAPIVersionInfo() const Configuration CMMCore::getSystemState() { Configuration config; - vector devices = deviceManager_->GetDeviceList(); - for (vector::const_iterator i = devices.begin(), dend = devices.end(); i != dend; ++i) + std::vector devices = deviceManager_->GetDeviceList(); + for (std::vector::const_iterator i = devices.begin(), dend = devices.end(); i != dend; ++i) { std::shared_ptr pDev = deviceManager_->GetDevice(*i); mm::DeviceModuleLockGuard guard(pDev); @@ -473,11 +471,11 @@ Configuration CMMCore::getSystemState() } // add core properties - vector coreProps = properties_->GetNames(); + std::vector coreProps = properties_->GetNames(); for (unsigned i=0; i < coreProps.size(); i++) { - string name = coreProps[i]; - string val = properties_->Get(name.c_str()); + std::string name = coreProps[i]; + std::string val = properties_->Get(name.c_str()); config.addSetting(PropertySetting(MM::g_Keyword_CoreDevice, name.c_str(), val.c_str(), properties_->IsReadOnly(name.c_str()))); } @@ -507,7 +505,7 @@ Configuration CMMCore::getConfigState(const char* group, const char* config) thr for (size_t i=0; i < cfgData.size(); i++) { PropertySetting cs = cfgData.getSetting(i); // config setting - string value = getProperty(cs.getDeviceLabel().c_str(), cs.getPropertyName().c_str()); + std::string value = getProperty(cs.getDeviceLabel().c_str(), cs.getPropertyName().c_str()); PropertySetting ss(cs.getDeviceLabel().c_str(), cs.getPropertyName().c_str(), value.c_str()); // state setting state.addSetting(ss); } @@ -844,7 +842,7 @@ void CMMCore::reset() throw (CMMError) */ void CMMCore::initializeAllDevices() throw (CMMError) { - vector devices = deviceManager_->GetDeviceList(); + std::vector devices = deviceManager_->GetDeviceList(); LOG_INFO(coreLogger_) << "Will initialize " << devices.size() << " devices"; for (size_t i=0; i devices = getLoadedDevicesOfType(devType); + std::vector devices = getLoadedDevicesOfType(devType); devices.push_back(""); // add empty value properties_->ClearAllowedValues(propName); for (size_t i=0; i devices = deviceManager_->GetDeviceList(); - vector::reverse_iterator it; + std::vector devices = deviceManager_->GetDeviceList(); + std::vector::reverse_iterator it; for (it=devices.rbegin(); it != devices.rend(); it++) { std::shared_ptr pDev = deviceManager_->GetDevice(*it); @@ -1205,7 +1203,7 @@ void CMMCore::waitForDevice(std::shared_ptr pDev) throw (CMMErro if (std::chrono::steady_clock::now() > deadline) { - string label = pDev->GetLabel(); + std::string label = pDev->GetLabel(); std::ostringstream mez; mez << "wait timed out after " << timeoutMs_ << " ms. "; logError(label.c_str(), mez.str().c_str()); @@ -1248,7 +1246,7 @@ void CMMCore::waitForSystem() throw (CMMError) */ bool CMMCore::deviceTypeBusy(MM::DeviceType devType) throw (CMMError) { - vector devices = deviceManager_->GetDeviceList(devType); + std::vector devices = deviceManager_->GetDeviceList(devType); for (size_t i=0; i devices = deviceManager_->GetDeviceList(devType); + std::vector devices = deviceManager_->GetDeviceList(devType); for (size_t i=0; i camera = currentCameraDevice_.lock(); if (camera) @@ -3174,7 +3172,7 @@ string CMMCore::getCameraDevice() * Returns the label of the currently selected shutter device. * @return shutter name */ -string CMMCore::getShutterDevice() +std::string CMMCore::getShutterDevice() { std::shared_ptr shutter = currentShutterDevice_.lock(); if (shutter) @@ -3188,7 +3186,7 @@ string CMMCore::getShutterDevice() * Returns the label of the currently selected focus device. * @return focus stage name */ -string CMMCore::getFocusDevice() +std::string CMMCore::getFocusDevice() { std::shared_ptr focus = currentFocusDevice_.lock(); if (focus) @@ -3201,7 +3199,7 @@ string CMMCore::getFocusDevice() /** * Returns the label of the currently selected XYStage device. */ -string CMMCore::getXYStageDevice() +std::string CMMCore::getXYStageDevice() { std::shared_ptr xyStage = currentXYStageDevice_.lock(); if (xyStage) @@ -3214,7 +3212,7 @@ string CMMCore::getXYStageDevice() /** * Returns the label of the currently selected auto-focus device. */ -string CMMCore::getAutoFocusDevice() +std::string CMMCore::getAutoFocusDevice() { std::shared_ptr autofocus = currentAutofocusDevice_.lock(); @@ -3252,7 +3250,7 @@ void CMMCore::setAutoFocusDevice(const char* autofocusLabel) throw (CMMError) /** * Returns the label of the currently selected image processor device. */ -string CMMCore::getImageProcessorDevice() +std::string CMMCore::getImageProcessorDevice() { std::shared_ptr imageProcessor = currentImageProcessor_.lock(); @@ -3267,7 +3265,7 @@ string CMMCore::getImageProcessorDevice() * Returns the label of the currently selected SLM device. * @return slm name */ -string CMMCore::getSLMDevice() +std::string CMMCore::getSLMDevice() { std::shared_ptr slm = currentSLMDevice_.lock(); if (slm) @@ -3281,7 +3279,7 @@ string CMMCore::getSLMDevice() * Returns the label of the currently selected Galvo device. * @return galvo name */ -string CMMCore::getGalvoDevice() +std::string CMMCore::getGalvoDevice() { std::shared_ptr galvos = currentGalvoDevice_.lock(); if (galvos) @@ -3399,7 +3397,7 @@ void CMMCore::setChannelGroup(const char* chGroup) throw (CMMError) /** * Returns the group determining the channel selection. */ -string CMMCore::getChannelGroup() +std::string CMMCore::getChannelGroup() { return channelGroup_; @@ -3547,7 +3545,7 @@ void CMMCore::setCameraDevice(const char* cameraLabel) throw (CMMError) * @return property name array * @param label the device label */ -vector CMMCore::getDevicePropertyNames(const char* label) throw (CMMError) +std::vector CMMCore::getDevicePropertyNames(const char* label) throw (CMMError) { if (IsCoreDeviceLabel(label)) return properties_->GetNames(); @@ -3563,9 +3561,9 @@ vector CMMCore::getDevicePropertyNames(const char* label) throw (CMMErro * Returns an array of labels for currently loaded devices. * @return array of labels */ -vector CMMCore::getLoadedDevices() const +std::vector CMMCore::getLoadedDevices() const { - vector deviceList = deviceManager_->GetDeviceList(); + std::vector deviceList = deviceManager_->GetDeviceList(); deviceList.push_back(MM::g_Keyword_CoreDevice); return deviceList; } @@ -3575,10 +3573,10 @@ vector CMMCore::getLoadedDevices() const * @param devType the device type identifier * @return array of labels */ -vector CMMCore::getLoadedDevicesOfType(MM::DeviceType devType) const +std::vector CMMCore::getLoadedDevicesOfType(MM::DeviceType devType) const { if (devType == MM::CoreDevice) { - vector coreDev; + std::vector coreDev; coreDev.push_back(MM::g_Keyword_CoreDevice); return coreDev; } @@ -3626,7 +3624,7 @@ std::vector CMMCore::getAllowedPropertyValues(const char* label, co * @param label the device label * @param propName the property name */ -string CMMCore::getProperty(const char* label, const char* propName) throw (CMMError) +std::string CMMCore::getProperty(const char* label, const char* propName) throw (CMMError) { if (IsCoreDeviceLabel(label)) return properties_->Get(propName); @@ -3654,7 +3652,7 @@ string CMMCore::getProperty(const char* label, const char* propName) throw (CMME * @param label the device label * @param propName the property name */ -string CMMCore::getPropertyFromCache(const char* label, const char* propName) const throw (CMMError) +std::string CMMCore::getPropertyFromCache(const char* label, const char* propName) const throw (CMMError) { if (IsCoreDeviceLabel(label)) return properties_->Get(propName); @@ -4116,7 +4114,7 @@ unsigned CMMCore::getNumberOfCameraChannels() /** * Returns the name of the requested channel as known by the default camera */ -string CMMCore::getCameraChannelName(unsigned int channelNr) +std::string CMMCore::getCameraChannelName(unsigned int channelNr) { std::shared_ptr camera = currentCameraDevice_.lock(); if (camera) @@ -4661,7 +4659,7 @@ void CMMCore::setStateLabel(const char* deviceLabel, const char* stateLabel) thr * @return the current state's label * @param deviceLabel the device label */ -string CMMCore::getStateLabel(const char* deviceLabel) throw (CMMError) +std::string CMMCore::getStateLabel(const char* deviceLabel) throw (CMMError) { std::shared_ptr pStateDev = deviceManager_->GetDeviceOfType(deviceLabel); @@ -4738,13 +4736,13 @@ void CMMCore::defineStateLabel(const char* deviceLabel, long state, const char* * @return an array of state labels * @param deviceLabel the device label */ -vector CMMCore::getStateLabels(const char* deviceLabel) throw (CMMError) +std::vector CMMCore::getStateLabels(const char* deviceLabel) throw (CMMError) { std::shared_ptr pStateDev = deviceManager_->GetDeviceOfType(deviceLabel); mm::DeviceModuleLockGuard guard(pStateDev); - vector stateLabels; + std::vector stateLabels; for (unsigned i=0; iGetNumberOfPositions(); i++) { stateLabels.push_back(pStateDev->GetPositionLabel(i)); @@ -4995,7 +4993,7 @@ void CMMCore::setPixelSizeConfig(const char* resolutionID) throw (CMMError) CheckConfigPresetName(resolutionID); PixelSizeConfiguration* psc = pixelSizeGroup_->Find(resolutionID); - ostringstream os; + std::ostringstream os; os << resolutionID; if (!psc) { @@ -5027,7 +5025,7 @@ void CMMCore::setConfig(const char* groupName, const char* configName) throw (CM CheckConfigPresetName(configName); Configuration* pCfg = configGroups_->Find(groupName, configName); - ostringstream os; + std::ostringstream os; os << groupName << "/" << configName; if (!pCfg) { @@ -5082,7 +5080,7 @@ void CMMCore::deleteConfig(const char* groupName, const char* configName) throw CheckConfigGroupName(groupName); CheckConfigPresetName(configName); - ostringstream os; + std::ostringstream os; os << groupName << "/" << configName; if (!configGroups_->Delete(groupName, configName)) { logError("deleteConfig", getCoreErrorText(MMERR_NoConfiguration).c_str()); @@ -5107,7 +5105,7 @@ void CMMCore::deleteConfig(const char* groupName, const char* configName, const CheckDeviceLabel(deviceLabel); CheckPropertyName(propName); - ostringstream os; + std::ostringstream os; os << groupName << "/" << configName << "/" << deviceLabel << "/" << propName; if (!configGroups_->Delete(groupName, configName, deviceLabel, propName)) { logError("deleteConfig", getCoreErrorText(MMERR_NoConfiguration).c_str()); @@ -5134,7 +5132,7 @@ void CMMCore::deleteConfig(const char* groupName, const char* configName, const * * @return an array of configuration names */ -vector CMMCore::getAvailableConfigs(const char* group) const +std::vector CMMCore::getAvailableConfigs(const char* group) const { std::vector ret; try @@ -5153,7 +5151,7 @@ vector CMMCore::getAvailableConfigs(const char* group) const * Returns the names of all defined configuration groups * @return an array of names of configuration groups */ -vector CMMCore::getAvailableConfigGroups() const +std::vector CMMCore::getAvailableConfigGroups() const { return configGroups_->GetAvailableGroups(); } @@ -5162,7 +5160,7 @@ vector CMMCore::getAvailableConfigGroups() const * Returns all defined resolution preset names * @return an array of resolution presets */ -vector CMMCore::getAvailablePixelSizeConfigs() const +std::vector CMMCore::getAvailablePixelSizeConfigs() const { return pixelSizeGroup_->GetAvailable(); } @@ -5176,11 +5174,11 @@ vector CMMCore::getAvailablePixelSizeConfigs() const * * @return The current configuration preset's name */ -string CMMCore::getCurrentConfig(const char* groupName) throw (CMMError) +std::string CMMCore::getCurrentConfig(const char* groupName) throw (CMMError) { CheckConfigGroupName(groupName); - vector cfgs = configGroups_->GetAvailableConfigs(groupName); + std::vector cfgs = configGroups_->GetAvailableConfigs(groupName); if (cfgs.empty()) return ""; @@ -5206,11 +5204,11 @@ string CMMCore::getCurrentConfig(const char* groupName) throw (CMMError) * * @return The cache's current configuration preset name */ -string CMMCore::getCurrentConfigFromCache(const char* groupName) throw (CMMError) +std::string CMMCore::getCurrentConfigFromCache(const char* groupName) throw (CMMError) { CheckConfigGroupName(groupName); - vector cfgs = configGroups_->GetAvailableConfigs(groupName); + std::vector cfgs = configGroups_->GetAvailableConfigs(groupName); if (cfgs.empty()) return ""; @@ -5241,7 +5239,7 @@ Configuration CMMCore::getConfigData(const char* groupName, const char* configNa if (!pCfg) { // not found - ostringstream os; + std::ostringstream os; os << groupName << "/" << configName; logError(os.str().c_str(), getCoreErrorText(MMERR_NoConfiguration).c_str()); throw CMMError("Configuration group " + ToQuotedString(groupName) + @@ -5264,7 +5262,7 @@ Configuration CMMCore::getPixelSizeConfigData(const char* configName) throw (CMM if (!pCfg) { // not found - ostringstream os; + std::ostringstream os; os << "Pixel size" << "/" << configName; logError(os.str().c_str(), getCoreErrorText(MMERR_NoConfiguration).c_str()); throw CMMError("Pixel size configuration preset " + ToQuotedString(configName) + @@ -5318,7 +5316,7 @@ void CMMCore::deletePixelSizeConfig(const char* configName) throw (CMMError) /** * Get the current pixel configuration name **/ -string CMMCore::getCurrentPixelSizeConfig() throw (CMMError) +std::string CMMCore::getCurrentPixelSizeConfig() throw (CMMError) { return getCurrentPixelSizeConfig(false); } @@ -5326,10 +5324,10 @@ string CMMCore::getCurrentPixelSizeConfig() throw (CMMError) /** * Get the current pixel configuration name **/ -string CMMCore::getCurrentPixelSizeConfig(bool cached) throw (CMMError) +std::string CMMCore::getCurrentPixelSizeConfig(bool cached) throw (CMMError) { // get a list of configuration names - vector cfgs = pixelSizeGroup_->GetAvailable(); + std::vector cfgs = pixelSizeGroup_->GetAvailable(); if (cfgs.empty()) return ""; @@ -5346,7 +5344,7 @@ string CMMCore::getCurrentPixelSizeConfig(bool cached) throw (CMMError) { try { - string value; + std::string value; if (!cached) { value = getProperty(cs.getDeviceLabel().c_str(), cs.getPropertyName().c_str()); @@ -5553,7 +5551,7 @@ std::vector CMMCore::getPixelSizeAffineByID(const char* resolutionID) th double CMMCore::getMagnificationFactor() const { double magnification = 1.0; - vector magnifiers = getLoadedDevicesOfType(MM::MagnifierDevice); + std::vector magnifiers = getLoadedDevicesOfType(MM::MagnifierDevice); for (size_t i=0; i magnifier = @@ -5655,12 +5653,12 @@ std::string CMMCore::getSerialPortAnswer(const char* portLabel, const char* term int ret = pSerial->GetAnswer(answerBuf, bufLen, term); if (ret != DEVICE_OK) { - string errText = getDeviceErrorText(ret, pSerial).c_str(); + std::string errText = getDeviceErrorText(ret, pSerial).c_str(); logError(portLabel, errText.c_str()); throw CMMError(errText); } - return string(answerBuf); + return std::string(answerBuf); } /** @@ -5682,7 +5680,7 @@ void CMMCore::writeToSerialPort(const char* portLabel, const std::vector & /** * Reads the contents of the Rx buffer. */ -vector CMMCore::readFromSerialPort(const char* portLabel) throw (CMMError) +std::vector CMMCore::readFromSerialPort(const char* portLabel) throw (CMMError) { std::shared_ptr pSerial = deviceManager_->GetDeviceOfType(portLabel); @@ -5697,7 +5695,7 @@ vector CMMCore::readFromSerialPort(const char* portLabel) throw (CMMError) throw CMMError(getDeviceErrorText(ret, pSerial)); } - vector data; + std::vector data; data.resize(read, 0); if (read > 0) std::memcpy(&(data[0]), answerBuf, read); @@ -6228,7 +6226,7 @@ void CMMCore::runGalvoSequence(const char* deviceLabel) throw (CMMError) /** * Get the name of the active galvo channel (for a multi-laser galvo device). */ -string CMMCore::getGalvoChannel(const char* deviceLabel) throw (CMMError) +std::string CMMCore::getGalvoChannel(const char* deviceLabel) throw (CMMError) { std::shared_ptr pGalvo = deviceManager_->GetDeviceOfType(deviceLabel); @@ -6250,8 +6248,8 @@ void CMMCore::saveSystemState(const char* fileName) throw (CMMError) if (!fileName) throw CMMError("Null filename"); - ofstream os; - os.open(fileName, ios_base::out | ios_base::trunc); + std::ofstream os; + os.open(fileName, std::ios_base::out | std::ios_base::trunc); if (!os.is_open()) { logError(fileName, getCoreErrorText(MMERR_FileOpenFailed).c_str()); @@ -6267,7 +6265,7 @@ void CMMCore::saveSystemState(const char* fileName) throw (CMMError) if (!isPropertyReadOnly(s.getDeviceLabel().c_str(), s.getPropertyName().c_str())) { os << MM::g_CFGCommand_Property << ',' << s.getDeviceLabel() - << ',' << s.getPropertyName() << ',' << s.getPropertyValue() << endl; + << ',' << s.getPropertyName() << ',' << s.getPropertyValue() << '\n'; } } } @@ -6284,8 +6282,8 @@ void CMMCore::loadSystemState(const char* fileName) throw (CMMError) if (!fileName) throw CMMError("Null filename"); - ifstream is; - is.open(fileName, ios_base::in); + std::ifstream is; + is.open(fileName, std::ios_base::in); if (!is.is_open()) { logError(fileName, getCoreErrorText(MMERR_FileOpenFailed).c_str()); @@ -6296,11 +6294,11 @@ void CMMCore::loadSystemState(const char* fileName) throw (CMMError) // Process commands const int maxLineLength = 4 * MM::MaxStrLength + 4; // accommodate up to 4 strings and delimiters char line[maxLineLength+1]; - vector tokens; + std::vector tokens; while(is.getline(line, maxLineLength, '\n')) { // strip a potential Windows/dos CR - istringstream il(line); + std::istringstream il(line); il.getline(line, maxLineLength, '\r'); if (strlen(line) > 0) { @@ -6357,8 +6355,8 @@ void CMMCore::saveSystemConfiguration(const char* fileName) throw (CMMError) if (!fileName) throw CMMError("Null filename"); - ofstream os; - os.open(fileName, ios_base::out | ios_base::trunc); + std::ofstream os; + os.open(fileName, std::ios_base::out | std::ios_base::trunc); if (!os.is_open()) { logError(fileName, getCoreErrorText(MMERR_FileOpenFailed).c_str()); @@ -6368,22 +6366,22 @@ void CMMCore::saveSystemConfiguration(const char* fileName) throw (CMMError) // insert the system reset command // this will unload all current devices - os << "# Unload all devices" << endl; - os << "Property,Core,Initialize,0" << endl; + os << "# Unload all devices\n"; + os << "Property,Core,Initialize,0\n"; // save device list - os << "# Load devices" << endl; - vector devices = deviceManager_->GetDeviceList(); - vector::const_iterator it; + os << "# Load devices\n"; + std::vector devices = deviceManager_->GetDeviceList(); + std::vector::const_iterator it; for (it=devices.begin(); it != devices.end(); it++) { std::shared_ptr pDev = deviceManager_->GetDevice(*it); mm::DeviceModuleLockGuard guard(pDev); - os << MM::g_CFGCommand_Device << "," << *it << "," << pDev->GetAdapterModule()->GetName() << "," << pDev->GetName() << endl; + os << MM::g_CFGCommand_Device << "," << *it << "," << pDev->GetAdapterModule()->GetName() << "," << pDev->GetName() << '\n'; } // save the pre-initialization properties - os << "# Pre-initialization properties" << endl; + os << "# Pre-initialization properties\n"; Configuration config = getSystemState(); for (size_t i=0; i device = deviceManager_->GetDevice(*it); @@ -6414,22 +6412,22 @@ void CMMCore::saveSystemConfiguration(const char* fileName) throw (CMMError) std::string parentID = device->GetParentID(); if (!parentID.empty()) { - os << MM::g_CFGCommand_ParentID << ',' << device->GetLabel() << ',' << parentID << endl; + os << MM::g_CFGCommand_ParentID << ',' << device->GetLabel() << ',' << parentID << '\n'; } } // insert the initialize command - os << "Property,Core,Initialize,1" << endl; + os << "Property,Core,Initialize,1\n"; // save delays - os << "# Delays" << endl; + os << "# Delays\n"; for (it=devices.begin(); it != devices.end(); it++) { std::shared_ptr pDev = deviceManager_->GetDevice(*it); mm::DeviceModuleLockGuard guard(pDev); if (pDev->GetDelayMs() > 0.0) - os << MM::g_CFGCommand_Delay << "," << *it << "," << pDev->GetDelayMs() << endl; + os << MM::g_CFGCommand_Delay << "," << *it << "," << pDev->GetDelayMs() << '\n'; } // save focus directions @@ -6448,8 +6446,8 @@ void CMMCore::saveSystemConfiguration(const char* fileName) throw (CMMError) } // save labels - os << "# Labels" << endl; - vector deviceLabels = deviceManager_->GetDeviceList(MM::StateDevice); + os << "# Labels\n"; + std::vector deviceLabels = deviceManager_->GetDeviceList(MM::StateDevice); for (size_t i=0; i pSD = @@ -6470,20 +6468,20 @@ void CMMCore::saveSystemConfiguration(const char* fileName) throw (CMMError) } if (!stateLabel.empty()) { - os << MM::g_CFGCommand_Label << ',' << deviceLabels[i] << ',' << j << ',' << stateLabel << endl; + os << MM::g_CFGCommand_Label << ',' << deviceLabels[i] << ',' << j << ',' << stateLabel << '\n'; } } } // save configuration groups - os << "# Group configurations" << endl; - vector groups = getAvailableConfigGroups(); + os << "# Group configurations\n"; + std::vector groups = getAvailableConfigGroups(); for (size_t i=0; i configs = getAvailableConfigs(groups[i].c_str()); + std::vector configs = getAvailableConfigs(groups[i].c_str()); if (configs.size() == 0) - os << MM::g_CFGCommand_ConfigGroup << ',' << groups[i] << endl; + os << MM::g_CFGCommand_ConfigGroup << ',' << groups[i] << '\n'; // normal group records for (size_t j=0; j camera = currentCameraDevice_.lock(); if (camera) { - os << MM::g_CFGCommand_Property << ',' << MM::g_Keyword_CoreDevice << ',' << MM::g_Keyword_CoreCamera << ',' << camera->GetLabel() << endl; + os << MM::g_CFGCommand_Property << ',' << MM::g_Keyword_CoreDevice << ',' << MM::g_Keyword_CoreCamera << ',' << camera->GetLabel() << '\n'; } std::shared_ptr shutter = currentShutterDevice_.lock(); if (shutter) { - os << MM::g_CFGCommand_Property << ',' << MM::g_Keyword_CoreDevice << ',' << MM::g_Keyword_CoreShutter << ',' << shutter->GetLabel() << endl; + os << MM::g_CFGCommand_Property << ',' << MM::g_Keyword_CoreDevice << ',' << MM::g_Keyword_CoreShutter << ',' << shutter->GetLabel() << '\n'; } std::shared_ptr focus = currentFocusDevice_.lock(); if (focus) { - os << MM::g_CFGCommand_Property << ',' << MM::g_Keyword_CoreDevice << ',' << MM::g_Keyword_CoreFocus << ',' << focus->GetLabel() << endl; + os << MM::g_CFGCommand_Property << ',' << MM::g_Keyword_CoreDevice << ',' << MM::g_Keyword_CoreFocus << ',' << focus->GetLabel() << '\n'; } } @@ -6575,8 +6573,8 @@ void CMMCore::loadSystemConfigurationImpl(const char* fileName) throw (CMMError) if (!fileName) throw CMMError("Null filename"); - ifstream is; - is.open(fileName, ios_base::in); + std::ifstream is; + is.open(fileName, std::ios_base::in); if (!is.is_open()) { logError(fileName, getCoreErrorText(MMERR_FileOpenFailed).c_str()); @@ -6587,14 +6585,14 @@ void CMMCore::loadSystemConfigurationImpl(const char* fileName) throw (CMMError) // Process commands const int maxLineLength = 4 * MM::MaxStrLength + 4; // accommodate up to 4 strings and delimiters char line[maxLineLength+1]; - vector tokens; + std::vector tokens; int lineCount = 0; while(is.getline(line, maxLineLength, '\n')) { // strip a potential Windows/dos CR - istringstream il(line); + std::istringstream il(line); il.getline(line, maxLineLength, '\r'); lineCount++; @@ -6776,8 +6774,8 @@ void CMMCore::loadSystemConfigurationImpl(const char* fileName) throw (CMMError) if (externalCallback_) externalCallback_->onSystemConfigurationLoaded(); std::ostringstream errorText; - errorText << "Line " << lineCount << ": " << line << endl; - errorText << err.getFullMsg() << endl << endl; + errorText << "Line " << lineCount << ": " << line << '\n'; + errorText << err.getFullMsg() << "\n\n"; throw CMMError(errorText.str().c_str(), MMERR_InvalidConfigurationFile); } } @@ -7259,7 +7257,7 @@ void CMMCore::applyConfiguration(const Configuration& config) throw (CMMError) { std::ostringstream sall; bool error = false; - vector failedProps; + std::vector failedProps; for (size_t i=0; i (unsigned) applyProperties(failedProps, errorString) ) { if (failedProps.size() == 0) @@ -7316,10 +7314,10 @@ void CMMCore::applyConfiguration(const Configuration& config) throw (CMMError) * properties until there are none left or none succeed * returns number of properties successfully set */ -int CMMCore::applyProperties(vector& props, string& lastError) +int CMMCore::applyProperties(std::vector& props, std::string& lastError) { // int succeeded = 0; - vector failedProps; + std::vector failedProps; for (size_t i=0; i& props, string& lastError) -string CMMCore::getDeviceErrorText(int deviceCode, std::shared_ptr device) +std::string CMMCore::getDeviceErrorText(int deviceCode, std::shared_ptr device) { if (!device) { @@ -7366,11 +7364,11 @@ string CMMCore::getDeviceErrorText(int deviceCode, std::shared_ptr::const_iterator it; + std::string txt; + std::map::const_iterator it; it = errorText_.find(code); if (it != errorText_.end()) txt = it->second; @@ -7384,7 +7382,7 @@ void CMMCore::logError(const char* device, const char* msg) LOG_ERROR(coreLogger_) << "Error occurred in device " << device << ": " << msg; } -string CMMCore::getDeviceName(std::shared_ptr pDev) +std::string CMMCore::getDeviceName(std::shared_ptr pDev) { mm::DeviceModuleLockGuard guard(pDev); return pDev->GetName(); From e28383c56f01eaefdbf43877e8e8ea8e0c47c32f Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Fri, 8 Dec 2023 17:05:43 -0600 Subject: [PATCH 084/141] MMCore: Use #pragma once consistently --- MMCore/ConfigGroup.h | 10 ++-------- MMCore/Configuration.h | 8 ++------ MMCore/CoreCallback.h | 5 +---- MMCore/CoreProperty.h | 9 ++------- MMCore/Error.h | 5 +---- MMCore/MMCore.h | 7 ++----- MMCore/PluginManager.h | 6 +----- 7 files changed, 11 insertions(+), 39 deletions(-) diff --git a/MMCore/ConfigGroup.h b/MMCore/ConfigGroup.h index 0a8d705ed..050020815 100644 --- a/MMCore/ConfigGroup.h +++ b/MMCore/ConfigGroup.h @@ -19,11 +19,8 @@ // IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -// -// CVS: $Id: ConfigGroup.h 17248 2020-01-25 19:49:51Z nico $ -// -#ifndef _CONFIG_GROUP_H_ -#define _CONFIG_GROUP_H_ + +#pragma once #include "Configuration.h" #include "Error.h" @@ -439,6 +436,3 @@ class PixelSizeConfigGroup : public ConfigGroupBase } } }; - -#endif - diff --git a/MMCore/Configuration.h b/MMCore/Configuration.h index 133321259..ea171bc6c 100644 --- a/MMCore/Configuration.h +++ b/MMCore/Configuration.h @@ -17,10 +17,8 @@ // IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -// CVS: $Id: Configuration.h 16305 2017-02-11 05:01:12Z mark $ -// -#ifndef _CONFIGURATION_H_ -#define _CONFIGURATION_H_ + +#pragma once #ifdef WIN32 // disable exception scpecification warnings in MSVC @@ -120,5 +118,3 @@ class Configuration std::vector settings_; std::map index_; }; - -#endif //_CONFIGURATION_H_ diff --git a/MMCore/CoreCallback.h b/MMCore/CoreCallback.h index 29be56f9e..275493b42 100644 --- a/MMCore/CoreCallback.h +++ b/MMCore/CoreCallback.h @@ -22,8 +22,7 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -#ifndef _CORECALLBACK_H_ -#define _CORECALLBACK_H_ +#pragma once #include "Devices/DeviceInstances.h" #include "CoreUtils.h" @@ -147,5 +146,3 @@ class CoreCallback : public MM::Core int OnPixelSizeChanged(double newPixelSizeUm); int OnPixelSizeAffineChanged(std::vector newPixelSizeAffine); }; - -#endif // _CORECALLBACK_H_ diff --git a/MMCore/CoreProperty.h b/MMCore/CoreProperty.h index 1b3306539..07ddb7af7 100644 --- a/MMCore/CoreProperty.h +++ b/MMCore/CoreProperty.h @@ -21,10 +21,8 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. // -// CVS: $Id: CoreProperty.h 8004 2011-10-27 04:58:53Z nico $ -// -#ifndef _CORE_PROPERTY_H -#define _CORE_PROPERTY_H + +#pragma once #include #include @@ -83,6 +81,3 @@ class CorePropertyCollection CMMCore* core_; std::map properties_; }; - -#endif - diff --git a/MMCore/Error.h b/MMCore/Error.h index 9a2e835ad..00cc017ec 100644 --- a/MMCore/Error.h +++ b/MMCore/Error.h @@ -22,8 +22,7 @@ // AUTHOR: Nenad Amodaj, nenad@amodaj.com, 09/19/2005 // Mark Tsuchida, 12/04/2013 (made chainable) -#ifndef _ERROR_H_ -#define _ERROR_H_ +#pragma once #include "ErrorCodes.h" @@ -158,5 +157,3 @@ class CMMError : public std::exception Code code_; std::unique_ptr underlying_; }; - -#endif //_ERROR_H_ diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 79b2af386..6264bda8f 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -36,6 +36,8 @@ // because public method names appear as wrapped methods in other // languages, in particular Java. +#pragma once + /* * Important! Read this before changing this file. * @@ -43,9 +45,6 @@ * file (MMCore.cpp). */ -#ifndef _MMCORE_H_ -#define _MMCORE_H_ - #ifdef _MSC_VER // We use exception specifications to instruct SWIG to generate the correct // exception specifications for Java. Turn off the warnings that VC++ issues by @@ -693,5 +692,3 @@ class CMMCore void updateCoreProperty(const char* propName, MM::DeviceType devType) throw (CMMError); void loadSystemConfigurationImpl(const char* fileName) throw (CMMError); }; - -#endif //_MMCORE_H_ diff --git a/MMCore/PluginManager.h b/MMCore/PluginManager.h index aa3b1aa45..d61ad0686 100644 --- a/MMCore/PluginManager.h +++ b/MMCore/PluginManager.h @@ -20,9 +20,7 @@ // // AUTHOR: Nenad Amodaj, nenad@amodaj.com, 08/10/2005 -#ifndef _PLUGIN_MANAGER_H_ -#define _PLUGIN_MANAGER_H_ - +#pragma once #include "../MMDevice/DeviceThreads.h" @@ -66,5 +64,3 @@ class CPluginManager /* final */ std::map< std::string, std::shared_ptr > moduleMap_; }; - -#endif //_PLUGIN_MANAGER_H_ From 948375890fbf6f4ec8037f854ccede910e0e8dca Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Fri, 8 Dec 2023 17:10:17 -0600 Subject: [PATCH 085/141] DemoCamera: Avoid 'using namespace std' --- DeviceAdapters/DemoCamera/DemoCamera.cpp | 17 +++++++---------- DeviceAdapters/DemoCamera/DemoCamera.h | 7 +------ DeviceAdapters/DemoCamera/WriteCompactTiffRGB.h | 5 +---- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/DeviceAdapters/DemoCamera/DemoCamera.cpp b/DeviceAdapters/DemoCamera/DemoCamera.cpp index d5dd386c0..4684fc74b 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.cpp +++ b/DeviceAdapters/DemoCamera/DemoCamera.cpp @@ -33,9 +33,6 @@ #include #include - - -using namespace std; const double CDemoCamera::nominalPixelSizeUm_ = 1.0; double g_IntensityFactor_ = 1.0; @@ -346,7 +343,7 @@ int CDemoCamera::Initialize() nRet = CreateStringProperty(MM::g_Keyword_PixelType, g_PixelType_8bit, false, pAct); assert(nRet == DEVICE_OK); - vector pixelTypeValues; + std::vector pixelTypeValues; pixelTypeValues.push_back(g_PixelType_8bit); pixelTypeValues.push_back(g_PixelType_16bit); pixelTypeValues.push_back(g_PixelType_32bitRGB); @@ -362,7 +359,7 @@ int CDemoCamera::Initialize() nRet = CreateIntegerProperty("BitDepth", 8, false, pAct); assert(nRet == DEVICE_OK); - vector bitDepths; + std::vector bitDepths; bitDepths.push_back("8"); bitDepths.push_back("10"); bitDepths.push_back("11"); @@ -1021,7 +1018,7 @@ int CDemoCamera::SendExposureSequence() const { int CDemoCamera::SetAllowedBinning() { - vector binValues; + std::vector binValues; binValues.push_back("1"); binValues.push_back("2"); if (scanMode_ < 3) @@ -1400,7 +1397,7 @@ int CDemoCamera::OnPixelType(MM::PropertyBase* pProp, MM::ActionType eAct) if(IsCapturing()) return DEVICE_CAMERA_BUSY_ACQUIRING; - string pixelType; + std::string pixelType; pProp->Get(pixelType); if (pixelType.compare(g_PixelType_8bit) == 0) @@ -3991,8 +3988,8 @@ gatedVolts_(0), open_(true), sequenceRunning_(false), sequenceIndex_(0), -sentSequence_(vector()), -nascentSequence_(vector()) +sentSequence_(std::vector()), +nascentSequence_(std::vector()) { SetErrorText(ERR_SEQUENCE_INACTIVE, "Sequence triggered, but sequence is not running"); @@ -4063,7 +4060,7 @@ int DemoDA::SetSignal(double volts) volt_ = volts; if (open_) gatedVolts_ = volts; - stringstream s; + std::stringstream s; s << "Voltage set to " << volts; LogMessage(s.str(), false); return DEVICE_OK; diff --git a/DeviceAdapters/DemoCamera/DemoCamera.h b/DeviceAdapters/DemoCamera/DemoCamera.h index acd3df13a..881db22f7 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.h +++ b/DeviceAdapters/DemoCamera/DemoCamera.h @@ -27,8 +27,7 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -#ifndef _DEMOCAMERA_H_ -#define _DEMOCAMERA_H_ +#pragma once #include "DeviceBase.h" #include "ImgBuffer.h" @@ -1193,7 +1192,3 @@ class DemoGalvo : public CGalvoBase, ImgManipulator int offsetY_; double vMaxY_; }; - - - -#endif //_DEMOCAMERA_H_ diff --git a/DeviceAdapters/DemoCamera/WriteCompactTiffRGB.h b/DeviceAdapters/DemoCamera/WriteCompactTiffRGB.h index 71dd7bfec..0c676e965 100644 --- a/DeviceAdapters/DemoCamera/WriteCompactTiffRGB.h +++ b/DeviceAdapters/DemoCamera/WriteCompactTiffRGB.h @@ -1,3 +1,4 @@ +#pragma once #include "stdio.h" #include "math.h" @@ -240,7 +241,3 @@ void GenerateRGBTestImage( const int nx, const int ny, const char color, unsigne } } } - - - - From 2eebada3245732cb6261803f53d438d321a3e652 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 11 Dec 2023 15:46:14 -0600 Subject: [PATCH 086/141] DemoCamera: Avoid Windows.h's min/max macros --- DeviceAdapters/DemoCamera/DemoCamera.cpp | 20 ++++++++++---------- DeviceAdapters/DemoCamera/DemoCamera.vcxproj | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/DeviceAdapters/DemoCamera/DemoCamera.cpp b/DeviceAdapters/DemoCamera/DemoCamera.cpp index 4684fc74b..537ef041c 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.cpp +++ b/DeviceAdapters/DemoCamera/DemoCamera.cpp @@ -2136,7 +2136,7 @@ void CDemoCamera::GenerateSyntheticImage(ImgBuffer& img, double exp) for (k=0; k maxDrawnVal) { maxDrawnVal = val; } @@ -2169,7 +2169,7 @@ void CDemoCamera::GenerateSyntheticImage(ImgBuffer& img, double exp) for (k=0; k maxDrawnVal) { maxDrawnVal = val; } @@ -2204,7 +2204,7 @@ void CDemoCamera::GenerateSyntheticImage(ImgBuffer& img, double exp) for (k=0; k maxDrawnVal) { maxDrawnVal = value; } @@ -2273,15 +2273,15 @@ void CDemoCamera::GenerateSyntheticImage(ImgBuffer& img, double exp) { long lIndex = imgWidth*j + k; double factor = (2.0 * lSinePeriod * k) / lPeriod; - unsigned char value0 = (unsigned char) min(255.0, (pedestal + dAmp * sin(dPhase_ + dLinePhase + factor))); + unsigned char value0 = (unsigned char) std::min(255.0, (pedestal + dAmp * sin(dPhase_ + dLinePhase + factor))); theBytes[0] = value0; if( NULL != pTmpBuffer) pTmp2[1] = value0; - unsigned char value1 = (unsigned char) min(255.0, (pedestal + dAmp * sin(dPhase_ + dLinePhase*2 + factor))); + unsigned char value1 = (unsigned char) std::min(255.0, (pedestal + dAmp * sin(dPhase_ + dLinePhase*2 + factor))); theBytes[1] = value1; if( NULL != pTmpBuffer) pTmp2[2] = value1; - unsigned char value2 = (unsigned char) min(255.0, (pedestal + dAmp * sin(dPhase_ + dLinePhase*4 + factor))); + unsigned char value2 = (unsigned char) std::min(255.0, (pedestal + dAmp * sin(dPhase_ + dLinePhase*4 + factor))); theBytes[2] = value2; if( NULL != pTmpBuffer){ @@ -2325,9 +2325,9 @@ void CDemoCamera::GenerateSyntheticImage(ImgBuffer& img, double exp) for (k=0; k maxDrawnVal) { maxDrawnVal = static_cast(tval); @@ -3470,7 +3470,7 @@ int CDemoStage::SetPositionUm(double pos) void CDemoStage::SetIntensityFactor(double pos) { pos = fabs(pos); - g_IntensityFactor_ = max(.1, min(1.0, 1.0 - .2 * log(pos))); + g_IntensityFactor_ = std::max(.1, std::min(1.0, 1.0 - .2 * log(pos))); } int CDemoStage::IsStageSequenceable(bool& isSequenceable) const diff --git a/DeviceAdapters/DemoCamera/DemoCamera.vcxproj b/DeviceAdapters/DemoCamera/DemoCamera.vcxproj index 754e069f4..9c38b333f 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.vcxproj +++ b/DeviceAdapters/DemoCamera/DemoCamera.vcxproj @@ -56,7 +56,7 @@ Disabled true Speed - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + NOMINMAX;WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) EnableFastChecks true @@ -77,7 +77,7 @@ MaxSpeed true Speed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + NOMINMAX;WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) true From 8a295f565547ea4cab93d740a29676035944b879 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 11 Dec 2023 16:10:14 -0600 Subject: [PATCH 087/141] MMCore: Avoid Windows.h's min/max macros --- MMCore/CoreCallback.cpp | 4 ---- MMCore/MMCore.vcxproj | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index b0e98fe69..471313c24 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -1031,11 +1031,7 @@ void CoreCallback::NextPostedError(int& errorCode, char* pMessage, int maxlen, i if( 0 < maxlen ) { *pMessage = 0; -#ifdef _WINDOWS - messageLength = min( maxlen, (int) nextError.second.length()); -#else messageLength = std::min( maxlen, (int) nextError.second.length()); -#endif strncpy(pMessage, nextError.second.c_str(), messageLength); } } diff --git a/MMCore/MMCore.vcxproj b/MMCore/MMCore.vcxproj index d5e4c2f28..26d732feb 100644 --- a/MMCore/MMCore.vcxproj +++ b/MMCore/MMCore.vcxproj @@ -50,7 +50,7 @@ Disabled - WIN32;_DEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + NOMINMAX;WIN32;_DEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -66,7 +66,7 @@ X64 - WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + NOMINMAX;WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) true @@ -183,4 +183,4 @@ - + \ No newline at end of file From d5917378226688c6a582bd1d2146444758461a99 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 14:27:41 -0600 Subject: [PATCH 088/141] MMDevice/MMCore: Cleanly disable MSVC warnings Always predicate on _MSC_VER and use push/pop. --- MMCore/CircularBuffer.h | 7 ++++++- MMCore/Configuration.h | 10 +++++++--- MMCore/MMCore.h | 12 ++++++++---- MMDevice/ImageMetadata.h | 12 ++++++++---- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/MMCore/CircularBuffer.h b/MMCore/CircularBuffer.h index c456ec008..d42ea4a47 100644 --- a/MMCore/CircularBuffer.h +++ b/MMCore/CircularBuffer.h @@ -36,7 +36,8 @@ #include #ifdef _MSC_VER -#pragma warning( disable : 4290 ) // exception declaration warning +#pragma warning(push) +#pragma warning(disable: 4290) // 'C++ exception specification ignored' #endif @@ -99,3 +100,7 @@ class CircularBuffer std::shared_ptr threadPool_; std::shared_ptr tasksMemCopy_; }; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/MMCore/Configuration.h b/MMCore/Configuration.h index ea171bc6c..4cd4a6ce7 100644 --- a/MMCore/Configuration.h +++ b/MMCore/Configuration.h @@ -20,9 +20,9 @@ #pragma once -#ifdef WIN32 -// disable exception scpecification warnings in MSVC -#pragma warning( disable : 4290 ) +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4290) // 'C++ exception specification ignored' #endif #include @@ -118,3 +118,7 @@ class Configuration std::vector settings_; std::map index_; }; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 6264bda8f..d05411cec 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -45,11 +45,11 @@ * file (MMCore.cpp). */ -#ifdef _MSC_VER // We use exception specifications to instruct SWIG to generate the correct -// exception specifications for Java. Turn off the warnings that VC++ issues by -// the mere use of exception specifications (which VC++ does not implement). -#pragma warning(disable : 4290) +// exception specifications for Java. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4290) // 'C++ exception specification ignored' #endif #include "../MMDevice/DeviceThreads.h" @@ -692,3 +692,7 @@ class CMMCore void updateCoreProperty(const char* propName, MM::DeviceType devType) throw (CMMError); void loadSystemConfigurationImpl(const char* fileName) throw (CMMError); }; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/MMDevice/ImageMetadata.h b/MMDevice/ImageMetadata.h index 73f375e92..49623bd28 100644 --- a/MMDevice/ImageMetadata.h +++ b/MMDevice/ImageMetadata.h @@ -22,9 +22,9 @@ #pragma once -#ifdef WIN32 -// disable exception scpecification warnings in MSVC -#pragma warning( disable : 4290 ) +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4290) // 'C++ exception specification ignored' #endif #include "MMDeviceConstants.h" @@ -492,4 +492,8 @@ class Metadata std::map tags_; typedef std::map::iterator TagIter; typedef std::map::const_iterator TagConstIter; -}; \ No newline at end of file +}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif \ No newline at end of file From 4536cc099caa4793fadacbea3dcc694de679f014 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 14:28:23 -0600 Subject: [PATCH 089/141] MMDevice/MMCore: Use _WIN32 consistently WIN32 is only defined in the .vcxproj (albeit by default), so avoid. _WIN32 is defined by the compiler toolchain and is always available. --- MMCore/LibraryInfo/LibraryPathsWindows.cpp | 4 ++-- MMCore/LoadableModules/LoadedModuleImpl.cpp | 2 +- MMCore/PluginManager.cpp | 12 ++++++------ MMDevice/DeviceThreads.h | 2 +- MMDevice/DeviceUtils.cpp | 6 +++--- MMDevice/MMDevice.h | 2 +- MMDevice/ModuleInterface.h | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/MMCore/LibraryInfo/LibraryPathsWindows.cpp b/MMCore/LibraryInfo/LibraryPathsWindows.cpp index efa49f95f..b76f52526 100644 --- a/MMCore/LibraryInfo/LibraryPathsWindows.cpp +++ b/MMCore/LibraryInfo/LibraryPathsWindows.cpp @@ -20,7 +20,7 @@ // Note: This code must reside in the same binary image as the rest of the // Core. -#ifdef WIN32 // whole file +#ifdef _WIN32 // whole file #include "LibraryPaths.h" @@ -87,4 +87,4 @@ std::string GetPathOfThisModule() } // namespace MMCorePrivate -#endif // WIN32 +#endif // _WIN32 diff --git a/MMCore/LoadableModules/LoadedModuleImpl.cpp b/MMCore/LoadableModules/LoadedModuleImpl.cpp index 0ea239d8b..a30edf8c1 100644 --- a/MMCore/LoadableModules/LoadedModuleImpl.cpp +++ b/MMCore/LoadableModules/LoadedModuleImpl.cpp @@ -22,7 +22,7 @@ #include "LoadedModuleImpl.h" -#ifdef WIN32 +#ifdef _WIN32 # include "LoadedModuleImplWindows.h" typedef LoadedModuleImplWindows PlatformLoadedModuleImpl; #else diff --git a/MMCore/PluginManager.cpp b/MMCore/PluginManager.cpp index 125057e07..c459a97e8 100644 --- a/MMCore/PluginManager.cpp +++ b/MMCore/PluginManager.cpp @@ -20,14 +20,14 @@ // // AUTHOR: Nenad Amodaj, nenad@amodaj.com, 08/10/2005 -#ifdef WIN32 +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #else #include #include -#endif // WIN32 +#endif // _WIN32 #include "../MMDevice/ModuleInterface.h" #include "CoreUtils.h" @@ -47,7 +47,7 @@ #include -#ifdef WIN32 +#ifdef _WIN32 const char* const LIB_NAME_PREFIX = "mmgr_dal_"; #else const char* const LIB_NAME_PREFIX = "libmmgr_dal_"; @@ -86,7 +86,7 @@ CPluginManager::FindInSearchPath(std::string filename) { for (const auto& p : searchPaths_) { std::string path = p; - #ifdef WIN32 + #ifdef _WIN32 path += "\\" + filename + ".dll"; #else path += "/" + filename; @@ -183,7 +183,7 @@ CPluginManager::UnloadPluginLibrary(const char* moduleName) static std::string GetDirName(const std::string& path) { -#ifdef WIN32 +#ifdef _WIN32 const char* pathSep = "\\/"; #else const char* pathSep = "/"; @@ -232,7 +232,7 @@ void CPluginManager::GetModules(std::vector &modules, const char* searchPath) { -#ifdef WIN32 +#ifdef _WIN32 std::string path = searchPath; path += "\\"; path += LIB_NAME_PREFIX; diff --git a/MMDevice/DeviceThreads.h b/MMDevice/DeviceThreads.h index e0aa9c7ef..0986f324b 100644 --- a/MMDevice/DeviceThreads.h +++ b/MMDevice/DeviceThreads.h @@ -20,7 +20,7 @@ #pragma once -#ifdef WIN32 +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #else diff --git a/MMDevice/DeviceUtils.cpp b/MMDevice/DeviceUtils.cpp index fe5162a8d..c2624c4dd 100644 --- a/MMDevice/DeviceUtils.cpp +++ b/MMDevice/DeviceUtils.cpp @@ -24,7 +24,7 @@ #include #include -#ifdef WIN32 +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #else @@ -151,7 +151,7 @@ void CDeviceUtils::Tokenize(const std::string& str, std::vector& to */ void CDeviceUtils::SleepMs(long periodMs) { -#ifdef WIN32 +#ifdef _WIN32 Sleep(periodMs); #else usleep(periodMs * 1000); @@ -163,7 +163,7 @@ void CDeviceUtils::SleepMs(long periodMs) */ void CDeviceUtils::NapMicros(unsigned long period) { -#ifdef WIN32 +#ifdef _WIN32 Sleep(period/1000); #else usleep(period); diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index e4be5ebe3..ae95e6ec6 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -65,7 +65,7 @@ # define MM_DEPRECATED(prototype) prototype #endif -#ifdef WIN32 +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include diff --git a/MMDevice/ModuleInterface.h b/MMDevice/ModuleInterface.h index 5faef75f6..4aa5f2301 100644 --- a/MMDevice/ModuleInterface.h +++ b/MMDevice/ModuleInterface.h @@ -29,7 +29,7 @@ #include "MMDevice.h" -#ifdef WIN32 +#ifdef _WIN32 #ifdef MODULE_EXPORTS #define MODULE_API __declspec(dllexport) #else From d2128b407b9403d05a8a8617bf11899d748d93d5 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 13:13:33 -0600 Subject: [PATCH 090/141] MMDevice: Remove an implicit conversion from test --- MMDevice/unittest/MMTime-Tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MMDevice/unittest/MMTime-Tests.cpp b/MMDevice/unittest/MMTime-Tests.cpp index 58277937a..e31fc522a 100644 --- a/MMDevice/unittest/MMTime-Tests.cpp +++ b/MMDevice/unittest/MMTime-Tests.cpp @@ -56,8 +56,8 @@ TEST(MMTimeTests, Comparison) TEST(MMTimeTests, FromNumbers) { - ASSERT_EQ(MMTime(1.0), MMTime::fromUs(1.0)); - ASSERT_EQ(MMTime(1'000.0), MMTime::fromMs(1.0)); + ASSERT_EQ(MMTime(1.0), MMTime::fromUs(1)); + ASSERT_EQ(MMTime(1'000.0), MMTime::fromMs(1)); ASSERT_EQ(MMTime(1'000'000.0), MMTime::fromSeconds(1)); } From c6192ce864151700f64417e03c19179ec1590afc Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 15:11:04 -0600 Subject: [PATCH 091/141] MMCore: Remove need for platform-conditional build Use preprocessor to choose platform-specific code rather than requiring the build system to selectively include files in the build. --- MMCore/LoadableModules/LoadedModuleImplUnix.cpp | 4 ++++ MMCore/LoadableModules/LoadedModuleImplUnix.h | 7 ++++++- MMCore/LoadableModules/LoadedModuleImplWindows.cpp | 4 ++++ MMCore/LoadableModules/LoadedModuleImplWindows.h | 4 ++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/MMCore/LoadableModules/LoadedModuleImplUnix.cpp b/MMCore/LoadableModules/LoadedModuleImplUnix.cpp index d8fef459d..9c58b0b12 100644 --- a/MMCore/LoadableModules/LoadedModuleImplUnix.cpp +++ b/MMCore/LoadableModules/LoadedModuleImplUnix.cpp @@ -22,6 +22,8 @@ #include "LoadedModuleImplUnix.h" +#ifndef _WIN32 + #include "../Error.h" #include @@ -77,3 +79,5 @@ LoadedModuleImplUnix::GetFunction(const char* funcName) ThrowDlError(); return proc; } + +#endif // !defined(_WIN32) \ No newline at end of file diff --git a/MMCore/LoadableModules/LoadedModuleImplUnix.h b/MMCore/LoadableModules/LoadedModuleImplUnix.h index af5d74f19..41b3c65c2 100644 --- a/MMCore/LoadableModules/LoadedModuleImplUnix.h +++ b/MMCore/LoadableModules/LoadedModuleImplUnix.h @@ -20,8 +20,11 @@ // AUTHOR: Mark Tsuchida, // based on parts of CPluginManager by Nenad Amodaj -#include "LoadedModuleImpl.h" +#pragma once + +#ifndef _WIN32 +#include "LoadedModuleImpl.h" class LoadedModuleImplUnix : public LoadedModuleImpl { @@ -34,3 +37,5 @@ class LoadedModuleImplUnix : public LoadedModuleImpl private: void* handle_; }; + +#endif // !defined(_WIN32) \ No newline at end of file diff --git a/MMCore/LoadableModules/LoadedModuleImplWindows.cpp b/MMCore/LoadableModules/LoadedModuleImplWindows.cpp index f6fcd1286..d7adc5f89 100644 --- a/MMCore/LoadableModules/LoadedModuleImplWindows.cpp +++ b/MMCore/LoadableModules/LoadedModuleImplWindows.cpp @@ -22,6 +22,8 @@ #include "LoadedModuleImplWindows.h" +#ifdef _WIN32 + #include "../Error.h" #include @@ -104,3 +106,5 @@ LoadedModuleImplWindows::GetFunction(const char* funcName) ThrowLastError(); return proc; } + +#endif // _WIN32 \ No newline at end of file diff --git a/MMCore/LoadableModules/LoadedModuleImplWindows.h b/MMCore/LoadableModules/LoadedModuleImplWindows.h index 46e4e2d03..287b24202 100644 --- a/MMCore/LoadableModules/LoadedModuleImplWindows.h +++ b/MMCore/LoadableModules/LoadedModuleImplWindows.h @@ -22,6 +22,8 @@ #pragma once +#ifdef _WIN32 + #include "LoadedModuleImpl.h" #define WIN32_LEAN_AND_MEAN @@ -39,3 +41,5 @@ class LoadedModuleImplWindows: public LoadedModuleImpl private: HMODULE handle_; }; + +#endif // _WIN32 \ No newline at end of file From b5bf300a4c0f9f99bfd90e979b9e1ecc8644261a Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 15:30:42 -0600 Subject: [PATCH 092/141] MMCore: Add missing 'const' for parameter This does not break existing callers but correctly allows calling with const arguments. --- MMCore/MMCore.cpp | 4 ++-- MMCore/MMCore.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 5d01bf21b..e3eb01bc9 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -7413,7 +7413,7 @@ void CMMCore::updateAllowedChannelGroups() * For legacy reasons, an exception is not thrown if there is an error. * Instead, false is returned if label is not a valid device. */ -bool CMMCore::supportsDeviceDetection(char* label) +bool CMMCore::supportsDeviceDetection(const char* label) { try { @@ -7438,7 +7438,7 @@ bool CMMCore::supportsDeviceDetection(char* label) * * @param label the label of the device for which the serial port should be found */ -MM::DeviceDetectionStatus CMMCore::detectDevice(char* label) +MM::DeviceDetectionStatus CMMCore::detectDevice(const char* label) { try { diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index d05411cec..e320b6c1f 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -604,8 +604,8 @@ class CMMCore /** \name Device discovery. */ ///@{ - bool supportsDeviceDetection(char* deviceLabel); - MM::DeviceDetectionStatus detectDevice(char* deviceLabel); + bool supportsDeviceDetection(const char* deviceLabel); + MM::DeviceDetectionStatus detectDevice(const char* deviceLabel); ///@} /** \name Hub and peripheral devices. */ From 2a86e994e0541db301dd431add286cd38ea0728b Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 15:39:29 -0600 Subject: [PATCH 093/141] MMCore: Avoid implicit unsigned -> char conversion --- MMCore/unittest/Logger-Tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MMCore/unittest/Logger-Tests.cpp b/MMCore/unittest/Logger-Tests.cpp index 15d971998..3eb892853 100644 --- a/MMCore/unittest/Logger-Tests.cpp +++ b/MMCore/unittest/Logger-Tests.cpp @@ -69,12 +69,12 @@ class LoggerTestThreadFunc { Logger lgr = c_->NewLogger("thread" + std::to_string(n_)); - char ch = '0' + n_; + auto ch = '0' + n_; if (ch < '0' || ch > 'z') ch = '~'; for (size_t j = 0; j < 50; ++j) { - LOG_TRACE(lgr) << j << ' ' << std::string(n_ * j, ch); + LOG_TRACE(lgr) << j << ' ' << std::string(n_ * j, char(ch)); } } }; From 00ede634e0f9c75bf20cbac85d7ff75c7192c07a Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 15:43:42 -0600 Subject: [PATCH 094/141] MMCore: Avoid deprecation warning from GoogleTest --- .../LoggingSplitEntryIntoLines-Tests.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp b/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp index 964059c54..2ca1a0cb0 100644 --- a/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp +++ b/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp @@ -66,7 +66,7 @@ TEST_P(SplitEntryIntoPacketsEmptyResultTest, EmptyResult) EXPECT_STREQ("", result_[0].GetText()); } -INSTANTIATE_TEST_CASE_P(NewlinesCase, SplitEntryIntoPacketsEmptyResultTest, +INSTANTIATE_TEST_SUITE_P(NewlinesCase, SplitEntryIntoPacketsEmptyResultTest, ::testing::Values("", "\r", "\n", "\r\r", "\r\n", "\n\n", "\r\r\r", "\r\r\n", "\r\n\r", "\r\n\n", "\n\r\r", "\n\r\n", "\n\n\r", "\n\n\n")); @@ -82,7 +82,7 @@ TEST_P(SplitEntryIntoPacketsSingleCharResultTest, SingleXResult) EXPECT_STREQ("X", result_[0].GetText()); } -INSTANTIATE_TEST_CASE_P(XFollowedByNewlinesCase, +INSTANTIATE_TEST_SUITE_P(XFollowedByNewlinesCase, SplitEntryIntoPacketsSingleCharResultTest, ::testing::Values("X", "X\r", "X\n", "X\r\r", "X\r\n", "X\n\n", "X\r\r\r", "X\r\r\n", "X\r\n\r", "X\r\n\n", @@ -102,11 +102,11 @@ TEST_P(SplitEntryIntoPacketsTwoLineResultTest, TwoLineXYResult) EXPECT_STREQ("Y", result_[1].GetText()); } -INSTANTIATE_TEST_CASE_P(XNewlineYCase, +INSTANTIATE_TEST_SUITE_P(XNewlineYCase, SplitEntryIntoPacketsTwoLineResultTest, ::testing::Values("X\rY", "X\nY", "X\r\nY")); -INSTANTIATE_TEST_CASE_P(XLinefeedYNewlinesCase, +INSTANTIATE_TEST_SUITE_P(XLinefeedYNewlinesCase, SplitEntryIntoPacketsTwoLineResultTest, ::testing::Values("X\nY\r", "X\nY\n", "X\nY\r\r", "X\nY\r\n", "X\nY\n\n", "X\nY\r\r\r", "X\nY\r\r\n", "X\nY\r\n\r", "X\nY\r\n\n", @@ -128,7 +128,7 @@ TEST_P(SplitEntryIntoPacketsXEmptyYResultTest, XEmptyYResult) EXPECT_STREQ("Y", result_[2].GetText()); } -INSTANTIATE_TEST_CASE_P(XNewlineNewlineYCase, +INSTANTIATE_TEST_SUITE_P(XNewlineNewlineYCase, SplitEntryIntoPacketsXEmptyYResultTest, ::testing::Values("X\r\rY", "X\n\nY", "X\n\rY", "X\r\n\rY", "X\r\n\nY", "X\r\r\nY", "X\n\r\nY", @@ -162,7 +162,7 @@ TEST_P(SplitEntryIntoPacketsLeadingNewlineTest, CorrectLeadingNewlines) EXPECT_STREQ("X", result_[expected_].GetText()); } -INSTANTIATE_TEST_CASE_P(LeadingNewlinesCase, +INSTANTIATE_TEST_SUITE_P(LeadingNewlinesCase, SplitEntryIntoPacketsLeadingNewlineTest, ::testing::Values(std::make_pair(0, "X"), std::make_pair(1, "\rX"), @@ -201,20 +201,20 @@ TEST_P(SplitEntryIntoPacketsSoftNewlineTest, CorrectSplit) } } -INSTANTIATE_TEST_CASE_P(NoSoftSplitCase, +INSTANTIATE_TEST_SUITE_P(NoSoftSplitCase, SplitEntryIntoPacketsSoftNewlineTest, ::testing::Values( std::string(MaxLogLineLen - 1, 'x'), std::string(MaxLogLineLen, 'x'))); -INSTANTIATE_TEST_CASE_P(OneSoftSplitCase, +INSTANTIATE_TEST_SUITE_P(OneSoftSplitCase, SplitEntryIntoPacketsSoftNewlineTest, ::testing::Values( std::string(MaxLogLineLen + 1, 'x'), std::string(2 * MaxLogLineLen - 1, 'x'), std::string(2 * MaxLogLineLen, 'x'))); -INSTANTIATE_TEST_CASE_P(TwoSoftSplitCase, +INSTANTIATE_TEST_SUITE_P(TwoSoftSplitCase, SplitEntryIntoPacketsSoftNewlineTest, ::testing::Values( std::string(2 * MaxLogLineLen + 1, 'x'), From dee6134f2be62af6cf38127b784de78486a08960 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 15:52:13 -0600 Subject: [PATCH 095/141] MMCore: Delete ancient Eclipse project files Wow, unchanged since 2007.... --- MMCore/.cdtbuild | 33 --------------------------------- MMCore/.cdtproject | 18 ------------------ MMCore/.project | 27 --------------------------- 3 files changed, 78 deletions(-) delete mode 100644 MMCore/.cdtbuild delete mode 100644 MMCore/.cdtproject delete mode 100644 MMCore/.project diff --git a/MMCore/.cdtbuild b/MMCore/.cdtbuild deleted file mode 100644 index b20213226..000000000 --- a/MMCore/.cdtbuild +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MMCore/.cdtproject b/MMCore/.cdtproject deleted file mode 100644 index 0003d7074..000000000 --- a/MMCore/.cdtproject +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/MMCore/.project b/MMCore/.project deleted file mode 100644 index bb995703a..000000000 --- a/MMCore/.project +++ /dev/null @@ -1,27 +0,0 @@ - - - MMCore - - - MMDevice - - - - org.eclipse.cdt.managedbuilder.core.genmakebuilder - - - - - - org.eclipse.cdt.core.cnature - org.eclipse.cdt.managedbuilder.core.managedBuildNature - org.eclipse.cdt.core.ccnature - - - - SourcePool - 2 - C:/projects/MicroManage/SourcePool - - - From 94f939b6271dd43bdeb0903068c5ffa5ccc95bb1 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 16:18:47 -0600 Subject: [PATCH 096/141] MMDevice/MMCore: Replace std::endl with '\n' Finish the job of 4b88884f946ef748ce2b11adf54d14fc14a2a76e. --- MMCore/MMEventCallback.h | 25 +++++++++++-------------- MMDevice/ImageMetadata.h | 2 +- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/MMCore/MMEventCallback.h b/MMCore/MMEventCallback.h index 638508808..49ddc497c 100644 --- a/MMCore/MMEventCallback.h +++ b/MMCore/MMEventCallback.h @@ -32,61 +32,58 @@ class MMEventCallback virtual void onPropertiesChanged() { - std::cout << "onPropertiesChanged()" << std:: endl; + std::cout << "onPropertiesChanged()\n"; } virtual void onPropertyChanged(const char* name, const char* propName, const char* propValue) { - std::cout << "onPropertyChanged() " << name << " " << propName << " " << propValue; - std::cout << std:: endl; + std::cout << "onPropertyChanged() " << name << " " << propName << " " << propValue << '\n'; } virtual void onChannelGroupChanged(const char* newChannelGroupName) { - std::cout << "onChannelGroupChanged() " << newChannelGroupName << std::endl; + std::cout << "onChannelGroupChanged() " << newChannelGroupName << '\n'; } virtual void onConfigGroupChanged(const char* groupName, const char* newConfigName) { - std::cout << "onConfigGroupChanged() " << groupName << " " << newConfigName; - std::cout << std:: endl; + std::cout << "onConfigGroupChanged() " << groupName << " " << newConfigName << '\n'; } virtual void onSystemConfigurationLoaded() { - std::cout << "onSystemConfigurationLoaded() "; - std::cout << std::endl; + std::cout << "onSystemConfigurationLoaded() \n"; } virtual void onPixelSizeChanged(double newPixelSizeUm) { - std::cout << "onPixelSizeChanged() " << newPixelSizeUm << std::endl; + std::cout << "onPixelSizeChanged() " << newPixelSizeUm << '\n'; } virtual void onPixelSizeAffineChanged(double v0, double v1, double v2, double v3, double v4, double v5) { - std::cout << "onPixelSizeAffineChanged() " << v0 << "-" << v1 << "-" << v2 << "-" << v3 << "-" << v4 << "-" << v5 << std::endl; + std::cout << "onPixelSizeAffineChanged() " << v0 << "-" << v1 << "-" << v2 << "-" << v3 << "-" << v4 << "-" << v5 << '\n'; } virtual void onStagePositionChanged(char* name, double pos) { - std::cout << "onStagePositionChanged()" << name << " " << pos << "\n"; + std::cout << "onStagePositionChanged()" << name << " " << pos << '\n'; } virtual void onXYStagePositionChanged(char* name, double xpos, double ypos) { std::cout << "onXYStagePositionChanged()" << name << " " << xpos; - std::cout << " " << ypos << "\n"; + std::cout << " " << ypos << '\n'; } virtual void onExposureChanged(char* name, double newExposure) { - std::cout << "onExposureChanged()" << name << " " << newExposure << "\n"; + std::cout << "onExposureChanged()" << name << " " << newExposure << '\n'; } virtual void onSLMExposureChanged(char* name, double newExposure) { - std::cout << "onSLMExposureChanged()" << name << " " << newExposure << "\n"; + std::cout << "onSLMExposureChanged()" << name << " " << newExposure << '\n'; } }; diff --git a/MMDevice/ImageMetadata.h b/MMDevice/ImageMetadata.h index 49623bd28..156385eb1 100644 --- a/MMDevice/ImageMetadata.h +++ b/MMDevice/ImageMetadata.h @@ -473,7 +473,7 @@ class Metadata if (it->second->ToArrayTag()) id = "a"; std::string ser = it->second->Serialize(); - os << id << " : " << ser << std::endl; + os << id << " : " << ser << '\n'; } return os.str(); From 8c02aaabbe9de3e0ca4d8fe47ca9f56b9c01dcba Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 13 Dec 2023 13:14:51 -0600 Subject: [PATCH 097/141] Experimental Meson build for MMDevice, MMCore MMDevice builds on its own. MMCore requires a copy (or symlink) of MMDevice to be in its subprojects/ directory. (This is unavoidable as we develop the Meson build in place. When MMDevice and MMCore are in their own repositories (and/or have source tarballs) MMCore will be able to depend on MMDevice as either a git submodule or a Meson wrap.) --- MMCore/meson.build | 98 +++++++++++++++++++++++++++++++++ MMCore/subprojects/.gitignore | 12 ++++ MMCore/subprojects/gtest.wrap | 16 ++++++ MMCore/unittest/meson.build | 57 +++++++++++++++++++ MMDevice/meson.build | 60 ++++++++++++++++++++ MMDevice/subprojects/.gitignore | 10 ++++ MMDevice/subprojects/gtest.wrap | 16 ++++++ MMDevice/unittest/meson.build | 32 +++++++++++ 8 files changed, 301 insertions(+) create mode 100644 MMCore/meson.build create mode 100644 MMCore/subprojects/.gitignore create mode 100644 MMCore/subprojects/gtest.wrap create mode 100644 MMCore/unittest/meson.build create mode 100644 MMDevice/meson.build create mode 100644 MMDevice/subprojects/.gitignore create mode 100644 MMDevice/subprojects/gtest.wrap create mode 100644 MMDevice/unittest/meson.build diff --git a/MMCore/meson.build b/MMCore/meson.build new file mode 100644 index 000000000..b60ab51fc --- /dev/null +++ b/MMCore/meson.build @@ -0,0 +1,98 @@ +# This Meson script is experimental and potentially incomplete. It is not part +# of the supported build system for Micro-Manager or mmCoreAndDevices. + +project( + 'MMCore', + 'cpp', + meson_version: '>=1.1.0', # May relax + default_options: [ + 'cpp_std=c++14', + 'warning_level=3', + ], +) + +cxx = meson.get_compiler('cpp') + +if cxx.get_id() in ['msvc', 'clang-cl'] + add_project_arguments('-DNOMINMAX', language: 'cpp') + add_project_arguments('/wd4290', language: 'cpp') +endif + +# MMDevice must be copied into subprojects/ for this experimental build to work +# (unless MMCore is itself being used as a subproject). +mmdevice_proj = subproject('MMDevice') +mmdevice_dep = mmdevice_proj.get_variable('mmdevice') + +mmcore_sources = files( + 'CircularBuffer.cpp', + 'Configuration.cpp', + 'CoreCallback.cpp', + 'CoreFeatures.cpp', + 'CoreProperty.cpp', + 'DeviceManager.cpp', + 'Devices/AutoFocusInstance.cpp', + 'Devices/CameraInstance.cpp', + 'Devices/DeviceInstance.cpp', + 'Devices/GalvoInstance.cpp', + 'Devices/HubInstance.cpp', + 'Devices/ImageProcessorInstance.cpp', + 'Devices/MagnifierInstance.cpp', + 'Devices/SerialInstance.cpp', + 'Devices/ShutterInstance.cpp', + 'Devices/SignalIOInstance.cpp', + 'Devices/SLMInstance.cpp', + 'Devices/StageInstance.cpp', + 'Devices/StateInstance.cpp', + 'Devices/XYStageInstance.cpp', + 'Error.cpp', + 'FrameBuffer.cpp', + 'LibraryInfo/LibraryPathsUnix.cpp', + 'LibraryInfo/LibraryPathsWindows.cpp', + 'LoadableModules/LoadedDeviceAdapter.cpp', + 'LoadableModules/LoadedModule.cpp', + 'LoadableModules/LoadedModuleImpl.cpp', + 'LoadableModules/LoadedModuleImplUnix.cpp', + 'LoadableModules/LoadedModuleImplWindows.cpp', + 'Logging/Metadata.cpp', + 'LogManager.cpp', + 'MMCore.cpp', + 'PluginManager.cpp', + 'Semaphore.cpp', + 'Task.cpp', + 'TaskSet.cpp', + 'TaskSet_CopyMemory.cpp', + 'ThreadPool.cpp', +) + +mmcore_include_dir = include_directories('.') + +mmcore_public_headers = files( + 'Configuration.h', + 'Error.h', + 'ErrorCodes.h', + 'MMCore.h', + 'MMEventCallback.h', + # TODO MMCore.h currently includes Logging/Logger.h and CoreUtils.h, which + # should not be public. That will need to be fixed before we support + # installing the public headers. +) + +# TODO Allow MMCore to be built as a shared library, too. For that, we'd need +# to define the exported symbols on Windows (__declspec(dllexport)). +mmcore_lib = static_library( + 'MMCore', + sources: mmcore_sources, + include_directories: mmcore_include_dir, + dependencies: mmdevice_dep, + cpp_args: [ + '-D_CRT_SECURE_NO_WARNINGS', # TODO Eliminate the need + ], +) + +subdir('unittest') + +mmcore = declare_dependency( + include_directories: mmcore_include_dir, + link_with: mmcore_lib, + dependencies: mmdevice_dep, +) diff --git a/MMCore/subprojects/.gitignore b/MMCore/subprojects/.gitignore new file mode 100644 index 000000000..479f9fa9c --- /dev/null +++ b/MMCore/subprojects/.gitignore @@ -0,0 +1,12 @@ +/packagecache/ + +/MMDevice/ + +# Subprojects installed by meson wrap +/*-*/ + +# Ignore *.wrap by default (may be auto-installed transitive dependencies) +/*.wrap + +# Do not ignore wraps we provide +!/gtest.wrap \ No newline at end of file diff --git a/MMCore/subprojects/gtest.wrap b/MMCore/subprojects/gtest.wrap new file mode 100644 index 000000000..8c067ff20 --- /dev/null +++ b/MMCore/subprojects/gtest.wrap @@ -0,0 +1,16 @@ +[wrap-file] +directory = googletest-1.14.0 +source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz +source_filename = gtest-1.14.0.tar.gz +source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7 +patch_filename = gtest_1.14.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-1/get_patch +patch_hash = 2e693c7d3f9370a7aa6dac802bada0874d3198ad4cfdf75647b818f691182b50 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-1/gtest-1.14.0.tar.gz +wrapdb_version = 1.14.0-1 + +[provide] +gtest = gtest_dep +gtest_main = gtest_main_dep +gmock = gmock_dep +gmock_main = gmock_main_dep diff --git a/MMCore/unittest/meson.build b/MMCore/unittest/meson.build new file mode 100644 index 000000000..05395a28c --- /dev/null +++ b/MMCore/unittest/meson.build @@ -0,0 +1,57 @@ +# This Meson script is experimental and potentially incomplete. It is not part +# of the supported build system for Micro-Manager or mmCoreAndDevices. + +gtest_proj = subproject('gtest') +gtest_dep = gtest_proj.get_variable('gtest_dep') + +# TODO: Use a single executable for all tests -- but that requires modifying +# the sources (to remove main()), so needs to wait until we remove these tests +# from the Automake build. + +mmcore_apierror_test_exe = executable( + 'MMCoreAPIErrorTest', + sources: 'APIError-Tests.cpp', + include_directories: mmcore_include_dir, + link_with: mmcore_lib, + dependencies: [ + mmdevice_dep, + gtest_dep, + ], +) + +test('MMCore APIError test', mmcore_apierror_test_exe) + +mmcore_logger_test_exe = executable( + 'MMCoreLoggerTest', + sources: 'Logger-Tests.cpp', + include_directories: mmcore_include_dir, + link_with: mmcore_lib, + dependencies: [ + mmdevice_dep, + gtest_dep, + ], + cpp_args: [ + '-D_CRT_SECURE_NO_WARNINGS', # TODO Eliminate the need + ], +) + +test('MMCore Logger test', mmcore_logger_test_exe) + +mmcore_loggingsplitentryintolines_test_exe = executable( + 'MMCoreLoggingSplitEntryIntoLinesTest', + sources: 'LoggingSplitEntryIntoLines-Tests.cpp', + include_directories: mmcore_include_dir, + link_with: mmcore_lib, + dependencies: [ + mmdevice_dep, + gtest_dep, + ], + cpp_args: [ + '-D_CRT_SECURE_NO_WARNINGS', # TODO Eliminate the need + ], +) + +test( + 'MMCore LoggingSplitEntryIntoLines test', + mmcore_loggingsplitentryintolines_test_exe +) diff --git a/MMDevice/meson.build b/MMDevice/meson.build new file mode 100644 index 000000000..9f6bdf496 --- /dev/null +++ b/MMDevice/meson.build @@ -0,0 +1,60 @@ +# This Meson script is experimental and potentially incomplete. It is not part +# of the supported build system for Micro-Manager or mmCoreAndDevices. + +project( + 'MMDevice', + 'cpp', + meson_version: '>=1.1.0', # May relax + default_options: [ + 'cpp_std=c++14', + 'warning_level=3', + ], +) + +# We intentionally do NOT define NOMINMAX on Windows. MMDevice should compile +# correctly with or without Windows.h's min()/max() macros. + +mmdevice_sources = files( + 'Debayer.cpp', + 'DeviceUtils.cpp', + 'ImgBuffer.cpp', + 'MMDevice.cpp', + 'ModuleInterface.cpp', + 'Property.cpp', +) + +mmdevice_include_dir = include_directories('.') + +mmdevice_public_headers = files( + 'Debayer.h', + 'DeviceBase.h', + 'DeviceThreads.h', + 'DeviceUtils.h', + 'ImageMetadata.h', + 'ImgBuffer.h', + 'MMDevice.h', + 'MMDeviceConstants.h', + 'ModuleInterface.h', + 'Property.h', +) +# TODO Support installing public headers + +mmdevice_lib = static_library( + 'MMDevice', + sources: mmdevice_sources, + include_directories: mmdevice_include_dir, + cpp_args: [ + '-DMODULE_EXPORTS', + '-D_CRT_SECURE_NO_WARNINGS', # TODO Eliminate the need + ], + # MMDevice does not depend on any external library. This is a big advantage + # in simplifing its usage (given hundreds of device adapters depending on + # MMDevice), so think twice before adding dependencies. +) + +subdir('unittest') + +mmdevice = declare_dependency( + include_directories: mmdevice_include_dir, + link_with: mmdevice_lib, +) diff --git a/MMDevice/subprojects/.gitignore b/MMDevice/subprojects/.gitignore new file mode 100644 index 000000000..8b7ac1334 --- /dev/null +++ b/MMDevice/subprojects/.gitignore @@ -0,0 +1,10 @@ +/packagecache/ + +# Subprojects installed by meson wrap +/*-*/ + +# Ignore *.wrap by default (may be auto-installed transitive dependencies) +/*.wrap + +# Do not ignore wraps we provide +!/gtest.wrap \ No newline at end of file diff --git a/MMDevice/subprojects/gtest.wrap b/MMDevice/subprojects/gtest.wrap new file mode 100644 index 000000000..8c067ff20 --- /dev/null +++ b/MMDevice/subprojects/gtest.wrap @@ -0,0 +1,16 @@ +[wrap-file] +directory = googletest-1.14.0 +source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz +source_filename = gtest-1.14.0.tar.gz +source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7 +patch_filename = gtest_1.14.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-1/get_patch +patch_hash = 2e693c7d3f9370a7aa6dac802bada0874d3198ad4cfdf75647b818f691182b50 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-1/gtest-1.14.0.tar.gz +wrapdb_version = 1.14.0-1 + +[provide] +gtest = gtest_dep +gtest_main = gtest_main_dep +gmock = gmock_dep +gmock_main = gmock_main_dep diff --git a/MMDevice/unittest/meson.build b/MMDevice/unittest/meson.build new file mode 100644 index 000000000..0b9296ac2 --- /dev/null +++ b/MMDevice/unittest/meson.build @@ -0,0 +1,32 @@ +# This Meson script is experimental and potentially incomplete. It is not part +# of the supported build system for Micro-Manager or mmCoreAndDevices. + +gtest_proj = subproject('gtest') +gtest_dep = gtest_proj.get_variable('gtest_dep') + +# TODO: Use a single executable for all tests -- but that requires modifying +# the sources (to remove main()), so needs to wait until we remove these tests +# from the Automake build. + +mmdevice_floatpropertytruncation_test_exe = executable( + 'MMDeviceFloatPropertyTruncationTest', + sources: 'FloatPropertyTruncation-Tests.cpp', + include_directories: mmdevice_include_dir, + link_with: mmdevice_lib, + dependencies: gtest_dep, +) + +test( + 'MMDevice FloatPropertyTruncation test', + mmdevice_floatpropertytruncation_test_exe, +) + +mmdevice_mmtime_test_exe = executable( + 'MMDeviceMMTimeTest', + sources: 'MMTime-Tests.cpp', + include_directories: mmdevice_include_dir, + link_with: mmdevice_lib, + dependencies: gtest_dep, +) + +test('MMDevice MMTime test', mmdevice_mmtime_test_exe) From 78f9ea974184dddc61272b1a2ae101b6ee9d4d73 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 12:26:00 -0600 Subject: [PATCH 098/141] CI for MMDevice/MMCore experimental Meson build --- .github/workflows/ci-mmdevice-mmcore.yml | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/ci-mmdevice-mmcore.yml diff --git a/.github/workflows/ci-mmdevice-mmcore.yml b/.github/workflows/ci-mmdevice-mmcore.yml new file mode 100644 index 000000000..bcc16bb02 --- /dev/null +++ b/.github/workflows/ci-mmdevice-mmcore.yml @@ -0,0 +1,67 @@ +name: MMDevice/MMCore experimental Meson build & tests + +on: + pull_request: + paths: + - MMDevice/** + - MMCore/** + push: + branches: + - main + paths: + - MMDevice/** + - MMCore/** + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: + - windows + - macos + - ubuntu + include: + - os: windows + runner: windows-2019 + cxx: cl + - os: macos + runner: macos-11 + cxx: clang++ + - os: ubuntu + runner: ubuntu-22.04 + cxx: g++ + name: ${{ matrix.runner }}-${{ matrix.cxx }} + runs-on: ${{ matrix.runner }} + env: + CXX: ${{ matrix.cxx }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install tools + run: | + python -m pip install --upgrade pip meson ninja + - uses: ilammy/msvc-dev-cmd@v1 + with: + vsversion: '2019' + - name: Build and test MMDevice + run: | + cd MMDevice + meson setup builddir --buildtype debug + meson test -C builddir --print-errorlogs + - name: Prepare MMCore source + shell: bash + run: | + git clean -dxf + cp -R MMDevice MMCore/subprojects + - name: Build and test MMCore + run: | + cd MMCore + meson setup builddir --buildtype debug + meson test -C builddir --print-errorlogs From 159660719af86b015078b7fc998549ba45761fec Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 14:14:52 -0600 Subject: [PATCH 099/141] MMDevice: Remove superfluous 'const' Doesn't change anything but produces warnings from GCC and Clang. This change should not affect the device binary interface. --- MMDevice/ImageMetadata.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MMDevice/ImageMetadata.h b/MMDevice/ImageMetadata.h index 156385eb1..eebebfbd0 100644 --- a/MMDevice/ImageMetadata.h +++ b/MMDevice/ImageMetadata.h @@ -101,7 +101,7 @@ class MetadataTag str.append(name_); return str; } - const bool IsReadOnly() const {return readOnly_;} + bool IsReadOnly() const {return readOnly_;} void SetDevice(const char* device) {deviceLabel_ = device;} void SetName(const char* name) {name_ = name;} @@ -496,4 +496,4 @@ class Metadata #ifdef _MSC_VER #pragma warning(pop) -#endif \ No newline at end of file +#endif From 03df42e64b5566a9a0a2267b0e583c2776fe649f Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 14:17:25 -0600 Subject: [PATCH 100/141] MMDevice: Avoid sprintf() (deprecation warning) --- MMDevice/Property.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMDevice/Property.cpp b/MMDevice/Property.cpp index b7f807ccd..7088a8f81 100644 --- a/MMDevice/Property.cpp +++ b/MMDevice/Property.cpp @@ -191,7 +191,7 @@ bool MM::FloatProperty::Get(std::string& strVal) const { char fmtStr[20]; char buf[BUFSIZE]; - std::sprintf(fmtStr, "%%.%df", decimalPlaces_); + std::snprintf(fmtStr, sizeof(fmtStr), "%%.%df", decimalPlaces_); std::snprintf(buf, BUFSIZE, fmtStr, value_); strVal = buf; return true; From 5a665ce1daee9d67719d70cbdc306594403b6f4b Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 14:37:18 -0600 Subject: [PATCH 101/141] MMDevice/MMCore: Suppress GCC warnings for throw() There is no way to suppress just for exception specifications, but limit the suppression to within the headers. Also make suppression of MSVC C4290 consistent with GCC. --- MMCore/CircularBuffer.cpp | 8 ++++++++ MMCore/CircularBuffer.h | 9 +++++++++ MMCore/ConfigGroup.h | 19 +++++++++++++++++++ MMCore/Configuration.cpp | 9 +++++++++ MMCore/Configuration.h | 10 ++++++++++ MMCore/MMCore.cpp | 10 +++++++++- MMCore/MMCore.h | 10 ++++++++++ MMDevice/ImageMetadata.h | 12 +++++++++++- 8 files changed, 85 insertions(+), 2 deletions(-) diff --git a/MMCore/CircularBuffer.cpp b/MMCore/CircularBuffer.cpp index 1b796a4e9..933142e9b 100644 --- a/MMCore/CircularBuffer.cpp +++ b/MMCore/CircularBuffer.cpp @@ -36,6 +36,14 @@ #include #include +#ifdef _MSC_VER +#pragma warning(disable: 4290) // 'C++ exception specification ignored' +#endif + +#if defined(__GNUC__) && !defined(__clang__) +// 'dynamic exception specifications are deprecated in C++11 [-Wdeprecated]' +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif const long long bytesInMB = 1 << 20; const long adjustThreshold = LONG_MAX / 2; diff --git a/MMCore/CircularBuffer.h b/MMCore/CircularBuffer.h index d42ea4a47..636c02ceb 100644 --- a/MMCore/CircularBuffer.h +++ b/MMCore/CircularBuffer.h @@ -40,6 +40,11 @@ #pragma warning(disable: 4290) // 'C++ exception specification ignored' #endif +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// 'dynamic exception specifications are deprecated in C++11 [-Wdeprecated]' +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif class ThreadPool; class TaskSet_CopyMemory; @@ -101,6 +106,10 @@ class CircularBuffer std::shared_ptr tasksMemCopy_; }; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/MMCore/ConfigGroup.h b/MMCore/ConfigGroup.h index 050020815..a5b7d41bd 100644 --- a/MMCore/ConfigGroup.h +++ b/MMCore/ConfigGroup.h @@ -22,6 +22,17 @@ #pragma once +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4290) // 'C++ exception specification ignored' +#endif + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// 'dynamic exception specifications are deprecated in C++11 [-Wdeprecated]' +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif + #include "Configuration.h" #include "Error.h" #include @@ -436,3 +447,11 @@ class PixelSizeConfigGroup : public ConfigGroupBase } } }; + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif \ No newline at end of file diff --git a/MMCore/Configuration.cpp b/MMCore/Configuration.cpp index 32c8fa8ae..acd2df824 100644 --- a/MMCore/Configuration.cpp +++ b/MMCore/Configuration.cpp @@ -27,6 +27,15 @@ #include #include +#ifdef _MSC_VER +#pragma warning(disable: 4290) // 'C++ exception specification ignored' +#endif + +#if defined(__GNUC__) && !defined(__clang__) +// 'dynamic exception specifications are deprecated in C++11 [-Wdeprecated]' +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif + std::string PropertySetting::generateKey(const char* device, const char* prop) { std::string key(device); diff --git a/MMCore/Configuration.h b/MMCore/Configuration.h index 4cd4a6ce7..16a2b775b 100644 --- a/MMCore/Configuration.h +++ b/MMCore/Configuration.h @@ -25,6 +25,12 @@ #pragma warning(disable: 4290) // 'C++ exception specification ignored' #endif +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// 'dynamic exception specifications are deprecated in C++11 [-Wdeprecated]' +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif + #include #include #include @@ -119,6 +125,10 @@ class Configuration std::map index_; }; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index e3eb01bc9..1db4309a9 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -37,7 +37,6 @@ // because all public methods will most likely appear in other // programming environments (Java or Python). - #include "../MMDevice/DeviceThreads.h" #include "../MMDevice/DeviceUtils.h" #include "../MMDevice/ImageMetadata.h" @@ -65,6 +64,15 @@ #include #include +#ifdef _MSC_VER +#pragma warning(disable: 4290) // 'C++ exception specification ignored' +#endif + +#if defined(__GNUC__) && !defined(__clang__) +// 'dynamic exception specifications are deprecated in C++11 [-Wdeprecated]' +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif + /* * Important! Read this before changing this file: * diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index e320b6c1f..542b954ca 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -52,6 +52,12 @@ #pragma warning(disable: 4290) // 'C++ exception specification ignored' #endif +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// 'dynamic exception specifications are deprecated in C++11 [-Wdeprecated]' +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif + #include "../MMDevice/DeviceThreads.h" #include "../MMDevice/MMDevice.h" #include "../MMDevice/MMDeviceConstants.h" @@ -693,6 +699,10 @@ class CMMCore void loadSystemConfigurationImpl(const char* fileName) throw (CMMError); }; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/MMDevice/ImageMetadata.h b/MMDevice/ImageMetadata.h index eebebfbd0..98f5c8a64 100644 --- a/MMDevice/ImageMetadata.h +++ b/MMDevice/ImageMetadata.h @@ -27,6 +27,12 @@ #pragma warning(disable: 4290) // 'C++ exception specification ignored' #endif +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// 'dynamic exception specifications are deprecated in C++11 [-Wdeprecated]' +#pragma GCC diagnostic ignored "-Wdeprecated" +#endif + #include "MMDeviceConstants.h" #include @@ -319,7 +325,7 @@ class Metadata else return false; } - + MetadataSingleTag GetSingleTag(const char* key) const throw (MetadataKeyError) { MetadataTag* tag = FindTag(key); @@ -494,6 +500,10 @@ class Metadata typedef std::map::const_iterator TagConstIter; }; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + #ifdef _MSC_VER #pragma warning(pop) #endif From 97ecc51a3e7214dd312c6b2f00f0bf9e30bfb7d7 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 14:49:37 -0600 Subject: [PATCH 102/141] MMCore: Remove warning suppression from build The warnings for dynamic exception specifications are now suppressed in the code using pragmas for both GCC and MSVC. --- MMCore/MMCore.vcxproj | 4 +--- MMCore/meson.build | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/MMCore/MMCore.vcxproj b/MMCore/MMCore.vcxproj index 26d732feb..413f2b563 100644 --- a/MMCore/MMCore.vcxproj +++ b/MMCore/MMCore.vcxproj @@ -55,7 +55,6 @@ true - 4290;%(DisableSpecificWarnings) $(OutDir)MMCore.lib @@ -70,7 +69,6 @@ true - 4290;%(DisableSpecificWarnings) $(OutDir)MMCore.lib @@ -183,4 +181,4 @@ - \ No newline at end of file + diff --git a/MMCore/meson.build b/MMCore/meson.build index b60ab51fc..d7399dc29 100644 --- a/MMCore/meson.build +++ b/MMCore/meson.build @@ -15,7 +15,6 @@ cxx = meson.get_compiler('cpp') if cxx.get_id() in ['msvc', 'clang-cl'] add_project_arguments('-DNOMINMAX', language: 'cpp') - add_project_arguments('/wd4290', language: 'cpp') endif # MMDevice must be copied into subprojects/ for this experimental build to work From d5a58b7f906753db4d9ae9575ef0c76b18b51982 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 15:14:34 -0600 Subject: [PATCH 103/141] MMCore: Don't free() arrays allocated by 'new' And don't manually manage memory to begin with; use std::vector. --- MMCore/MMCore.cpp | 56 +++++++++++++---------------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 1db4309a9..22c2c2ecc 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -4458,23 +4458,10 @@ void CMMCore::setMultiROI(std::vector xs, std::vector ys, throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); } mm::DeviceModuleLockGuard guard(camera); - unsigned numROI = (unsigned) xs.size(); - unsigned* xsArr = new unsigned[numROI]; - unsigned* ysArr = new unsigned[numROI]; - unsigned* widthsArr = new unsigned[numROI]; - unsigned* heightsArr = new unsigned[numROI]; - for (unsigned i = 0; i < numROI; ++i) - { - xsArr[i] = xs[i]; - ysArr[i] = ys[i]; - widthsArr[i] = widths[i]; - heightsArr[i] = heights[i]; - } - int nRet = camera->SetMultiROI(xsArr, ysArr, widthsArr, heightsArr, numROI); - free(xsArr); - free(ysArr); - free(widthsArr); - free(heightsArr); + const unsigned numROI = (unsigned) xs.size(); + int nRet = camera->SetMultiROI(xs.data(), ys.data(), + widths.data(), heights.data(), + numROI); if (nRet != DEVICE_OK) { throw CMMError(getDeviceErrorText(nRet, camera).c_str(), MMERR_DEVICE_GENERIC); @@ -4507,18 +4494,16 @@ void CMMCore::getMultiROI(std::vector& xs, std::vector& ys, throw CMMError(getDeviceErrorText(nRet, camera).c_str(), MMERR_DEVICE_GENERIC); } - unsigned* xsArr = new unsigned[numROI]; - unsigned* ysArr = new unsigned[numROI]; - unsigned* widthsArr = new unsigned[numROI]; - unsigned* heightsArr = new unsigned[numROI]; + std::vector xsTmp(numROI); + std::vector ysTmp(numROI); + std::vector widthsTmp(numROI); + std::vector heightsTmp(numROI); unsigned newNum = numROI; - nRet = camera->GetMultiROI(xsArr, ysArr, widthsArr, heightsArr, &newNum); + nRet = camera->GetMultiROI(xsTmp.data(), ysTmp.data(), + widthsTmp.data(), heightsTmp.data(), + &newNum); if (nRet != DEVICE_OK) { - free(xsArr); - free(ysArr); - free(widthsArr); - free(heightsArr); throw CMMError(getDeviceErrorText(nRet, camera).c_str(), MMERR_DEVICE_GENERIC); } if (newNum > numROI) @@ -4527,21 +4512,10 @@ void CMMCore::getMultiROI(std::vector& xs, std::vector& ys, throw CMMError("Camera returned too many ROIs"); } - xs.clear(); - ys.clear(); - widths.clear(); - heights.clear(); - for (unsigned i = 0; i < newNum; ++i) - { - xs.push_back(xsArr[i]); - ys.push_back(ysArr[i]); - widths.push_back(widthsArr[i]); - heights.push_back(heightsArr[i]); - } - free(xsArr); - free(ysArr); - free(widthsArr); - free(heightsArr); + xs.swap(xsTmp); + ys.swap(ysTmp); + widths.swap(widthsTmp); + heights.swap(heightsTmp); } /** From 3f1f6dd11b155b6ef43d9bd68fcef5ce98636e9a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 23:04:17 +0000 Subject: [PATCH 104/141] Update secret-device-adapters-commit to latest --- secret-device-adapters-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret-device-adapters-commit b/secret-device-adapters-commit index 76810a726..5ed7c7885 100644 --- a/secret-device-adapters-commit +++ b/secret-device-adapters-commit @@ -1 +1 @@ -bf0e2813637a519b3f1348788f9526b1590c5813 +1f81fd72c9ec557c32c44e03ed91a247e14df2ce From b256ba6f1582d453a0cd5e8414eb724763776fd1 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 17:16:59 -0600 Subject: [PATCH 105/141] MMCore: Remove unused #include --- MMCore/MMCore.h | 1 - MMCore/meson.build | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 542b954ca..d06d86013 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -62,7 +62,6 @@ #include "../MMDevice/MMDevice.h" #include "../MMDevice/MMDeviceConstants.h" #include "Configuration.h" -#include "CoreUtils.h" #include "Error.h" #include "ErrorCodes.h" #include "Logging/Logger.h" diff --git a/MMCore/meson.build b/MMCore/meson.build index d7399dc29..8164b98a9 100644 --- a/MMCore/meson.build +++ b/MMCore/meson.build @@ -71,7 +71,7 @@ mmcore_public_headers = files( 'ErrorCodes.h', 'MMCore.h', 'MMEventCallback.h', - # TODO MMCore.h currently includes Logging/Logger.h and CoreUtils.h, which + # TODO MMCore.h currently includes Logging/Logger.h, which # should not be public. That will need to be fixed before we support # installing the public headers. ) From 1234bfde097e9cc05ee437d3e77ade7897d4847b Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 18:21:20 -0600 Subject: [PATCH 106/141] MMCore: List all transitively public headers (Not used in any build yet.) Currently MMCore is only used to build MMCoreJ and pymmcore, and providing a good (minimal) set of public headers (as with proper support to use MMCore as a C++ library by itself) should probably be a future goal. For now, list the headers needed in practice. --- MMCore/meson.build | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/MMCore/meson.build b/MMCore/meson.build index 8164b98a9..a24403066 100644 --- a/MMCore/meson.build +++ b/MMCore/meson.build @@ -69,12 +69,15 @@ mmcore_public_headers = files( 'Configuration.h', 'Error.h', 'ErrorCodes.h', + 'Logging/GenericLogger.h', + 'Logging/Logger.h', + 'Logging/Metadata.h', + 'Logging/GenericMetadata.h', 'MMCore.h', 'MMEventCallback.h', - # TODO MMCore.h currently includes Logging/Logger.h, which - # should not be public. That will need to be fixed before we support - # installing the public headers. ) +# Note that the MMDevice headers are also needed; which of those are part of +# MMCore's public interface is poorly defined at the moment. # TODO Allow MMCore to be built as a shared library, too. For that, we'd need # to define the exported symbols on Windows (__declspec(dllexport)). From 16e44719e2ad1d22aedd5eb1c88422cf6fa35f55 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 16:04:16 -0600 Subject: [PATCH 107/141] Remove MMDevice/MMCore unit tests from Automake We do not run these tests from CI using Automake currently, and there is not much of an advantage in doing so because we now have them run using the experimental Meson build for MMDevice and MMCore. So remove from the Automake build so that these tests can be evolved (including switching the testing framework away from GoogleTest) without having to update the Automake build. For the couple of device adapters that have unit tests, those can still be run from Automake. --- MMCore/Makefile.am | 6 ------ MMCore/unittest/Makefile.am | 9 --------- MMDevice/Makefile.am | 2 -- MMDevice/unittest/Makefile.am | 7 ------- 4 files changed, 24 deletions(-) delete mode 100644 MMCore/unittest/Makefile.am delete mode 100644 MMDevice/unittest/Makefile.am diff --git a/MMCore/Makefile.am b/MMCore/Makefile.am index d56ad5273..013860a8a 100644 --- a/MMCore/Makefile.am +++ b/MMCore/Makefile.am @@ -99,10 +99,4 @@ libMMCore_la_SOURCES = \ ThreadPool.cpp \ ThreadPool.h -if BUILD_CPP_TESTS -UNITTESTS = unittest -endif - -SUBDIRS = . $(UNITTESTS) - EXTRA_DIST = license.txt diff --git a/MMCore/unittest/Makefile.am b/MMCore/unittest/Makefile.am deleted file mode 100644 index 26eec6399..000000000 --- a/MMCore/unittest/Makefile.am +++ /dev/null @@ -1,9 +0,0 @@ -check_PROGRAMS = \ - APIError-Tests \ - CoreSanity-Tests \ - LoggingSplitEntryIntoLines-Tests \ - Logger-Tests -AM_DEFAULT_SOURCE_EXT = .cpp -AM_CPPFLAGS = $(GMOCK_CPPFLAGS) -I.. -LDADD = ../../../testing/libgmock.la ../libMMCore.la -TESTS = $(check_PROGRAMS) diff --git a/MMDevice/Makefile.am b/MMDevice/Makefile.am index 33444b2e0..19dd6dba4 100644 --- a/MMDevice/Makefile.am +++ b/MMDevice/Makefile.am @@ -36,5 +36,3 @@ install: install-am uninstall: uninstall-am rm -f $(DESTDIR)${libdir}/libMMDevice.a endif - -SUBDIRS = . unittest diff --git a/MMDevice/unittest/Makefile.am b/MMDevice/unittest/Makefile.am deleted file mode 100644 index e86d01c07..000000000 --- a/MMDevice/unittest/Makefile.am +++ /dev/null @@ -1,7 +0,0 @@ -check_PROGRAMS = \ - FloatPropertyTruncation-Tests \ - MMTime-Tests -AM_DEFAULT_SOURCE_EXT = .cpp -AM_CPPFLAGS = $(GMOCK_CPPFLAGS) -I.. -LDADD = ../../../testing/libgmock.la ../libMMDevice.la -TESTS = $(check_PROGRAMS) From 51adfab0a80e4957ed92e4b0145ddad70d5028e7 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 14 Dec 2023 16:17:30 -0600 Subject: [PATCH 108/141] MMDevice/MMCore: Use single executable for tests And use GoogleTest's default main() implementation. --- MMCore/unittest/APIError-Tests.cpp | 6 --- MMCore/unittest/CoreSanity-Tests.cpp | 8 +-- MMCore/unittest/Logger-Tests.cpp | 9 +--- .../LoggingSplitEntryIntoLines-Tests.cpp | 9 +--- MMCore/unittest/meson.build | 49 ++++--------------- .../FloatPropertyTruncation-Tests.cpp | 9 +--- MMDevice/unittest/MMTime-Tests.cpp | 8 +-- MMDevice/unittest/meson.build | 31 +++++------- 8 files changed, 26 insertions(+), 103 deletions(-) diff --git a/MMCore/unittest/APIError-Tests.cpp b/MMCore/unittest/APIError-Tests.cpp index 94b0557e1..66ab0df2e 100644 --- a/MMCore/unittest/APIError-Tests.cpp +++ b/MMCore/unittest/APIError-Tests.cpp @@ -84,10 +84,4 @@ TEST(APIErrorTests, DetectDeviceWithInvalidDevice) EXPECT_EQ(MM::Unimplemented, c.detectDevice("")); EXPECT_EQ(MM::Unimplemented, c.detectDevice("Blah")); EXPECT_EQ(MM::Unimplemented, c.detectDevice("Core")); -} - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); } \ No newline at end of file diff --git a/MMCore/unittest/CoreSanity-Tests.cpp b/MMCore/unittest/CoreSanity-Tests.cpp index 2cbd9ad2b..4abb5f5a2 100644 --- a/MMCore/unittest/CoreSanity-Tests.cpp +++ b/MMCore/unittest/CoreSanity-Tests.cpp @@ -22,10 +22,4 @@ TEST(CoreSanityTests, CreateAndReset) { CMMCore c; c.reset(); -} - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} +} \ No newline at end of file diff --git a/MMCore/unittest/Logger-Tests.cpp b/MMCore/unittest/Logger-Tests.cpp index 3eb892853..7da24b8c9 100644 --- a/MMCore/unittest/Logger-Tests.cpp +++ b/MMCore/unittest/Logger-Tests.cpp @@ -117,11 +117,4 @@ TEST(LoggerTests, AsyncAndThreaded) } for (unsigned i = 0; i < threads.size(); ++i) threads[i]->join(); -} - - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} +} \ No newline at end of file diff --git a/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp b/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp index 2ca1a0cb0..917bbe3a8 100644 --- a/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp +++ b/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp @@ -219,11 +219,4 @@ INSTANTIATE_TEST_SUITE_P(TwoSoftSplitCase, ::testing::Values( std::string(2 * MaxLogLineLen + 1, 'x'), std::string(3 * MaxLogLineLen - 1, 'x'), - std::string(3 * MaxLogLineLen, 'x'))); - - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} + std::string(3 * MaxLogLineLen, 'x'))); \ No newline at end of file diff --git a/MMCore/unittest/meson.build b/MMCore/unittest/meson.build index 05395a28c..92ca4b566 100644 --- a/MMCore/unittest/meson.build +++ b/MMCore/unittest/meson.build @@ -2,28 +2,18 @@ # of the supported build system for Micro-Manager or mmCoreAndDevices. gtest_proj = subproject('gtest') -gtest_dep = gtest_proj.get_variable('gtest_dep') +gtest_dep = gtest_proj.get_variable('gtest_main_dep') -# TODO: Use a single executable for all tests -- but that requires modifying -# the sources (to remove main()), so needs to wait until we remove these tests -# from the Automake build. - -mmcore_apierror_test_exe = executable( - 'MMCoreAPIErrorTest', - sources: 'APIError-Tests.cpp', - include_directories: mmcore_include_dir, - link_with: mmcore_lib, - dependencies: [ - mmdevice_dep, - gtest_dep, - ], +mmcore_test_sources = files( + 'APIError-Tests.cpp', + 'CoreSanity-Tests.cpp', + 'Logger-Tests.cpp', + 'LoggingSplitEntryIntoLines-Tests.cpp', ) -test('MMCore APIError test', mmcore_apierror_test_exe) - -mmcore_logger_test_exe = executable( - 'MMCoreLoggerTest', - sources: 'Logger-Tests.cpp', +mmcore_test_exe = executable( + 'MMCoreTests', + sources: mmcore_test_sources, include_directories: mmcore_include_dir, link_with: mmcore_lib, dependencies: [ @@ -35,23 +25,4 @@ mmcore_logger_test_exe = executable( ], ) -test('MMCore Logger test', mmcore_logger_test_exe) - -mmcore_loggingsplitentryintolines_test_exe = executable( - 'MMCoreLoggingSplitEntryIntoLinesTest', - sources: 'LoggingSplitEntryIntoLines-Tests.cpp', - include_directories: mmcore_include_dir, - link_with: mmcore_lib, - dependencies: [ - mmdevice_dep, - gtest_dep, - ], - cpp_args: [ - '-D_CRT_SECURE_NO_WARNINGS', # TODO Eliminate the need - ], -) - -test( - 'MMCore LoggingSplitEntryIntoLines test', - mmcore_loggingsplitentryintolines_test_exe -) +test('MMCore tests', mmcore_test_exe) \ No newline at end of file diff --git a/MMDevice/unittest/FloatPropertyTruncation-Tests.cpp b/MMDevice/unittest/FloatPropertyTruncation-Tests.cpp index f6ee76d22..abcfccb3c 100644 --- a/MMDevice/unittest/FloatPropertyTruncation-Tests.cpp +++ b/MMDevice/unittest/FloatPropertyTruncation-Tests.cpp @@ -65,11 +65,4 @@ TEST(FloatPropertyTruncationTests, UpperLimitIsTruncatedDown) ASSERT_DOUBLE_EQ(-0.0001, fp.GetUpperLimit()); ASSERT_TRUE(fp.SetLimits(-1000.0, -0.00011)); ASSERT_DOUBLE_EQ(-0.0002, fp.GetUpperLimit()); -} - - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} +} \ No newline at end of file diff --git a/MMDevice/unittest/MMTime-Tests.cpp b/MMDevice/unittest/MMTime-Tests.cpp index e31fc522a..174b5d6c2 100644 --- a/MMDevice/unittest/MMTime-Tests.cpp +++ b/MMDevice/unittest/MMTime-Tests.cpp @@ -66,10 +66,4 @@ TEST(MMTimeTests, ToNumbers) { ASSERT_DOUBLE_EQ(1.0, MMTime(1.0).getUsec()); ASSERT_DOUBLE_EQ(1.0, MMTime(1000.0).getMsec()); -} - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} +} \ No newline at end of file diff --git a/MMDevice/unittest/meson.build b/MMDevice/unittest/meson.build index 0b9296ac2..603a26673 100644 --- a/MMDevice/unittest/meson.build +++ b/MMDevice/unittest/meson.build @@ -2,31 +2,22 @@ # of the supported build system for Micro-Manager or mmCoreAndDevices. gtest_proj = subproject('gtest') -gtest_dep = gtest_proj.get_variable('gtest_dep') +gtest_dep = gtest_proj.get_variable('gtest_main_dep') -# TODO: Use a single executable for all tests -- but that requires modifying -# the sources (to remove main()), so needs to wait until we remove these tests -# from the Automake build. - -mmdevice_floatpropertytruncation_test_exe = executable( - 'MMDeviceFloatPropertyTruncationTest', - sources: 'FloatPropertyTruncation-Tests.cpp', - include_directories: mmdevice_include_dir, - link_with: mmdevice_lib, - dependencies: gtest_dep, +mmdevice_test_sources = files( + 'FloatPropertyTruncation-Tests.cpp', + 'MMTime-Tests.cpp', ) -test( - 'MMDevice FloatPropertyTruncation test', - mmdevice_floatpropertytruncation_test_exe, -) - -mmdevice_mmtime_test_exe = executable( - 'MMDeviceMMTimeTest', - sources: 'MMTime-Tests.cpp', +mmdevice_test_exe = executable( + 'MMDeviceTests', + sources: mmdevice_test_sources, include_directories: mmdevice_include_dir, link_with: mmdevice_lib, dependencies: gtest_dep, ) -test('MMDevice MMTime test', mmdevice_mmtime_test_exe) +test( + 'MMDevice unit tests', + mmdevice_test_exe, +) \ No newline at end of file From 4b3071e29fac7d88d28b52cc511c8e2973a20da1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 01:04:32 +0000 Subject: [PATCH 109/141] Update secret-device-adapters-commit to latest --- secret-device-adapters-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret-device-adapters-commit b/secret-device-adapters-commit index 5ed7c7885..8b35b92cd 100644 --- a/secret-device-adapters-commit +++ b/secret-device-adapters-commit @@ -1 +1 @@ -1f81fd72c9ec557c32c44e03ed91a247e14df2ce +da3dc9f547ec408a17f4610e7e01b4b870c5c546 From 9d625c384b4da6dfe77b800fb0a4ccc610c0cd34 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 18 Dec 2023 14:58:32 -0600 Subject: [PATCH 110/141] MMDevice: Switch from GoogleTest to Catch2 --- MMDevice/subprojects/.gitignore | 2 +- MMDevice/subprojects/catch2.wrap | 11 +++ MMDevice/subprojects/gtest.wrap | 16 ---- .../FloatPropertyTruncation-Tests.cpp | 91 +++++++++--------- MMDevice/unittest/MMTime-Tests.cpp | 92 +++++++++---------- MMDevice/unittest/meson.build | 9 +- 6 files changed, 107 insertions(+), 114 deletions(-) create mode 100644 MMDevice/subprojects/catch2.wrap delete mode 100644 MMDevice/subprojects/gtest.wrap diff --git a/MMDevice/subprojects/.gitignore b/MMDevice/subprojects/.gitignore index 8b7ac1334..9a8e50c79 100644 --- a/MMDevice/subprojects/.gitignore +++ b/MMDevice/subprojects/.gitignore @@ -7,4 +7,4 @@ /*.wrap # Do not ignore wraps we provide -!/gtest.wrap \ No newline at end of file +!/catch2.wrap diff --git a/MMDevice/subprojects/catch2.wrap b/MMDevice/subprojects/catch2.wrap new file mode 100644 index 000000000..691d39c85 --- /dev/null +++ b/MMDevice/subprojects/catch2.wrap @@ -0,0 +1,11 @@ +[wrap-file] +directory = Catch2-3.4.0 +source_url = https://github.com/catchorg/Catch2/archive/v3.4.0.tar.gz +source_filename = Catch2-3.4.0.tar.gz +source_hash = 122928b814b75717316c71af69bd2b43387643ba076a6ec16e7882bfb2dfacbb +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.4.0-1/Catch2-3.4.0.tar.gz +wrapdb_version = 3.4.0-1 + +[provide] +catch2 = catch2_dep +catch2-with-main = catch2_with_main_dep diff --git a/MMDevice/subprojects/gtest.wrap b/MMDevice/subprojects/gtest.wrap deleted file mode 100644 index 8c067ff20..000000000 --- a/MMDevice/subprojects/gtest.wrap +++ /dev/null @@ -1,16 +0,0 @@ -[wrap-file] -directory = googletest-1.14.0 -source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz -source_filename = gtest-1.14.0.tar.gz -source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7 -patch_filename = gtest_1.14.0-1_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-1/get_patch -patch_hash = 2e693c7d3f9370a7aa6dac802bada0874d3198ad4cfdf75647b818f691182b50 -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-1/gtest-1.14.0.tar.gz -wrapdb_version = 1.14.0-1 - -[provide] -gtest = gtest_dep -gtest_main = gtest_main_dep -gmock = gmock_dep -gmock_main = gmock_main_dep diff --git a/MMDevice/unittest/FloatPropertyTruncation-Tests.cpp b/MMDevice/unittest/FloatPropertyTruncation-Tests.cpp index abcfccb3c..9c041baec 100644 --- a/MMDevice/unittest/FloatPropertyTruncation-Tests.cpp +++ b/MMDevice/unittest/FloatPropertyTruncation-Tests.cpp @@ -1,68 +1,67 @@ -#include +#include #include "Property.h" -using namespace MM; +namespace MM { - -TEST(FloatPropertyTruncationTests, SetValueIsTruncatedTo4Digits) +TEST_CASE("Set value is truncated to 4 digits", "[FloatPropertyTruncation]") { FloatProperty fp("TestProp"); double v; - ASSERT_TRUE(fp.Set(0.00004)); - ASSERT_TRUE(fp.Get(v)); - ASSERT_DOUBLE_EQ(0.0, v); + CHECK(fp.Set(0.00004)); + CHECK(fp.Get(v)); + CHECK(v == 0.0); - ASSERT_TRUE(fp.Set(0.00005)); - ASSERT_TRUE(fp.Get(v)); - ASSERT_DOUBLE_EQ(0.0001, v); + CHECK(fp.Set(0.00005)); + CHECK(fp.Get(v)); + CHECK(v == 0.0001); - ASSERT_TRUE(fp.Set(-0.00004)); - ASSERT_TRUE(fp.Get(v)); - ASSERT_DOUBLE_EQ(0.0, v); + CHECK(fp.Set(-0.00004)); + CHECK(fp.Get(v)); + CHECK(v == 0.0); - ASSERT_TRUE(fp.Set(-0.00005)); - ASSERT_TRUE(fp.Get(v)); - ASSERT_DOUBLE_EQ(-0.0001, v); + CHECK(fp.Set(-0.00005)); + CHECK(fp.Get(v)); + CHECK(v == -0.0001); } - -TEST(FloatPropertyTruncationTests, LowerLimitIsTruncatedUp) +TEST_CASE("Lower limit is truncated up", "[FloatPropertyTruncation]") { FloatProperty fp("TestProp"); - ASSERT_TRUE(fp.SetLimits(0.0, 1000.0)); - ASSERT_DOUBLE_EQ(0.0, fp.GetLowerLimit()); - ASSERT_TRUE(fp.SetLimits(0.00001, 1000.0)); - ASSERT_DOUBLE_EQ(0.0001, fp.GetLowerLimit()); - ASSERT_TRUE(fp.SetLimits(0.00011, 1000.0)); - ASSERT_DOUBLE_EQ(0.0002, fp.GetLowerLimit()); - - ASSERT_TRUE(fp.SetLimits(-0.0, 1000.0)); - ASSERT_DOUBLE_EQ(0.0, fp.GetLowerLimit()); - ASSERT_TRUE(fp.SetLimits(-0.00001, 1000.0)); - ASSERT_DOUBLE_EQ(0.0, fp.GetLowerLimit()); - ASSERT_TRUE(fp.SetLimits(-0.00011, 1000.0)); - ASSERT_DOUBLE_EQ(-0.0001, fp.GetLowerLimit()); + CHECK(fp.SetLimits(0.0, 1000.0)); + CHECK(fp.GetLowerLimit() == 0.0); + CHECK(fp.SetLimits(0.00001, 1000.0)); + CHECK(fp.GetLowerLimit() == 0.0001); + CHECK(fp.SetLimits(0.00011, 1000.0)); + CHECK(fp.GetLowerLimit() == 0.0002); + + CHECK(fp.SetLimits(-0.0, 1000.0)); + CHECK(fp.GetLowerLimit() == 0.0); + CHECK(fp.SetLimits(-0.00001, 1000.0)); + CHECK(fp.GetLowerLimit() == 0.0); + CHECK(fp.SetLimits(-0.00011, 1000.0)); + CHECK(fp.GetLowerLimit() == -0.0001); } - -TEST(FloatPropertyTruncationTests, UpperLimitIsTruncatedDown) +TEST_CASE("Upper limit is truncated down", "[FloatPropertyTruncation]") { FloatProperty fp("TestProp"); - ASSERT_TRUE(fp.SetLimits(-1000.0, 0.0)); - ASSERT_DOUBLE_EQ(0.0, fp.GetUpperLimit()); - ASSERT_TRUE(fp.SetLimits(-1000.0, 0.00001)); - ASSERT_DOUBLE_EQ(0.0, fp.GetUpperLimit()); - ASSERT_TRUE(fp.SetLimits(-1000.0, 0.00011)); - ASSERT_DOUBLE_EQ(0.0001, fp.GetUpperLimit()); + CHECK(fp.SetLimits(-1000.0, 0.0)); + CHECK(fp.GetUpperLimit() == 0.0); + CHECK(fp.SetLimits(-1000.0, 0.00001)); + CHECK(fp.GetUpperLimit() == 0.0); + CHECK(fp.SetLimits(-1000.0, 0.00011)); + CHECK(fp.GetUpperLimit() == 0.0001); + + CHECK(fp.SetLimits(-1000.0, -0.0)); + CHECK(fp.GetUpperLimit() == 0.0); + CHECK(fp.SetLimits(-1000.0, -0.00001)); + CHECK(fp.GetUpperLimit() == -0.0001); + CHECK(fp.SetLimits(-1000.0, -0.00011)); + CHECK(fp.GetUpperLimit() == -0.0002); +} - ASSERT_TRUE(fp.SetLimits(-1000.0, -0.0)); - ASSERT_DOUBLE_EQ(0.0, fp.GetUpperLimit()); - ASSERT_TRUE(fp.SetLimits(-1000.0, -0.00001)); - ASSERT_DOUBLE_EQ(-0.0001, fp.GetUpperLimit()); - ASSERT_TRUE(fp.SetLimits(-1000.0, -0.00011)); - ASSERT_DOUBLE_EQ(-0.0002, fp.GetUpperLimit()); -} \ No newline at end of file +} // namespace MM \ No newline at end of file diff --git a/MMDevice/unittest/MMTime-Tests.cpp b/MMDevice/unittest/MMTime-Tests.cpp index 174b5d6c2..3dc3ad150 100644 --- a/MMDevice/unittest/MMTime-Tests.cpp +++ b/MMDevice/unittest/MMTime-Tests.cpp @@ -1,69 +1,65 @@ -#include +#include #include "MMDevice.h" -using namespace MM; +namespace MM { - -TEST(MMTimeTests, RoundTripNegativeValues) +TEST_CASE("MMTime round trip negative values", "[MMTime]") { - ASSERT_DOUBLE_EQ(0.0, MMTime(-0.4).getUsec()); - ASSERT_DOUBLE_EQ(-1.0, MMTime(-1.0).getUsec()); - ASSERT_DOUBLE_EQ(-1'000'000.0, MMTime(-1'000'000.0).getUsec()); + CHECK(MMTime(-0.4).getUsec() == 0.0); + CHECK(MMTime(-1.0).getUsec() == -1.0); + CHECK(MMTime(-1'000'000.0).getUsec() == -1'000'000.0); - ASSERT_DOUBLE_EQ(-1.0, MMTime(0, -1).getUsec()); - ASSERT_DOUBLE_EQ(-1'000'000.0, MMTime(-1, 0).getUsec()); - ASSERT_DOUBLE_EQ(-999'999.0, MMTime(-1, 1).getUsec()); - ASSERT_DOUBLE_EQ(-1'000'001.0, MMTime(-1, -1).getUsec()); + CHECK(MMTime(0, -1).getUsec() == -1.0); + CHECK(MMTime(-1, 0).getUsec() == -1'000'000.0); + CHECK(MMTime(-1, 1).getUsec() == -999'999.0); + CHECK(MMTime(-1, -1).getUsec() == -1'000'001.0); } - -TEST(MMTimeTests, ToString) +TEST_CASE("MMTime to string", "[MMTime]") { - using namespace std::literals::string_literals; - ASSERT_EQ("0.000000"s, MMTime{}.toString()); - ASSERT_EQ("0.000001"s, MMTime(0, 1).toString()); - ASSERT_EQ("-0.000001"s, MMTime(0, -1).toString()); - ASSERT_EQ("-0.000001"s, MMTime(-1, 999'999).toString()); - ASSERT_EQ("1.000000"s, MMTime(1, 0).toString()); - ASSERT_EQ("-1.000000"s, MMTime(-1, 0).toString()); - ASSERT_EQ("-1.000001"s, MMTime(-1, -1).toString()); - ASSERT_EQ("-0.999999"s, MMTime(-1, 1).toString()); + using namespace std::string_literals; + CHECK(MMTime{}.toString() == "0.000000"s); + CHECK(MMTime(0, 1).toString() == "0.000001"s); + CHECK(MMTime(0, -1).toString() == "-0.000001"s); + CHECK(MMTime(-1, 999'999).toString() == "-0.000001"s); + CHECK(MMTime(1, 0).toString() == "1.000000"s); + CHECK(MMTime(-1, 0).toString() == "-1.000000"s); + CHECK(MMTime(-1, -1).toString() == "-1.000001"s); + CHECK(MMTime(-1, 1).toString() == "-0.999999"s); } - -TEST(MMTimeTests, Arithmetic) +TEST_CASE("MMTime arithmetic", "[MMTime]") { - ASSERT_EQ(MMTime(5.0), MMTime(3.0) + MMTime(2.0)); - ASSERT_EQ(MMTime(1.0), MMTime(3.0) - MMTime(2.0)); - ASSERT_EQ(MMTime(-5.0), MMTime(-3.0) + MMTime(-2.0)); - ASSERT_EQ(MMTime(-1.0), MMTime(-3.0) - MMTime(-2.0)); + CHECK(MMTime(3.0) + MMTime(2.0) == MMTime(5.0)); + CHECK(MMTime(3.0) - MMTime(2.0) == MMTime(1.0)); + CHECK(MMTime(-3.0) + MMTime(-2.0) == MMTime(-5.0)); + CHECK(MMTime(-3.0) - MMTime(-2.0) == MMTime(-1.0)); } - -TEST(MMTimeTests, Comparison) +TEST_CASE("MMTime comparison", "[MMTime]") { - ASSERT_TRUE(MMTime(5.0) == MMTime(5.0)); - ASSERT_TRUE(MMTime(5.0) >= MMTime(5.0)); - ASSERT_TRUE(MMTime(5.0) <= MMTime(5.0)); - ASSERT_FALSE(MMTime(5.0) != MMTime(5.0)); - ASSERT_TRUE(MMTime(3.0) > MMTime(2.0)); - ASSERT_TRUE(MMTime(3.0) >= MMTime(2.0)); - ASSERT_TRUE(MMTime(2.0) < MMTime(3.0)); - ASSERT_TRUE(MMTime(2.0) <= MMTime(3.0)); + CHECK(MMTime(5.0) == MMTime(5.0)); + CHECK(MMTime(5.0) >= MMTime(5.0)); + CHECK(MMTime(5.0) <= MMTime(5.0)); + CHECK_FALSE(MMTime(5.0) != MMTime(5.0)); + CHECK(MMTime(3.0) > MMTime(2.0)); + CHECK(MMTime(3.0) >= MMTime(2.0)); + CHECK(MMTime(2.0) < MMTime(3.0)); + CHECK(MMTime(2.0) <= MMTime(3.0)); } - -TEST(MMTimeTests, FromNumbers) +TEST_CASE("MMTime from numbers", "[MMTime]") { - ASSERT_EQ(MMTime(1.0), MMTime::fromUs(1)); - ASSERT_EQ(MMTime(1'000.0), MMTime::fromMs(1)); - ASSERT_EQ(MMTime(1'000'000.0), MMTime::fromSeconds(1)); + CHECK(MMTime::fromUs(1) == MMTime(1.0)); + CHECK(MMTime::fromMs(1) == MMTime(1'000.0)); + CHECK(MMTime::fromSeconds(1) == MMTime(1'000'000.0)); } - -TEST(MMTimeTests, ToNumbers) +TEST_CASE("MMTime to umbers", "[MMTime]") { - ASSERT_DOUBLE_EQ(1.0, MMTime(1.0).getUsec()); - ASSERT_DOUBLE_EQ(1.0, MMTime(1000.0).getMsec()); -} \ No newline at end of file + CHECK(MMTime(1.0).getUsec() == 1.0); + CHECK(MMTime(1000.0).getMsec() == 1.0); +} + +} // namespace MM \ No newline at end of file diff --git a/MMDevice/unittest/meson.build b/MMDevice/unittest/meson.build index 603a26673..7c4f77276 100644 --- a/MMDevice/unittest/meson.build +++ b/MMDevice/unittest/meson.build @@ -1,8 +1,11 @@ # This Meson script is experimental and potentially incomplete. It is not part # of the supported build system for Micro-Manager or mmCoreAndDevices. -gtest_proj = subproject('gtest') -gtest_dep = gtest_proj.get_variable('gtest_main_dep') +catch2_with_main_dep = dependency( + 'catch2-with-main', + allow_fallback: true, + include_type: 'system', +) mmdevice_test_sources = files( 'FloatPropertyTruncation-Tests.cpp', @@ -14,7 +17,7 @@ mmdevice_test_exe = executable( sources: mmdevice_test_sources, include_directories: mmdevice_include_dir, link_with: mmdevice_lib, - dependencies: gtest_dep, + dependencies: catch2_with_main_dep, ) test( From b4a80f1361059e29804be67f95f58956938dd464 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 18 Dec 2023 16:48:30 -0600 Subject: [PATCH 111/141] MMCore: Switch from GoogleTest to Catch2 --- MMCore/subprojects/.gitignore | 2 +- MMCore/subprojects/catch2.wrap | 11 + MMCore/subprojects/gtest.wrap | 16 - MMCore/unittest/APIError-Tests.cpp | 78 ++--- MMCore/unittest/CoreCreateDestroy-Tests.cpp | 25 ++ MMCore/unittest/CoreSanity-Tests.cpp | 25 -- MMCore/unittest/Logger-Tests.cpp | 21 +- .../LoggingSplitEntryIntoLines-Tests.cpp | 331 ++++++++---------- MMCore/unittest/meson.build | 11 +- 9 files changed, 239 insertions(+), 281 deletions(-) create mode 100644 MMCore/subprojects/catch2.wrap delete mode 100644 MMCore/subprojects/gtest.wrap create mode 100644 MMCore/unittest/CoreCreateDestroy-Tests.cpp delete mode 100644 MMCore/unittest/CoreSanity-Tests.cpp diff --git a/MMCore/subprojects/.gitignore b/MMCore/subprojects/.gitignore index 479f9fa9c..a1beaa0c5 100644 --- a/MMCore/subprojects/.gitignore +++ b/MMCore/subprojects/.gitignore @@ -9,4 +9,4 @@ /*.wrap # Do not ignore wraps we provide -!/gtest.wrap \ No newline at end of file +!/catch2.wrap diff --git a/MMCore/subprojects/catch2.wrap b/MMCore/subprojects/catch2.wrap new file mode 100644 index 000000000..691d39c85 --- /dev/null +++ b/MMCore/subprojects/catch2.wrap @@ -0,0 +1,11 @@ +[wrap-file] +directory = Catch2-3.4.0 +source_url = https://github.com/catchorg/Catch2/archive/v3.4.0.tar.gz +source_filename = Catch2-3.4.0.tar.gz +source_hash = 122928b814b75717316c71af69bd2b43387643ba076a6ec16e7882bfb2dfacbb +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.4.0-1/Catch2-3.4.0.tar.gz +wrapdb_version = 3.4.0-1 + +[provide] +catch2 = catch2_dep +catch2-with-main = catch2_with_main_dep diff --git a/MMCore/subprojects/gtest.wrap b/MMCore/subprojects/gtest.wrap deleted file mode 100644 index 8c067ff20..000000000 --- a/MMCore/subprojects/gtest.wrap +++ /dev/null @@ -1,16 +0,0 @@ -[wrap-file] -directory = googletest-1.14.0 -source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz -source_filename = gtest-1.14.0.tar.gz -source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7 -patch_filename = gtest_1.14.0-1_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-1/get_patch -patch_hash = 2e693c7d3f9370a7aa6dac802bada0874d3198ad4cfdf75647b818f691182b50 -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-1/gtest-1.14.0.tar.gz -wrapdb_version = 1.14.0-1 - -[provide] -gtest = gtest_dep -gtest_main = gtest_main_dep -gmock = gmock_dep -gmock_main = gmock_main_dep diff --git a/MMCore/unittest/APIError-Tests.cpp b/MMCore/unittest/APIError-Tests.cpp index 66ab0df2e..767daa9db 100644 --- a/MMCore/unittest/APIError-Tests.cpp +++ b/MMCore/unittest/APIError-Tests.cpp @@ -1,8 +1,8 @@ -#include +#include #include "MMCore.h" -TEST(APIErrorTests, SetFocusDirectionWithInvalidDevice) +TEST_CASE("setFocusDirection with invalid device", "[APIError]") { CMMCore c; // Must not throw or crash @@ -20,68 +20,68 @@ TEST(APIErrorTests, SetFocusDirectionWithInvalidDevice) c.setFocusDirection("Core", -1); } -TEST(APIErrorTests, GetNumberOfStatesWithInvalidDevice) +TEST_CASE("getNumberOfStates with invalid device", "[APIError]") { CMMCore c; // Must not throw or crash - EXPECT_EQ(-1, c.getNumberOfStates(nullptr)); - EXPECT_EQ(-1, c.getNumberOfStates("")); - EXPECT_EQ(-1, c.getNumberOfStates("Blah")); - EXPECT_EQ(-1, c.getNumberOfStates("Core")); + CHECK(c.getNumberOfStates(nullptr) == -1); + CHECK(c.getNumberOfStates("") == -1); + CHECK(c.getNumberOfStates("Blah") == -1); + CHECK(c.getNumberOfStates("Core") == -1); } -TEST(APIErrorTests, GetAvailableConfigsWithInvalidGroup) +TEST_CASE("getAvailableConfigs with invalid group", "[APIError]") { CMMCore c; - EXPECT_TRUE(c.getAvailableConfigs(nullptr).empty()); - EXPECT_TRUE(c.getAvailableConfigs("").empty()); - EXPECT_TRUE(c.getAvailableConfigs("Blah").empty()); + CHECK(c.getAvailableConfigs(nullptr).empty()); + CHECK(c.getAvailableConfigs("").empty()); + CHECK(c.getAvailableConfigs("Blah").empty()); } -TEST(APIErrorTests, GetPixelSizeUmWithNoConfig) +TEST_CASE("getPixelSizeUm with no config", "[APIError]") { CMMCore c; - EXPECT_EQ(0.0, c.getPixelSizeUm(false)); - EXPECT_EQ(0.0, c.getPixelSizeUm(true)); + CHECK(c.getPixelSizeUm(false) == 0.0); + CHECK(c.getPixelSizeUm(true) == 0.0); } -TEST(APIErrorTests, IsConfigDefinedWithNullArgs) +TEST_CASE("isConfigDefined with null args", "[APIError]") { CMMCore c; - EXPECT_FALSE(c.isConfigDefined(nullptr, "Blah")); - EXPECT_FALSE(c.isConfigDefined("Blah", nullptr)); - EXPECT_FALSE(c.isConfigDefined(nullptr, nullptr)); - EXPECT_FALSE(c.isConfigDefined("Blah", "Blah")); - EXPECT_FALSE(c.isConfigDefined(nullptr, "")); - EXPECT_FALSE(c.isConfigDefined("", nullptr)); - EXPECT_FALSE(c.isConfigDefined(nullptr, nullptr)); - EXPECT_FALSE(c.isConfigDefined("", "")); - EXPECT_FALSE(c.isConfigDefined("", "Blah")); - EXPECT_FALSE(c.isConfigDefined("Blah", "")); + CHECK_FALSE(c.isConfigDefined(nullptr, "Blah")); + CHECK_FALSE(c.isConfigDefined("Blah", nullptr)); + CHECK_FALSE(c.isConfigDefined(nullptr, nullptr)); + CHECK_FALSE(c.isConfigDefined("Blah", "Blah")); + CHECK_FALSE(c.isConfigDefined(nullptr, "")); + CHECK_FALSE(c.isConfigDefined("", nullptr)); + CHECK_FALSE(c.isConfigDefined(nullptr, nullptr)); + CHECK_FALSE(c.isConfigDefined("", "")); + CHECK_FALSE(c.isConfigDefined("", "Blah")); + CHECK_FALSE(c.isConfigDefined("Blah", "")); } -TEST(APIErrorTests, IsGroupDefinedWithNullArg) +TEST_CASE("isGroupDefined with null arg", "[APIError]") { CMMCore c; - EXPECT_FALSE(c.isGroupDefined(nullptr)); - EXPECT_FALSE(c.isGroupDefined("")); - EXPECT_FALSE(c.isGroupDefined("Blah")); + CHECK_FALSE(c.isGroupDefined(nullptr)); + CHECK_FALSE(c.isGroupDefined("")); + CHECK_FALSE(c.isGroupDefined("Blah")); } -TEST(APIErrorTests, SupportsDeviceDetectionWithInvalidDevice) +TEST_CASE("supportsDeviceDetection with invalid device", "[APIError]") { CMMCore c; - EXPECT_FALSE(c.supportsDeviceDetection(nullptr)); - EXPECT_FALSE(c.supportsDeviceDetection("")); - EXPECT_FALSE(c.supportsDeviceDetection("Blah")); - EXPECT_FALSE(c.supportsDeviceDetection("Core")); + CHECK_FALSE(c.supportsDeviceDetection(nullptr)); + CHECK_FALSE(c.supportsDeviceDetection("")); + CHECK_FALSE(c.supportsDeviceDetection("Blah")); + CHECK_FALSE(c.supportsDeviceDetection("Core")); } -TEST(APIErrorTests, DetectDeviceWithInvalidDevice) +TEST_CASE("detectDevice with invalid device", "[APIError]") { CMMCore c; - EXPECT_EQ(MM::Unimplemented, c.detectDevice(nullptr)); - EXPECT_EQ(MM::Unimplemented, c.detectDevice("")); - EXPECT_EQ(MM::Unimplemented, c.detectDevice("Blah")); - EXPECT_EQ(MM::Unimplemented, c.detectDevice("Core")); + CHECK(c.detectDevice(nullptr) == MM::Unimplemented); + CHECK(c.detectDevice("") == MM::Unimplemented); + CHECK(c.detectDevice("Blah") == MM::Unimplemented); + CHECK(c.detectDevice("Core") == MM::Unimplemented); } \ No newline at end of file diff --git a/MMCore/unittest/CoreCreateDestroy-Tests.cpp b/MMCore/unittest/CoreCreateDestroy-Tests.cpp new file mode 100644 index 000000000..45abbbe55 --- /dev/null +++ b/MMCore/unittest/CoreCreateDestroy-Tests.cpp @@ -0,0 +1,25 @@ +#include + +#include "MMCore.h" + +TEST_CASE("CMMCore create and destroy twice", "[CoreCreateDestroy]") +{ + { + CMMCore c1; + } + { + CMMCore c2; + } +} + +TEST_CASE("CMMCore create two at once", "[CoreCreateDestroy]") +{ + CMMCore c1; + CMMCore c2; +} + +TEST_CASE("CMMCore create and reset", "[CoreCreateDestroy]") +{ + CMMCore c; + c.reset(); +} \ No newline at end of file diff --git a/MMCore/unittest/CoreSanity-Tests.cpp b/MMCore/unittest/CoreSanity-Tests.cpp deleted file mode 100644 index 4abb5f5a2..000000000 --- a/MMCore/unittest/CoreSanity-Tests.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include - -#include "MMCore.h" - -TEST(CoreSanityTests, CreateAndDestroyTwice) -{ - { - CMMCore c1; - } - { - CMMCore c2; - } -} - -TEST(CoreSanityTests, CreateTwoAtOnce) -{ - CMMCore c1; - CMMCore c2; -} - -TEST(CoreSanityTests, CreateAndReset) -{ - CMMCore c; - c.reset(); -} \ No newline at end of file diff --git a/MMCore/unittest/Logger-Tests.cpp b/MMCore/unittest/Logger-Tests.cpp index 7da24b8c9..e1b6d0922 100644 --- a/MMCore/unittest/Logger-Tests.cpp +++ b/MMCore/unittest/Logger-Tests.cpp @@ -1,4 +1,4 @@ -#include +#include #include "Logging/Logging.h" @@ -8,10 +8,10 @@ #include #include -using namespace mm::logging; +namespace mm { +namespace logging { - -TEST(LoggerTests, BasicSynchronous) +TEST_CASE("synchronous logger basics", "[Logger]") { std::shared_ptr c = std::make_shared(); @@ -26,7 +26,7 @@ TEST(LoggerTests, BasicSynchronous) } -TEST(LoggerTests, BasicAsynchronous) +TEST_CASE("asynchronous logger basics", "[Logger]") { std::shared_ptr c = std::make_shared(); @@ -41,7 +41,7 @@ TEST(LoggerTests, BasicAsynchronous) } -TEST(LoggerTests, BasicLogStream) +TEST_CASE("log stream basics", "[Logger]") { std::shared_ptr c = std::make_shared(); @@ -80,7 +80,7 @@ class LoggerTestThreadFunc }; -TEST(LoggerTests, SyncAndThreaded) +TEST_CASE("sync logger on thread", "[Logger]") { std::shared_ptr c = std::make_shared(); @@ -100,7 +100,7 @@ TEST(LoggerTests, SyncAndThreaded) } -TEST(LoggerTests, AsyncAndThreaded) +TEST_CASE("async logger on thread", "[Logger]") { std::shared_ptr c = std::make_shared(); @@ -117,4 +117,7 @@ TEST(LoggerTests, AsyncAndThreaded) } for (unsigned i = 0; i < threads.size(); ++i) threads[i]->join(); -} \ No newline at end of file +} + +} // namespace logging +} // namespace mm \ No newline at end of file diff --git a/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp b/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp index 917bbe3a8..8ec4bab84 100644 --- a/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp +++ b/MMCore/unittest/LoggingSplitEntryIntoLines-Tests.cpp @@ -1,4 +1,4 @@ -#include +#include #include "Logging/GenericLinePacket.h" #include "Logging/Logging.h" @@ -9,214 +9,171 @@ #include #include -using namespace mm::logging; +namespace mm { +namespace logging { -typedef Metadata MetadataType; -typedef internal::GenericPacketArray PacketArrayType; -const size_t MaxLogLineLen = - internal::GenericLinePacket::PacketTextLen; - - -class SplitEntryIntoPacketsTest : public ::testing::Test +TEST_CASE("split entry into lines", "[Logging]") { -public: - SplitEntryIntoPacketsTest() : - loggerData_("component"), - entryData_(LogLevelInfo) - {} + Metadata::LoggerDataType loggerData("component"); + Metadata::EntryDataType entryData(LogLevelInfo); + Metadata::StampDataType stampData; + stampData.Stamp(); -protected: - MetadataType::LoggerDataType loggerData_; - MetadataType::EntryDataType entryData_; - MetadataType::StampDataType stampData_; - - std::vector< internal::GenericLinePacket > result_; - virtual void SetUp() + SECTION("empty result") { - stampData_.Stamp(); - } - virtual void Split(const char* s) - { - PacketArrayType array; - array.AppendEntry(loggerData_, entryData_, stampData_, s); - std::copy(array.Begin(), array.End(), std::back_inserter(result_)); + const char *testStr = GENERATE( + "", "\r", "\n", "\r\r", "\r\n", "\n\n", + "\r\r\r", "\r\r\n", "\r\n\r", "\r\n\n", + "\n\r\r", "\n\r\n", "\n\n\r", "\n\n\n"); + internal::GenericPacketArray array; + array.AppendEntry(loggerData, entryData, stampData, testStr); + std::vector> result; + std::copy(array.Begin(), array.End(), std::back_inserter(result)); + CHECK(result.size() == 1); + CHECK_THAT(result[0].GetText(), Catch::Matchers::Equals("")); } -}; - -class SplitEntryIntoPacketsParameterizedTest : - public SplitEntryIntoPacketsTest, - public ::testing::WithParamInterface -{ - virtual void SetUp() + SECTION("single-char result") { - SplitEntryIntoPacketsTest::SetUp(); - Split(GetParam().c_str()); - } -}; - - -class SplitEntryIntoPacketsEmptyResultTest : - public SplitEntryIntoPacketsParameterizedTest -{}; - -TEST_P(SplitEntryIntoPacketsEmptyResultTest, EmptyResult) -{ - ASSERT_EQ(1, result_.size()); - EXPECT_STREQ("", result_[0].GetText()); -} - -INSTANTIATE_TEST_SUITE_P(NewlinesCase, SplitEntryIntoPacketsEmptyResultTest, - ::testing::Values("", "\r", "\n", "\r\r", "\r\n", "\n\n", - "\r\r\r", "\r\r\n", "\r\n\r", "\r\n\n", - "\n\r\r", "\n\r\n", "\n\n\r", "\n\n\n")); - - -class SplitEntryIntoPacketsSingleCharResultTest : - public SplitEntryIntoPacketsParameterizedTest -{}; - -TEST_P(SplitEntryIntoPacketsSingleCharResultTest, SingleXResult) -{ - ASSERT_EQ(1, result_.size()); - EXPECT_STREQ("X", result_[0].GetText()); -} - -INSTANTIATE_TEST_SUITE_P(XFollowedByNewlinesCase, - SplitEntryIntoPacketsSingleCharResultTest, - ::testing::Values("X", "X\r", "X\n", "X\r\r", "X\r\n", "X\n\n", + const char *testStr = GENERATE( + "X", "X\r", "X\n", "X\r\r", "X\r\n", "X\n\n", "X\r\r\r", "X\r\r\n", "X\r\n\r", "X\r\n\n", - "X\n\r\r", "X\n\r\n", "X\n\n\r", "X\n\n\n")); - - -class SplitEntryIntoPacketsTwoLineResultTest : - public SplitEntryIntoPacketsParameterizedTest -{}; - -TEST_P(SplitEntryIntoPacketsTwoLineResultTest, TwoLineXYResult) -{ - ASSERT_EQ(2, result_.size()); - EXPECT_EQ(internal::PacketStateEntryFirstLine, result_[0].GetPacketState()); - EXPECT_STREQ("X", result_[0].GetText()); - EXPECT_EQ(internal::PacketStateNewLine, result_[1].GetPacketState()); - EXPECT_STREQ("Y", result_[1].GetText()); -} - -INSTANTIATE_TEST_SUITE_P(XNewlineYCase, - SplitEntryIntoPacketsTwoLineResultTest, - ::testing::Values("X\rY", "X\nY", "X\r\nY")); + "X\n\r\r", "X\n\r\n", "X\n\n\r", "X\n\n\n"); + internal::GenericPacketArray array; + array.AppendEntry(loggerData, entryData, stampData, testStr); + std::vector> result; + std::copy(array.Begin(), array.End(), std::back_inserter(result)); + CHECK(result.size() == 1); + CHECK_THAT(result[0].GetText(), Catch::Matchers::Equals("X")); + } -INSTANTIATE_TEST_SUITE_P(XLinefeedYNewlinesCase, - SplitEntryIntoPacketsTwoLineResultTest, - ::testing::Values("X\nY\r", "X\nY\n", "X\nY\r\r", "X\nY\r\n", "X\nY\n\n", + SECTION("two-line result") + { + const char *testStr = GENERATE( + "X\rY", "X\nY", "X\r\nY", + "X\nY\r", "X\nY\n", "X\nY\r\r", "X\nY\r\n", "X\nY\n\n", "X\nY\r\r\r", "X\nY\r\r\n", "X\nY\r\n\r", "X\nY\r\n\n", - "X\nY\n\r\r", "X\nY\n\r\n", "X\nY\n\n\r", "X\nY\n\n\n")); - - -class SplitEntryIntoPacketsXEmptyYResultTest : - public SplitEntryIntoPacketsParameterizedTest -{}; - -TEST_P(SplitEntryIntoPacketsXEmptyYResultTest, XEmptyYResult) -{ - ASSERT_EQ(3, result_.size()); - EXPECT_EQ(internal::PacketStateEntryFirstLine, result_[0].GetPacketState()); - EXPECT_STREQ("X", result_[0].GetText()); - EXPECT_EQ(internal::PacketStateNewLine, result_[1].GetPacketState()); - EXPECT_STREQ("", result_[1].GetText()); - EXPECT_EQ(internal::PacketStateNewLine, result_[2].GetPacketState()); - EXPECT_STREQ("Y", result_[2].GetText()); -} - -INSTANTIATE_TEST_SUITE_P(XNewlineNewlineYCase, - SplitEntryIntoPacketsXEmptyYResultTest, - ::testing::Values("X\r\rY", "X\n\nY", "X\n\rY", - "X\r\n\rY", "X\r\n\nY", "X\r\r\nY", "X\n\r\nY", - "X\r\n\r\nY")); - - -class SplitEntryIntoPacketsLeadingNewlineTest : - public SplitEntryIntoPacketsTest, - public ::testing::WithParamInterface< std::pair > -{ -protected: - size_t expected_; + "X\nY\n\r\r", "X\nY\n\r\n", "X\nY\n\n\r", "X\nY\n\n\n"); + internal::GenericPacketArray array; + array.AppendEntry(loggerData, entryData, stampData, testStr); + std::vector> result; + std::copy(array.Begin(), array.End(), std::back_inserter(result)); + CHECK(result.size() == 2); + CHECK(result[0].GetPacketState() == internal::PacketStateEntryFirstLine); + CHECK_THAT(result[0].GetText(), Catch::Matchers::Equals("X")); + CHECK(result[1].GetPacketState() == internal::PacketStateNewLine); + CHECK_THAT(result[1].GetText(), Catch::Matchers::Equals("Y")); + } - virtual void SetUp() + SECTION("three-line result with empty middle line") { - SplitEntryIntoPacketsTest::SetUp(); - expected_ = GetParam().first; - Split(GetParam().second.c_str()); + const char *testStr = GENERATE( + "X\r\rY", "X\n\nY", "X\n\rY", "X\r\n\rY", "X\r\n\nY", + "X\r\r\nY", "X\n\r\nY", "X\r\n\r\nY"); + internal::GenericPacketArray array; + array.AppendEntry(loggerData, entryData, stampData, testStr); + std::vector> result; + std::copy(array.Begin(), array.End(), std::back_inserter(result)); + CHECK(result.size() == 3); + CHECK(result[0].GetPacketState() == internal::PacketStateEntryFirstLine); + CHECK_THAT(result[0].GetText(), Catch::Matchers::Equals("X")); + CHECK(result[1].GetPacketState() == internal::PacketStateNewLine); + CHECK_THAT(result[1].GetText(), Catch::Matchers::Equals("")); + CHECK(result[2].GetPacketState() == internal::PacketStateNewLine); + CHECK_THAT(result[2].GetText(), Catch::Matchers::Equals("Y")); } -}; -TEST_P(SplitEntryIntoPacketsLeadingNewlineTest, CorrectLeadingNewlines) -{ - ASSERT_EQ(expected_ + 1, result_.size()); - EXPECT_EQ(internal::PacketStateEntryFirstLine, result_[0].GetPacketState()); - for (size_t i = 0; i < expected_; ++i) + SECTION("one leading newline") { - EXPECT_STREQ("", result_[i].GetText()); - EXPECT_EQ(internal::PacketStateNewLine, result_[i + 1].GetPacketState()); + const char *testStr = GENERATE("\rX", "\nX", "\r\nX"); + internal::GenericPacketArray array; + array.AppendEntry(loggerData, entryData, stampData, testStr); + std::vector> result; + std::copy(array.Begin(), array.End(), std::back_inserter(result)); + CHECK(result.size() == 2); + CHECK(result[0].GetPacketState() == internal::PacketStateEntryFirstLine); + CHECK_THAT(result[0].GetText(), Catch::Matchers::Equals("")); + CHECK(result[1].GetPacketState() == internal::PacketStateNewLine); + CHECK_THAT(result[1].GetText(), Catch::Matchers::Equals("X")); + } - EXPECT_STREQ("X", result_[expected_].GetText()); -} -INSTANTIATE_TEST_SUITE_P(LeadingNewlinesCase, - SplitEntryIntoPacketsLeadingNewlineTest, - ::testing::Values(std::make_pair(0, "X"), - std::make_pair(1, "\rX"), - std::make_pair(1, "\nX"), - std::make_pair(1, "\r\nX"), - std::make_pair(2, "\r\rX"), - std::make_pair(2, "\n\rX"), - std::make_pair(2, "\n\nX"), - std::make_pair(2, "\r\n\rX"), - std::make_pair(2, "\r\n\nX"), - std::make_pair(2, "\r\r\nX"), - std::make_pair(2, "\n\r\nX"))); + SECTION("two leading newlines") + { + const char *testStr = GENERATE( + "\r\rX", "\n\rX", "\n\nX", "\r\n\rX", "\r\n\nX", + "\r\r\nX", "\n\r\nX"); + internal::GenericPacketArray array; + array.AppendEntry(loggerData, entryData, stampData, testStr); + std::vector> result; + std::copy(array.Begin(), array.End(), std::back_inserter(result)); + CHECK(result.size() == 3); + CHECK(result[0].GetPacketState() == internal::PacketStateEntryFirstLine); + CHECK_THAT(result[0].GetText(), Catch::Matchers::Equals("")); + CHECK(result[1].GetPacketState() == internal::PacketStateNewLine); + CHECK_THAT(result[1].GetText(), Catch::Matchers::Equals("")); + CHECK(result[2].GetPacketState() == internal::PacketStateNewLine); + CHECK_THAT(result[2].GetText(), Catch::Matchers::Equals("X")); + } + SECTION("soft newlines") + { + static const std::size_t MaxLogLineLen = + internal::GenericLinePacket::PacketTextLen; + SECTION("no soft split") + { + std::size_t repeatCount = GENERATE(MaxLogLineLen - 1, MaxLogLineLen); + std::string testStr(repeatCount, 'x'); + internal::GenericPacketArray array; + array.AppendEntry(loggerData, entryData, stampData, testStr.c_str()); + std::vector> result; + std::copy(array.Begin(), array.End(), std::back_inserter(result)); + CHECK(result.size() == 1); + CHECK(result[0].GetPacketState() == internal::PacketStateEntryFirstLine); + CHECK(result[0].GetText() == testStr); + } -class SplitEntryIntoPacketsSoftNewlineTest : - public SplitEntryIntoPacketsParameterizedTest -{}; + SECTION("one soft split") + { + std::size_t charCount = GENERATE( + MaxLogLineLen + 1, + 2 * MaxLogLineLen - 1, + 2 * MaxLogLineLen); + std::string testStr(charCount, 'x'); + internal::GenericPacketArray array; + array.AppendEntry(loggerData, entryData, stampData, testStr.c_str()); + std::vector> result; + std::copy(array.Begin(), array.End(), std::back_inserter(result)); + CHECK(result.size() == 2); + CHECK(result[0].GetPacketState() == internal::PacketStateEntryFirstLine); + CHECK(result[0].GetText() == std::string(MaxLogLineLen, 'x')); + CHECK(result[1].GetPacketState() == internal::PacketStateLineContinuation); + CHECK(result[1].GetText() == + std::string(testStr.size() - MaxLogLineLen, 'x')); + } -TEST_P(SplitEntryIntoPacketsSoftNewlineTest, CorrectSplit) -{ - // We are assuming input did not contain hard newlines - size_t inputLen = GetParam().size(); - size_t nLines = inputLen ? (inputLen - 1) / MaxLogLineLen + 1 : 1; - ASSERT_EQ(nLines, result_.size()); - EXPECT_EQ(internal::PacketStateEntryFirstLine, result_[0].GetPacketState()); - for (size_t i = 0; i < nLines; ++i) - { - if (i > 0) + SECTION("two soft splits") { - EXPECT_EQ(internal::PacketStateLineContinuation, - result_[i].GetPacketState()); + std::size_t charCount = GENERATE( + 2 * MaxLogLineLen + 1, + 3 * MaxLogLineLen - 1, + 3 * MaxLogLineLen); + std::string testStr(charCount, 'x'); + internal::GenericPacketArray array; + array.AppendEntry(loggerData, entryData, stampData, testStr.c_str()); + std::vector> result; + std::copy(array.Begin(), array.End(), std::back_inserter(result)); + CHECK(result.size() == 3); + CHECK(result[0].GetPacketState() == internal::PacketStateEntryFirstLine); + CHECK(result[0].GetText() == std::string(MaxLogLineLen, 'x')); + CHECK(result[1].GetPacketState() == internal::PacketStateLineContinuation); + CHECK(result[1].GetText() == std::string(MaxLogLineLen, 'x')); + CHECK(result[2].GetPacketState() == internal::PacketStateLineContinuation); + CHECK(result[2].GetText() == + std::string(testStr.size() - 2 * MaxLogLineLen, 'x')); } - EXPECT_STREQ( - GetParam().substr(i * MaxLogLineLen, MaxLogLineLen).c_str(), - result_[i].GetText()); } } -INSTANTIATE_TEST_SUITE_P(NoSoftSplitCase, - SplitEntryIntoPacketsSoftNewlineTest, - ::testing::Values( - std::string(MaxLogLineLen - 1, 'x'), - std::string(MaxLogLineLen, 'x'))); - -INSTANTIATE_TEST_SUITE_P(OneSoftSplitCase, - SplitEntryIntoPacketsSoftNewlineTest, - ::testing::Values( - std::string(MaxLogLineLen + 1, 'x'), - std::string(2 * MaxLogLineLen - 1, 'x'), - std::string(2 * MaxLogLineLen, 'x'))); - -INSTANTIATE_TEST_SUITE_P(TwoSoftSplitCase, - SplitEntryIntoPacketsSoftNewlineTest, - ::testing::Values( - std::string(2 * MaxLogLineLen + 1, 'x'), - std::string(3 * MaxLogLineLen - 1, 'x'), - std::string(3 * MaxLogLineLen, 'x'))); \ No newline at end of file +} // namespace logging +} // namespace mm \ No newline at end of file diff --git a/MMCore/unittest/meson.build b/MMCore/unittest/meson.build index 92ca4b566..416d51109 100644 --- a/MMCore/unittest/meson.build +++ b/MMCore/unittest/meson.build @@ -1,12 +1,15 @@ # This Meson script is experimental and potentially incomplete. It is not part # of the supported build system for Micro-Manager or mmCoreAndDevices. -gtest_proj = subproject('gtest') -gtest_dep = gtest_proj.get_variable('gtest_main_dep') +catch2_with_main_dep = dependency( + 'catch2-with-main', + allow_fallback: true, + include_type: 'system', +) mmcore_test_sources = files( 'APIError-Tests.cpp', - 'CoreSanity-Tests.cpp', + 'CoreCreateDestroy-Tests.cpp', 'Logger-Tests.cpp', 'LoggingSplitEntryIntoLines-Tests.cpp', ) @@ -18,7 +21,7 @@ mmcore_test_exe = executable( link_with: mmcore_lib, dependencies: [ mmdevice_dep, - gtest_dep, + catch2_with_main_dep, ], cpp_args: [ '-D_CRT_SECURE_NO_WARNINGS', # TODO Eliminate the need From 1ed092c1c126f439b515c726abef792d811eaf8d Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 20 Dec 2023 21:10:33 -0600 Subject: [PATCH 112/141] SequenceTester: Use more readable text rendering Replace tiny proportional font with larger monospace font. --- .../SequenceTester/SettingLogger.cpp | 118 +- DeviceAdapters/SequenceTester/SettingLogger.h | 47 +- DeviceAdapters/SequenceTester/TextImage.cpp | 1853 +++++++++++------ DeviceAdapters/SequenceTester/TextImage.h | 75 +- 4 files changed, 1335 insertions(+), 758 deletions(-) diff --git a/DeviceAdapters/SequenceTester/SettingLogger.cpp b/DeviceAdapters/SequenceTester/SettingLogger.cpp index 16ece5a2d..3b8534bf5 100644 --- a/DeviceAdapters/SequenceTester/SettingLogger.cpp +++ b/DeviceAdapters/SequenceTester/SettingLogger.cpp @@ -1,6 +1,7 @@ // Mock device adapter for testing of device sequencing // // Copyright (C) 2014 University of California, San Francisco. +// 2023 Board of Regents of the University of Wisconsin System // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by the @@ -25,7 +26,6 @@ #include -#include #include #include #include @@ -123,12 +123,11 @@ SettingEvent::Write(msgpack::sbuffer& sbuf) const } -void -SettingEvent::Draw(TextImageCursor& cursor) const +std::string +SettingEvent::AsText() const { - DrawStringOnImage(cursor, - "[" + boost::lexical_cast(count_) + "]" + - key_.GetStringRep() + "=" + value_->GetString()); + return "[" + std::to_string(count_) + "]" + + key_.GetStringRep() + "=" + value_->GetString(); } @@ -150,26 +149,35 @@ CameraInfo::Write(msgpack::sbuffer& sbuf) const } -void -CameraInfo::Draw(TextImageCursor& cursor) const +std::string +CameraInfo::AsText() const { - DrawStringOnImage(cursor, "camera,name=" + camera_); - cursor.Space(); - DrawStringOnImage(cursor, "camera,serialImageNr=" + - boost::lexical_cast(serialNr_)); - cursor.Space(); - DrawStringOnImage(cursor, "camera,isSequence=" + - std::string(isSequence_ ? "true" : "false")); - cursor.Space(); - DrawStringOnImage(cursor, - (isSequence_ ? "camera,sequenceImageNr=" : "camera,snapImageNr=") + - boost::lexical_cast(cumulativeNr_)); + std::string ret; + ret.reserve(256); + + ret += "camera,name="; + ret += camera_; + ret += ' '; + + ret += "camera,serialImageNr="; + ret += std::to_string(serialNr_); + ret += ' '; + + ret += "camera,isSequence="; + ret += isSequence_ ? "true" : "false"; + ret += ' '; + + ret += isSequence_ ? "camera,sequenceImageNr=" : "camera,snapImageNr="; + ret += std::to_string(cumulativeNr_); + if (isSequence_) { - cursor.Space(); - DrawStringOnImage(cursor, "camera,frameNr=" + - boost::lexical_cast(frameNr_)); + ret += ' '; + ret += "camera,frameNr="; + ret += std::to_string(frameNr_); } + + return ret; } @@ -357,29 +365,25 @@ SettingLogger::DrawTextToBuffer(char* dest, size_t destWidth, size_t destHeight, const std::string& camera, bool isSequenceImage, size_t serialImageNr, size_t cumulativeImageNr, size_t frameNr) { - memset(dest, 0, destWidth * destHeight); - TextImageCursor cursor(reinterpret_cast(dest), - static_cast(destWidth), static_cast(destHeight)); + std::string text; - DrawStringOnImage(cursor, "HubGlobalPacketNr=" + - boost::lexical_cast(GetNextGlobalImageNr())); - cursor.NewLine(); + text += "HubGlobalPacketNr="; + text += std::to_string(GetNextGlobalImageNr()); + text += '\n'; CameraInfo cameraInfo(camera, isSequenceImage, serialImageNr, cumulativeImageNr, frameNr); - cameraInfo.Draw(cursor); - cursor.NewLine(); - cursor.NewLine(); - - DrawStringOnImage(cursor, "State"); - cursor.NewLine(); - DrawSettingMap(cursor, settingValues_); - cursor.NewLine(); - cursor.NewLine(); - - DrawStringOnImage(cursor, "History"); - cursor.NewLine(); - DrawHistory(cursor); + text += cameraInfo.AsText(); + text += "\n\n"; + + text += "State\n"; + text += SettingMapAsText(settingValues_); + text += "\n\n"; + + text += "History\n"; + text += HistoryAsText(); + + DrawTextImage(text, reinterpret_cast(dest), destWidth, destHeight); } @@ -402,10 +406,11 @@ SettingLogger::WriteSettingMap(msgpack::sbuffer& sbuf, } -void -SettingLogger::DrawSettingMap(TextImageCursor& cursor, - const SettingMap& values) const +std::string +SettingLogger::SettingMapAsText(const SettingMap& values) const { + std::string ret; + ret.reserve(20 * values.size()); bool first = true; for (SettingConstIterator it = values.begin(), end = values.end(); it != end; ++it) @@ -416,10 +421,12 @@ SettingLogger::DrawSettingMap(TextImageCursor& cursor, if (first) first = false; else - cursor.Space(); - DrawStringOnImage(cursor, it->first.GetStringRep() + '=' + - it->second->GetString()); + ret += ' '; + ret += it->first.GetStringRep(); + ret += '='; + ret += it->second->GetString(); } + return ret; } @@ -437,15 +444,16 @@ SettingLogger::WriteHistory(msgpack::sbuffer& sbuf) const } -void -SettingLogger::DrawHistory(TextImageCursor& cursor) const +std::string +SettingLogger::HistoryAsText() const { - for (std::vector::const_iterator - begin = settingEvents_.begin(), it = begin, - end = settingEvents_.end(); it != end; ++it) + std::string ret; + ret.reserve(20 * settingEvents_.size()); + for (const auto& evt : settingEvents_) { - if (it != begin) - cursor.Space(); - it->Draw(cursor); + if (!ret.empty()) + ret += ' '; + ret += evt.AsText(); } + return ret; } diff --git a/DeviceAdapters/SequenceTester/SettingLogger.h b/DeviceAdapters/SequenceTester/SettingLogger.h index 98dc832a4..8cf96c758 100644 --- a/DeviceAdapters/SequenceTester/SettingLogger.h +++ b/DeviceAdapters/SequenceTester/SettingLogger.h @@ -1,6 +1,7 @@ // Mock device adapter for testing of device sequencing // // Copyright (C) 2014 University of California, San Francisco. +// 2023 Board of Regents of the University of Wisconsin System // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by the @@ -27,7 +28,6 @@ #include -#include #include #include @@ -60,9 +60,9 @@ class BoolSettingValue : public SettingValue const bool value_; public: BoolSettingValue(bool value) : value_(value) {} - virtual void Write(msgpack::sbuffer& sbuf) const; - virtual bool GetBool() const { return value_; } - virtual std::string GetString() const { return value_ ? "true" : "false"; } + void Write(msgpack::sbuffer& sbuf) const override; + bool GetBool() const override { return value_; } + std::string GetString() const override { return value_ ? "true" : "false"; } }; @@ -71,10 +71,10 @@ class IntegerSettingValue : public SettingValue const long value_; public: IntegerSettingValue(long value) : value_(value) {} - virtual void Write(msgpack::sbuffer& sbuf) const; - virtual long GetInteger() const { return value_; } - virtual std::string GetString() const - { return boost::lexical_cast(value_); } + void Write(msgpack::sbuffer& sbuf) const override; + long GetInteger() const override { return value_; } + std::string GetString() const override + { return std::to_string(value_); } }; @@ -83,10 +83,10 @@ class FloatSettingValue : public SettingValue const double value_; public: FloatSettingValue(double value) : value_(value) {} - virtual void Write(msgpack::sbuffer& sbuf) const; - virtual double GetFloat() const { return value_; } - virtual std::string GetString() const - { return boost::lexical_cast(value_); } + void Write(msgpack::sbuffer& sbuf) const override; + double GetFloat() const override { return value_; } + std::string GetString() const override + { return std::to_string(value_); } }; @@ -95,8 +95,8 @@ class StringSettingValue : public SettingValue const std::string value_; public: StringSettingValue(const std::string& value) : value_(value) {} - virtual void Write(msgpack::sbuffer& sbuf) const; - virtual std::string GetString() const { return value_; } + void Write(msgpack::sbuffer& sbuf) const override; + std::string GetString() const override { return value_; } }; @@ -104,8 +104,8 @@ class OneShotSettingValue : public SettingValue { public: OneShotSettingValue() {} - virtual void Write(msgpack::sbuffer& sbuf) const; - virtual std::string GetString() const { return "(one-shot)"; } + void Write(msgpack::sbuffer& sbuf) const override; + std::string GetString() const override { return "(one-shot)"; } }; @@ -118,7 +118,7 @@ class SettingKey : public Serializable SettingKey(const std::string& device, const std::string& key) : device_(device), key_(key) {} - virtual void Write(msgpack::sbuffer& sbuf) const; + void Write(msgpack::sbuffer& sbuf) const override; bool operator<(const SettingKey& rhs) const { return (this->device_ < rhs.device_) || @@ -139,8 +139,8 @@ class SettingEvent : public Serializable uint64_t counterValue) : key_(key), value_(value), count_(counterValue) {} - virtual void Write(msgpack::sbuffer& sbuf) const; - virtual void Draw(TextImageCursor& cursor) const; + void Write(msgpack::sbuffer& sbuf) const override; + std::string AsText() const; }; @@ -160,8 +160,8 @@ class CameraInfo : public Serializable cumulativeNr_(cumulativeNr), frameNr_(frameNr) {} - virtual void Write(msgpack::sbuffer& sbuf) const; - virtual void Draw(TextImageCursor& cursor) const; + void Write(msgpack::sbuffer& sbuf) const override; + std::string AsText() const; }; @@ -223,8 +223,7 @@ class SettingLogger uint64_t GetNextCount() { return counter_++; } uint64_t GetNextGlobalImageNr() { return nextGlobalImageNr_++; } void WriteSettingMap(msgpack::sbuffer& sbuf, const SettingMap& values) const; - void DrawSettingMap(TextImageCursor& cursor, - const SettingMap& values) const; + std::string SettingMapAsText(const SettingMap& values) const; void WriteHistory(msgpack::sbuffer& sbuf) const; - void DrawHistory(TextImageCursor& cursor) const; + std::string HistoryAsText() const; }; diff --git a/DeviceAdapters/SequenceTester/TextImage.cpp b/DeviceAdapters/SequenceTester/TextImage.cpp index 88a3e182b..7f103da2e 100644 --- a/DeviceAdapters/SequenceTester/TextImage.cpp +++ b/DeviceAdapters/SequenceTester/TextImage.cpp @@ -1,6 +1,6 @@ // Mock device adapter for testing of device sequencing // -// Copyright (C) 2014 University of California, San Francisco. +// Copyright (C) 2023 Board of Regents of the University of Wisconsin System // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by the @@ -24,638 +24,1275 @@ #include "TextImage.h" #include +#include +#include +#include #include +#include + +namespace { + +constexpr std::size_t GLYPH_WIDTH = 6; +constexpr std::size_t GLYPH_HEIGHT = 13; +constexpr std::size_t TOP_MARGIN = 4; +constexpr std::size_t LEFT_MARGIN = 4; + +// ASCII 0x20-0x7f of the public domain fixed-misc 6x13 font from X.org +// (-Misc-Fixed-Medium-R-SemiCondensed--13-120-75-75-C-60-ISO10646-1) +// with 0x7f (DEL) replaced with gray mesh. +// Glyphs are ended with "------\n" and trailing blank rows are omitted. +constexpr char glyphSrc[] = R"( +------ + + + * + * + * + * + * + * + * + + * +------ + + + * * + * * + * * +------ + -namespace -{ + * * + * * +***** + * * +***** + * * + * * +------ -typedef uint8_t Pixel; -const int GLYPH_HEIGHT = TextImageCursor::GLYPH_HEIGHT; -const int GLYPH_SPACING = TextImageCursor::GLYPH_SPACING; + * + **** +* * +* * + *** + * * + * * +**** + * +------ -inline Pixel -SaturatePixel(Pixel p) -{ - return p ? 255 : 0; -} + * * +* * * + * * + * + * + * + * * +* * * +* * +------ -class Glyph -{ - const Pixel* pixmap_; - int width_; - Glyph(const Pixel* pixmap, int pixmapSize) : - width_(pixmapSize / sizeof(Pixel) / GLYPH_HEIGHT) - { - Pixel* saturated = new Pixel[pixmapSize]; - std::transform(pixmap, pixmap + pixmapSize, saturated, SaturatePixel); - pixmap_ = saturated; - } -private: - static const Glyph* map_[256]; // ASCII-to-glyph lookup table + * +* * +* * + * +* * +* ** +* * + ** * +------ - static size_t CharIndex(char ch) - { return static_cast(static_cast(ch) & 0xff); } - static const Glyph* GetGlyph(char ch) - { - const Glyph* ret = map_[CharIndex(ch)]; - if (!ret) - return map_[0]; - return ret; - } + * + * + * +------ + + * + * + * + * + * + * + * + * + * + * + * +------ + + * + * + * + * + * + * + * + * + * + * + * +------ + + + * +* * * + *** +* * * + * +------ + + + + + * + * +***** + * + * +------ + + + + + + + + + + ** + * + * +------ + + + + + + +***** +------ + + + + + + + + + + * + *** + * +------ + + + * + * + * + * + * + * + * +* +* +------ + + + * + * * +* * +* * +* * +* * +* * + * * + * +------ + + + * + ** +* * + * + * + * + * + * +***** +------ + + + *** +* * +* * + * + * + * + * +* +***** +------ + + +***** + * + * + * + *** + * + * +* * + *** +------ + + + * + * + ** + * * + * * +* * +***** + * + * +------ + + +***** +* +* +* ** +** * + * + * +* * + *** +------ + + + *** +* * +* +* +**** +* * +* * +* * + *** +------ + + +***** + * + * + * + * + * + * + * + * +------ + + + *** +* * +* * +* * + *** +* * +* * +* * + *** +------ + + + *** +* * +* * +* * + **** + * + * +* * + *** +------ + + + + + * + *** + * + + + * + *** + * +------ + + + + + * + *** + * + + + ** + * + * +------ + + + * + * + * + * +* + * + * + * + * +------ + + + + + +***** + +***** +------ + + +* + * + * + * + * + * + * + * +* +------ + + + *** +* * +* * + * + * + * + * + + * +------ + + + *** +* * +* * +* ** +* * * +* * * +* ** +* + **** +------ + + + * + * * +* * +* * +* * +***** +* * +* * +* * +------ + + +**** + * * + * * + * * + *** + * * + * * + * * +**** +------ + + + *** +* * +* +* +* +* +* +* * + *** +------ + + +**** + * * + * * + * * + * * + * * + * * + * * +**** +------ + + +***** +* +* +* +**** +* +* +* +***** +------ + + +***** +* +* +* +**** +* +* +* +* +------ + + + *** +* * +* +* +* +* ** +* * +* * + *** +------ + + +* * +* * +* * +* * +***** +* * +* * +* * +* * +------ + + + *** + * + * + * + * + * + * + * + *** +------ + + + *** + * + * + * + * + * + * +* * + ** +------ + + +* * +* * +* * +* * +** +* * +* * +* * +* * +------ + + +* +* +* +* +* +* +* +* +***** +------ + + +* * +* * +** ** +* * * +* * * +* * +* * +* * +* * +------ + + +* * +** * +** * +* * * +* * * +* ** +* ** +* * +* * +------ + + + *** +* * +* * +* * +* * +* * +* * +* * + *** +------ + + +**** +* * +* * +* * +**** +* +* +* +* +------ + + + *** +* * +* * +* * +* * +* * +* * +* * * + *** + * +------ + + +**** +* * +* * +* * +**** +* * +* * +* * +* * +------ + + + *** +* * +* +* + *** + * + * +* * + *** +------ + + +***** + * + * + * + * + * + * + * + * +------ + + +* * +* * +* * +* * +* * +* * +* * +* * + *** +------ + + +* * +* * +* * +* * + * * + * * + * * + * + * +------ + + +* * +* * +* * +* * +* * * +* * * +* * * +* * * + * * +------ + + +* * +* * + * * + * * + * + * * + * * +* * +* * +------ + + +* * +* * + * * + * * + * + * + * + * + * +------ + + +***** + * + * + * + * + * + * +* +***** +------ + + *** + * + * + * + * + * + * + * + * + * + *** +------ + + +* +* + * + * + * + * + * + * + * +------ + + *** + * + * + * + * + * + * + * + * + * + *** +------ + + + * + * * +* * +------ + + + + + + + + + + + +***** +------ + + * + * +------ + + + + + + *** + * + **** +* * +* ** + ** * +------ + + +* +* +* +**** +* * +* * +* * +* * +**** +------ + + + + + + *** +* * +* +* +* * + *** +------ + + + * + * + * + **** +* * +* * +* * +* * + **** +------ + + + + + + *** +* * +***** +* +* * + *** +------ + + + ** + * * + * + * +**** + * + * + * + * +------ + + + + + + *** +* * +* * +* * + **** + * +* * + *** +------ + + +* +* +* +* ** +** * +* * +* * +* * +* * +------ + + + + * - friend class GlyphDef; - -private: - void Draw(TextImageCursor& cursor, bool knownToFit) const; - -public: - ~Glyph() { delete[] pixmap_; } - - int GetWidth() const { return width_; } - void Draw(TextImageCursor& cursor) const - { Draw(cursor, false); } - -public: - static int GetStringWidth(const std::string& s); - static void DrawString(const std::string& s, TextImageCursor& cursor, - bool allowLineBreak); -}; - -const Glyph* Glyph::map_[256]; - - -class GlyphDef -{ -public: - GlyphDef(char ch, const Pixel* pixmap, int pixmapSize) - { Glyph::map_[Glyph::CharIndex(ch)] = new Glyph(pixmap, pixmapSize); } -}; - - -void -Glyph::Draw(TextImageCursor& cursor, bool knownToFit) const -{ - int glyphWidth = GetWidth(); - if (knownToFit || cursor.MakeRoom(glyphWidth)) - { - int southWest = cursor.GetBaselineIndex(); - int northWest = southWest + GLYPH_HEIGHT * cursor.GetNorthStep(); - int pixelIndex = 0; - for (int rowStart = northWest; rowStart != southWest; - rowStart += cursor.GetSouthStep()) - { - int rowEnd = rowStart + glyphWidth * cursor.GetEastStep(); - for (int pos = rowStart; pos < rowEnd; ++pos) - { - cursor.GetBuffer()[pos] = pixmap_[pixelIndex++]; + ** + * + * + * + * + *** +------ + + + + * + + ** + * + * + * + * +* * +* * + ** +------ + + +* +* +* +* * +* * +** +* * +* * +* * +------ + + + ** + * + * + * + * + * + * + * + *** +------ + + + + + +** * +* * * +* * * +* * * +* * * +* * +------ + + + + + +* ** +** * +* * +* * +* * +* * +------ + + + + + + *** +* * +* * +* * +* * + *** +------ + + + + + +**** +* * +* * +* * +**** +* +* +* +------ + + + + + + **** +* * +* * +* * + **** + * + * + * +------ + + + + + +* ** +** * +* +* +* +* +------ + + + + + + *** +* * + ** + * +* * + *** +------ + + + + * + * +**** + * + * + * + * * + ** +------ + + + + + +* * +* * +* * +* * +* ** + ** * +------ + + + + + +* * +* * +* * + * * + * * + * +------ + + + + + +* * +* * +* * * +* * * +* * * + * * +------ + + + + + +* * + * * + * + * + * * +* * +------ + + + + + +* * +* * +* * +* ** + ** * + * +* * + *** +------ + + + + + +***** + * + * + * +* +***** +------ + + ** + * + * + * + * +** + * + * + * + * + ** +------ + + + * + * + * + * + * + * + * + * + * +------ + +** + * + * + * + * + ** + * + * + * + * +** +------ + + + * * +* * * +* * +------ +* * * + * * * +* * * + * * * +* * * + * * * +* * * + * * * +* * * + * * * +* * * + * * * +* * * +------ +)"; + +const std::uint8_t *glyphTable() { + static const std::vector table = [] { + std::vector ret( + (0x80 - 0x20) * GLYPH_HEIGHT * GLYPH_WIDTH); + const std::string src(glyphSrc); + std::size_t glyph = 0; + std::size_t y = 0; + std::size_t start = 0; + for (;;) { + std::size_t newline = src.find('\n', start); + if (newline == std::string::npos) { + assert(glyph == 0x80 - 0x20); + break; + } + assert(glyph < 0x80 - 0x20); + const auto line = src.substr(start, newline - start); + if (line == "------") { + ++glyph; + y = 0; + start = newline + 1; + continue; + } + assert(y < GLYPH_HEIGHT); + std::size_t x = 0; + for (auto ch : line) { + assert(x < GLYPH_WIDTH); + assert(ch == ' ' || ch == '*'); + ret[(glyph * GLYPH_HEIGHT + y) * GLYPH_WIDTH + x] = + (ch == '*' ? 0xff : 0x00); + ++x; } + ++y; + start = newline + 1; } - cursor.Advance(glyphWidth + GLYPH_SPACING); - } + return ret; + }(); + return table.data(); } - -int -Glyph::GetStringWidth(const std::string& s) -{ - int width = 0; - for (std::string::const_iterator it = s.begin(), end = s.end(); - it != end; ++it) - { - width += GetGlyph(*it)->GetWidth(); - width += GLYPH_SPACING; +const std::uint8_t *getGlyph(char ascii, std::size_t y = 0) { + // Widen without sign extension + auto code = std::size_t(static_cast(ascii)); + // Map all unknown chars to 0x7f (DEL) where we have mesh glyph + const bool isCtrl = (code & ~0x1f) == 0; + const bool isHigh = (code & 0x80) != 0; + if (isCtrl || isHigh) { + code = 0x7f; } - return width - GLYPH_SPACING; + return glyphTable() + + ((code - 0x20) * GLYPH_HEIGHT + y) * GLYPH_WIDTH; } - -void -Glyph::DrawString(const std::string& s, TextImageCursor& cursor, - bool allowLineBreak) -{ - int width = GetStringWidth(s); - bool allWillFitInCurrentLine = cursor.HasRoom(width); - if (!allWillFitInCurrentLine && !allowLineBreak) - { - cursor.NewLine(); - allWillFitInCurrentLine = cursor.HasRoom(width); +std::vector WrapLines(const std::string &text, + std::size_t maxRows, std::size_t maxCols) { + const auto sMaxCols = static_cast(maxCols); + std::vector ret; + if (maxCols == 0) { + return ret; } - for (std::string::const_iterator it = s.begin(), end = s.end(); - it != end; ++it) - { - GetGlyph(*it)->Draw(cursor, allWillFitInCurrentLine); + ret.reserve(maxRows); + auto start = text.begin(); + for (;;) { + auto eol = std::find(start, text.end(), '\n'); + while (std::distance(start, eol) > sMaxCols) { + const auto rstart = std::make_reverse_iterator( + std::next(start, maxCols)); + const auto rstop = std::make_reverse_iterator(start); + const auto rspace = std::find(rstart, rstop, ' '); + if (rspace == rstop || std::next(rspace) == rstop) { + // Cannot word-wrap, so hard-wrap + ret.emplace_back(start, std::next(start, maxCols)); + std::advance(start, maxCols); + } + else { + ret.emplace_back(start, std::next(rspace.base(), -1)); + start = rspace.base(); + } + if (ret.size() == maxRows) { + return ret; + } + } + ret.emplace_back(start, eol); + if (ret.size() == maxRows || eol == text.end()) { + return ret; + } + start = std::next(eol); } } -} // anonymous namespace - - -void -DrawStringOnImage(TextImageCursor& cursor, - const std::string& string, bool allowLineBreak) -{ - Glyph::DrawString(string, cursor, allowLineBreak); +void DrawRowsOfText(const std::vector &rows, std::uint8_t *buf, + std::size_t width, std::size_t height) { + // Loop over the pixel buffer exactly once, sequentially, for cache + // friendliness. This means repeatedly accessing the text line and randomly + // accessing the glyphs, but the text line and glyph table are much smaller + // than the image. + std::fill_n(buf, TOP_MARGIN * width, std::uint8_t(0)); + std::size_t row = 0; + for (; row < rows.size(); ++row) { + for (std::size_t glyphY = 0; glyphY < GLYPH_HEIGHT; ++glyphY) { + const auto y = TOP_MARGIN + row * GLYPH_HEIGHT + glyphY; + if (y >= height) { + return; + } + std::fill_n(std::next(buf, y * width), + LEFT_MARGIN, std::uint8_t(0)); + const auto &line = rows[row]; + std::size_t col = 0; + for (char ch : line) { + const auto x = LEFT_MARGIN + col * GLYPH_WIDTH; + const auto glyphWidth = std::min(GLYPH_WIDTH, width - x); + std::copy_n(getGlyph(ch, glyphY), glyphWidth, + std::next(buf, y * width + x)); + ++col; + } + const auto xEnd = LEFT_MARGIN + col * GLYPH_WIDTH; + std::fill_n(std::next(buf, y * width + xEnd), + width - xEnd, std::uint8_t(0)); + } + } + const auto yEnd = TOP_MARGIN + row * GLYPH_HEIGHT; + std::fill_n(std::next(buf, yEnd * width), + (height - yEnd) * width, std::uint8_t(0)); } +} // namespace -void -DrawTextImage(uint8_t* buffer, size_t width, size_t height, - const std::string& text) -{ - TextImageCursor cursor(buffer, - static_cast(width), static_cast(height)); - Glyph::DrawString(text, cursor, true); +void DrawTextImage(const std::string &text, std::uint8_t *buf, + std::size_t width, std::size_t height) { + const auto maxRows = (height - 2 * TOP_MARGIN) / GLYPH_HEIGHT; + const auto maxCols = (width - 2 * LEFT_MARGIN) / GLYPH_WIDTH; + auto rows = WrapLines(text, maxRows, maxCols); + DrawRowsOfText(rows, buf, width, height); } - - -namespace TextImageFont -{ - -// Glyphs are in ASCII order below (except for lowercase letters, which use the -// uppercase glyphs). - -// Not all characters are defined. All missing characters are drawn using the -// '\0' glyph. -const Pixel glyph_null[GLYPH_HEIGHT * 4] = { - 1,0,1,0, - 0,1,0,1, - 1,0,1,0, - 0,1,0,1, - 1,0,1,0, -}; -GlyphDef def_noglyph('\0', glyph_null, sizeof(glyph_null)); - -const Pixel glyph_space[GLYPH_HEIGHT * 4] = { - 0,0,0,0, - 0,0,0,0, - 0,0,0,0, - 0,0,0,0, - 0,0,0,0, -}; -GlyphDef def_space(' ', glyph_space, sizeof(glyph_space)); - -const Pixel glyph_leftparen[GLYPH_HEIGHT * 3] = { - 0,0,1, - 0,1,0, - 0,1,0, - 0,1,0, - 0,0,1, -}; -GlyphDef def_leftparen('(', glyph_leftparen, sizeof(glyph_leftparen)); - -const Pixel glyph_rightparen[GLYPH_HEIGHT * 3] = { - 1,0,0, - 0,1,0, - 0,1,0, - 0,1,0, - 1,0,0, -}; -GlyphDef def_rightparen(')', glyph_rightparen, sizeof(glyph_rightparen)); - -const Pixel glyph_plus[GLYPH_HEIGHT * 3] = { - 0,0,0, - 0,1,0, - 1,1,1, - 0,1,0, - 0,0,0, -}; -GlyphDef def_plus('+', glyph_plus, sizeof(glyph_plus)); - -const Pixel glyph_comma[GLYPH_HEIGHT * 2] = { - 0,0, - 0,0, - 0,0, - 0,1, - 1,0, -}; -GlyphDef def_comma(',', glyph_comma, sizeof(glyph_comma)); - -const Pixel glyph_minus[GLYPH_HEIGHT * 3] = { - 0,0,0, - 0,0,0, - 1,1,1, - 0,0,0, - 0,0,0, -}; -GlyphDef def_minus('-', glyph_minus, sizeof(glyph_minus)); - -const Pixel glyph_period[GLYPH_HEIGHT * 1] = { - 0, - 0, - 0, - 0, - 1, -}; -GlyphDef def_period('.', glyph_period, sizeof(glyph_period)); - -const Pixel glyph_0[GLYPH_HEIGHT * 3] = { - 0,1,0, - 1,0,1, - 1,1,1, - 1,0,1, - 0,1,0, -}; -GlyphDef def_0('0', glyph_0, sizeof(glyph_0)); - -const Pixel glyph_1[GLYPH_HEIGHT * 3] = { - 0,1,0, - 1,1,0, - 0,1,0, - 0,1,0, - 0,1,0, -}; -GlyphDef def_1('1', glyph_1, sizeof(glyph_1)); - -const Pixel glyph_2[GLYPH_HEIGHT * 3] = { - 0,1,0, - 1,0,1, - 0,0,1, - 0,1,0, - 1,1,1, -}; -GlyphDef def_2('2', glyph_2, sizeof(glyph_2)); - -const Pixel glyph_3[GLYPH_HEIGHT * 3] = { - 1,1,0, - 0,0,1, - 1,1,0, - 0,0,1, - 1,1,0, -}; -GlyphDef def_3('3', glyph_3, sizeof(glyph_3)); - -const Pixel glyph_4[GLYPH_HEIGHT * 3] = { - 1,0,1, - 1,0,1, - 1,1,1, - 0,0,1, - 0,0,1, -}; -GlyphDef def_4('4', glyph_4, sizeof(glyph_4)); - -const Pixel glyph_5[GLYPH_HEIGHT * 3] = { - 1,1,1, - 1,0,0, - 1,1,0, - 0,0,1, - 1,1,0, -}; -GlyphDef def_5('5', glyph_5, sizeof(glyph_5)); - -const Pixel glyph_6[GLYPH_HEIGHT * 3] = { - 0,1,1, - 1,0,0, - 1,1,0, - 1,0,1, - 0,1,0, -}; -GlyphDef def_6('6', glyph_6, sizeof(glyph_6)); - -const Pixel glyph_7[GLYPH_HEIGHT * 3] = { - 1,1,1, - 0,0,1, - 0,0,1, - 0,1,0, - 1,0,0, -}; -GlyphDef def_7('7', glyph_7, sizeof(glyph_7)); - -const Pixel glyph_8[GLYPH_HEIGHT * 3] = { - 0,1,0, - 1,0,1, - 0,1,0, - 1,0,1, - 0,1,0, -}; -GlyphDef def_8('8', glyph_8, sizeof(glyph_8)); - -const Pixel glyph_9[GLYPH_HEIGHT * 3] = { - 0,1,0, - 1,0,1, - 0,1,1, - 0,0,1, - 1,1,0, -}; -GlyphDef def_9('9', glyph_9, sizeof(glyph_9)); - -const Pixel glyph_colon[GLYPH_HEIGHT * 2] = { - 0,0, - 1,0, - 0,0, - 1,0, - 0,0, -}; -GlyphDef def_colon(':', glyph_colon, sizeof(glyph_colon)); - -const Pixel glyph_semicolon[GLYPH_HEIGHT * 3] = { - 0,0,0, - 0,1,0, - 0,0,0, - 0,1,0, - 1,0,0, -}; -GlyphDef def_semicolon(';', glyph_semicolon, sizeof(glyph_semicolon)); - -const Pixel glyph_equals[GLYPH_HEIGHT * 4] = { - 0,0,0,0, - 1,1,1,1, - 0,0,0,0, - 1,1,1,1, - 0,0,0,0, -}; -GlyphDef def_equals('=', glyph_equals, sizeof(glyph_equals)); - -const Pixel glyph_leftbracket[GLYPH_HEIGHT * 3] = { - 0,1,1, - 0,1,0, - 0,1,0, - 0,1,0, - 0,1,1, -}; -GlyphDef def_leftbracket('[', glyph_leftbracket, sizeof(glyph_leftbracket)); - -const Pixel glyph_rightbracket[GLYPH_HEIGHT * 3] = { - 1,1,0, - 0,1,0, - 0,1,0, - 0,1,0, - 1,1,0, -}; -GlyphDef def_rightbracket(']', glyph_rightbracket, sizeof(glyph_rightbracket)); - -const Pixel glyph_A[GLYPH_HEIGHT * 4] = { - 0,1,1,0, - 1,0,0,1, - 1,1,1,1, - 1,0,0,1, - 1,0,0,1, -}; -GlyphDef def_A('A', glyph_A, sizeof(glyph_A)); -GlyphDef def_a('a', glyph_A, sizeof(glyph_A)); - -const Pixel glyph_B[GLYPH_HEIGHT * 4] = { - 1,1,1,0, - 1,0,0,1, - 1,1,1,0, - 1,0,0,1, - 1,1,1,0, -}; -GlyphDef def_B('B', glyph_B, sizeof(glyph_B)); -GlyphDef def_b('b', glyph_B, sizeof(glyph_B)); - -const Pixel glyph_C[GLYPH_HEIGHT * 4] = { - 0,1,1,1, - 1,0,0,0, - 1,0,0,0, - 1,0,0,0, - 0,1,1,1, -}; -GlyphDef def_C('C', glyph_C, sizeof(glyph_C)); -GlyphDef def_c('c', glyph_C, sizeof(glyph_C)); - -const Pixel glyph_D[GLYPH_HEIGHT * 4] = { - 1,1,1,0, - 1,0,0,1, - 1,0,0,1, - 1,0,0,1, - 1,1,1,0, -}; -GlyphDef def_D('D', glyph_D, sizeof(glyph_D)); -GlyphDef def_d('d', glyph_D, sizeof(glyph_D)); - -const Pixel glyph_E[GLYPH_HEIGHT * 4] = { - 1,1,1,1, - 1,0,0,0, - 1,1,1,0, - 1,0,0,0, - 1,1,1,1, -}; -GlyphDef def_E('E', glyph_E, sizeof(glyph_E)); -GlyphDef def_e('e', glyph_E, sizeof(glyph_E)); - -const Pixel glyph_F[GLYPH_HEIGHT * 4] = { - 1,1,1,1, - 1,0,0,0, - 1,1,1,0, - 1,0,0,0, - 1,0,0,0, -}; -GlyphDef def_F('F', glyph_F, sizeof(glyph_F)); -GlyphDef def_f('f', glyph_F, sizeof(glyph_F)); - -const Pixel glyph_G[GLYPH_HEIGHT * 4] = { - 0,1,1,1, - 1,0,0,0, - 1,0,1,1, - 1,0,0,1, - 0,1,1,0, -}; -GlyphDef def_G('G', glyph_G, sizeof(glyph_G)); -GlyphDef def_g('g', glyph_G, sizeof(glyph_G)); - -const Pixel glyph_H[GLYPH_HEIGHT * 4] = { - 1,0,0,1, - 1,0,0,1, - 1,1,1,1, - 1,0,0,1, - 1,0,0,1, -}; -GlyphDef def_H('H', glyph_H, sizeof(glyph_H)); -GlyphDef def_h('h', glyph_H, sizeof(glyph_H)); - -const Pixel glyph_I[GLYPH_HEIGHT * 3] = { - 1,1,1, - 0,1,0, - 0,1,0, - 0,1,0, - 1,1,1, -}; -GlyphDef def_I('I', glyph_I, sizeof(glyph_I)); -GlyphDef def_i('i', glyph_I, sizeof(glyph_I)); - -const Pixel glyph_J[GLYPH_HEIGHT * 4] = { - 1,1,1,1, - 0,0,1,0, - 0,0,1,0, - 0,0,1,0, - 1,1,0,0, -}; -GlyphDef def_J('J', glyph_J, sizeof(glyph_J)); -GlyphDef def_j('j', glyph_J, sizeof(glyph_J)); - -const Pixel glyph_K[GLYPH_HEIGHT * 4] = { - 1,0,0,1, - 1,0,1,0, - 1,1,0,0, - 1,0,1,0, - 1,0,0,1, -}; -GlyphDef def_K('K', glyph_K, sizeof(glyph_K)); -GlyphDef def_k('k', glyph_K, sizeof(glyph_K)); - -const Pixel glyph_L[GLYPH_HEIGHT * 4] = { - 1,0,0,0, - 1,0,0,0, - 1,0,0,0, - 1,0,0,0, - 1,1,1,1, -}; -GlyphDef def_L('L', glyph_L, sizeof(glyph_L)); -GlyphDef def_l('l', glyph_L, sizeof(glyph_L)); - -const Pixel glyph_M[GLYPH_HEIGHT * 5] = { - 1,0,0,0,1, - 1,1,0,1,1, - 1,0,1,0,1, - 1,0,0,0,1, - 1,0,0,0,1, -}; -GlyphDef def_M('M', glyph_M, sizeof(glyph_M)); -GlyphDef def_m('m', glyph_M, sizeof(glyph_M)); - -const Pixel glyph_N[GLYPH_HEIGHT * 4] = { - 1,0,0,1, - 1,1,0,1, - 1,0,1,1, - 1,0,0,1, - 1,0,0,1, -}; -GlyphDef def_N('N', glyph_N, sizeof(glyph_N)); -GlyphDef def_n('n', glyph_N, sizeof(glyph_N)); - -const Pixel glyph_O[GLYPH_HEIGHT * 4] = { - 0,1,1,0, - 1,0,0,1, - 1,0,0,1, - 1,0,0,1, - 0,1,1,0, -}; -GlyphDef def_O('O', glyph_O, sizeof(glyph_O)); -GlyphDef def_o('o', glyph_O, sizeof(glyph_O)); - -const Pixel glyph_P[GLYPH_HEIGHT * 4] = { - 1,1,1,0, - 1,0,0,1, - 1,1,1,0, - 1,0,0,0, - 1,0,0,0, -}; -GlyphDef def_P('P', glyph_P, sizeof(glyph_P)); -GlyphDef def_p('p', glyph_P, sizeof(glyph_P)); - -const Pixel glyph_Q[GLYPH_HEIGHT * 5] = { - 0,1,1,0,0, - 1,0,0,1,0, - 1,0,0,1,0, - 1,0,1,1,0, - 0,1,1,1,1, -}; -GlyphDef def_Q('Q', glyph_Q, sizeof(glyph_Q)); -GlyphDef def_q('q', glyph_Q, sizeof(glyph_Q)); - -const Pixel glyph_R[GLYPH_HEIGHT * 4] = { - 1,1,1,0, - 1,0,0,1, - 1,1,1,0, - 1,0,1,0, - 1,0,0,1, -}; -GlyphDef def_R('R', glyph_R, sizeof(glyph_R)); -GlyphDef def_r('r', glyph_R, sizeof(glyph_R)); - -const Pixel glyph_S[GLYPH_HEIGHT * 4] = { - 0,1,1,1, - 1,0,0,0, - 0,1,1,0, - 0,0,0,1, - 1,1,1,0, -}; -GlyphDef def_S('S', glyph_S, sizeof(glyph_S)); -GlyphDef def_s('s', glyph_S, sizeof(glyph_S)); - -const Pixel glyph_T[GLYPH_HEIGHT * 5] = { - 1,1,1,1,1, - 0,0,1,0,0, - 0,0,1,0,0, - 0,0,1,0,0, - 0,0,1,0,0, -}; -GlyphDef def_T('T', glyph_T, sizeof(glyph_T)); -GlyphDef def_t('t', glyph_T, sizeof(glyph_T)); - -const Pixel glyph_U[GLYPH_HEIGHT * 4] = { - 1,0,0,1, - 1,0,0,1, - 1,0,0,1, - 1,0,0,1, - 0,1,1,0, -}; -GlyphDef def_U('U', glyph_U, sizeof(glyph_U)); -GlyphDef def_u('u', glyph_U, sizeof(glyph_U)); - -const Pixel glyph_V[GLYPH_HEIGHT * 4] = { - 1,0,0,1, - 1,0,0,1, - 1,0,0,1, - 0,1,0,1, - 0,0,1,0, -}; -GlyphDef def_V('V', glyph_V, sizeof(glyph_V)); -GlyphDef def_v('v', glyph_V, sizeof(glyph_V)); - -const Pixel glyph_W[GLYPH_HEIGHT * 5] = { - 1,0,0,0,1, - 1,0,1,0,1, - 1,0,1,0,1, - 1,0,1,0,1, - 0,1,0,1,0, -}; -GlyphDef def_W('W', glyph_W, sizeof(glyph_W)); -GlyphDef def_w('w', glyph_W, sizeof(glyph_W)); - -const Pixel glyph_X[GLYPH_HEIGHT * 4] = { - 1,0,0,1, - 1,0,0,1, - 0,1,1,0, - 1,0,0,1, - 1,0,0,1, -}; -GlyphDef def_X('X', glyph_X, sizeof(glyph_X)); -GlyphDef def_x('x', glyph_X, sizeof(glyph_X)); - -const Pixel glyph_Y[GLYPH_HEIGHT * 4] = { - 1,0,0,1, - 0,1,0,1, - 0,0,1,0, - 0,0,1,0, - 0,0,1,0, -}; -GlyphDef def_Y('Y', glyph_Y, sizeof(glyph_Y)); -GlyphDef def_y('y', glyph_Y, sizeof(glyph_Y)); - -const Pixel glyph_Z[GLYPH_HEIGHT * 4] = { - 1,1,1,1, - 0,0,1,0, - 0,1,0,0, - 1,0,0,0, - 1,1,1,1, -}; -GlyphDef def_Z('Z', glyph_Z, sizeof(glyph_Z)); -GlyphDef def_z('z', glyph_Z, sizeof(glyph_Z)); - -const Pixel glyph_underscore[GLYPH_HEIGHT * 4] = { - 0,0,0,0, - 0,0,0,0, - 0,0,0,0, - 0,0,0,0, - 1,1,1,1, -}; -GlyphDef def_underscore('_', glyph_underscore, sizeof(glyph_underscore)); - -} // namespace TextImageFont diff --git a/DeviceAdapters/SequenceTester/TextImage.h b/DeviceAdapters/SequenceTester/TextImage.h index 3e7934a74..bc7f16c54 100644 --- a/DeviceAdapters/SequenceTester/TextImage.h +++ b/DeviceAdapters/SequenceTester/TextImage.h @@ -1,6 +1,6 @@ // Mock device adapter for testing of device sequencing // -// Copyright (C) 2014 University of California, San Francisco. +// Copyright (C) 2023 Board of Regents of the University of Wisconsin System // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by the @@ -23,75 +23,8 @@ #pragma once -#include -#include +#include #include - -class TextImageCursor -{ - uint8_t* buffer_; - - int stride_; - int nRows_; - - int baseline_; - int hPos_; - -public: - static const int GLYPH_HEIGHT = 5; - static const int BASELINE_SKIP = GLYPH_HEIGHT + 4; - static const int GLYPH_SPACING = 1; - static const int MARGIN = 4; - -public: - TextImageCursor(uint8_t* buffer, int bufferWidth, int bufferHeight) : - buffer_(buffer), - stride_(bufferWidth), - nRows_(bufferHeight), - baseline_(MARGIN + GLYPH_HEIGHT), - hPos_(MARGIN) - {} - - uint8_t* GetBuffer() { return buffer_; } - - bool IsBeyondBuffer() const { return baseline_ > nRows_; } - void NewLine() { baseline_ += BASELINE_SKIP; hPos_ = MARGIN; } - void Space() { if (HasRoom(5)) Advance(5); else NewLine(); } - bool HasRoom(int width) const - { - if (IsBeyondBuffer() || width > stride_ - 2 * MARGIN) - return false; - if (stride_ - hPos_ >= width + MARGIN) - return true; - return false; - } - bool MakeRoom(int width) - { - if (IsBeyondBuffer() || width > stride_ - 2 * MARGIN) - return false; - if (stride_ - hPos_ >= width + MARGIN) - return true; - NewLine(); - return !IsBeyondBuffer(); - } - int GetBaselineIndex() const { return baseline_ * stride_ + hPos_; } - int GetNorthStep() const { return -stride_; } - int GetEastStep() const { return 1; } - int GetWestStep() const { return -1; } - int GetSouthStep() const { return stride_; } - void Advance(int hDelta) - { - hPos_ += hDelta; - if (hPos_ + MARGIN > stride_) - NewLine(); - } -}; - - -void DrawStringOnImage(TextImageCursor& cursor, const std::string& string, - bool allowLineBreak = false); - -// Draw a whole text image (no word wrapping) -void DrawTextImage(uint8_t* buffer, size_t width, size_t height, - const std::string& text); +void DrawTextImage(const std::string &text, std::uint8_t *buf, + std::size_t width, std::size_t height); From 44dd43e0aa291274a96c88f8fd86cf3de740611c Mon Sep 17 00:00:00 2001 From: Atik-Callum <129527371+Atik-Callum@users.noreply.github.com> Date: Tue, 2 Jan 2024 11:55:16 +0000 Subject: [PATCH 113/141] Initial commit for QSI device adapter --- DeviceAdapters/QSI/QSI.vcxproj | 112 ++ DeviceAdapters/QSI/QSI.vcxproj.filters | 33 + DeviceAdapters/QSI/QSICameraAdapter.cpp | 1881 +++++++++++++++++++++++ DeviceAdapters/QSI/QSICameraAdapter.h | 160 ++ DeviceAdapters/QSI/QSIImage.h | 44 + DeviceAdapters/QSI/QSIToolkit.h | 61 + micromanager.sln | 6 + 7 files changed, 2297 insertions(+) create mode 100644 DeviceAdapters/QSI/QSI.vcxproj create mode 100644 DeviceAdapters/QSI/QSI.vcxproj.filters create mode 100644 DeviceAdapters/QSI/QSICameraAdapter.cpp create mode 100644 DeviceAdapters/QSI/QSICameraAdapter.h create mode 100644 DeviceAdapters/QSI/QSIImage.h create mode 100644 DeviceAdapters/QSI/QSIToolkit.h diff --git a/DeviceAdapters/QSI/QSI.vcxproj b/DeviceAdapters/QSI/QSI.vcxproj new file mode 100644 index 000000000..dbcdb796b --- /dev/null +++ b/DeviceAdapters/QSI/QSI.vcxproj @@ -0,0 +1,112 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + {b8c95f39-54bf-40a9-807b-598df2821d55} + + + + 16.0 + Win32Proj + {320af64e-c4fc-4a97-a7d1-f8233a8a44c7} + QSI + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + false + + + + Level3 + true + _DEBUG;QSI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_2022_07_19\include;%(AdditionalIncludeDirectories) + + + Windows + true + false + QSICameraCLib.lib;%(AdditionalDependencies) + $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_2022_07_19\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + true + true + true + NDEBUG;QSI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_2022_07_19\include;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + false + QSICameraCLib.lib;%(AdditionalDependencies) + $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_2022_07_19\lib;%(AdditionalLibraryDirectories) + + + + + + \ No newline at end of file diff --git a/DeviceAdapters/QSI/QSI.vcxproj.filters b/DeviceAdapters/QSI/QSI.vcxproj.filters new file mode 100644 index 000000000..9b679b903 --- /dev/null +++ b/DeviceAdapters/QSI/QSI.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/DeviceAdapters/QSI/QSICameraAdapter.cpp b/DeviceAdapters/QSI/QSICameraAdapter.cpp new file mode 100644 index 000000000..9f6c71d99 --- /dev/null +++ b/DeviceAdapters/QSI/QSICameraAdapter.cpp @@ -0,0 +1,1881 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FILE: QSICameraAdapter.cpp +// PROJECT: Micro-Manager +// SUBSYSTEM: DeviceAdapters +//----------------------------------------------------------------------------- +// DESCRIPTION: QSI camera adapter for use with Micro-Manager +// +// AUTHOR: Mike Barry, http://www.qsimaging.com +// +// COPYRIGHT: Quantum Scientific Imaging, Inc. 2013 +// +// LICENSE: This file is distributed under the BSD license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. + +#include "QSICameraAdapter.h" +#include "QSIToolkit.h" +#include + +using namespace std; + +#pragma region [ Local Constants ] + +namespace QSIString +{ + // A namespace is being used so there's no chance of name collisions + + const char * const ANTI_BLOOMING = "AntiBlooming"; + const char * const ANTI_BLOOMING_HIGH = "High"; + const char * const ANTI_BLOOMING_NORMAL = "Normal"; + const char * const BINNING_MAX_X = "BinningMaxX"; + const char * const BINNING_MAX_Y = "BinningMaxY"; + const char * const BODY_TEMPERATURE = "BodyTemperature"; + const char * const CONNECT_TO_SERIAL_NUMBER = "ConnectToSerialNumber"; + const char * const CONNECT_TO_SERIAL_NUMBER_ANY = "Any"; + const char * const COOLER_STATE = "CoolerState"; + const char * const COOLER_STATE_ON = "On"; + const char * const COOLER_STATE_OFF = "Off"; + const char * const COOLER_POWER = "CoolerPower"; + const char * const DEVICE_NAME = "QSI Camera"; + const char * const DEVICE_DESCRIPTION = "QSI camera adapter"; + const char * const DRIVER_INFO = "DriverInfo"; + const char * const ELECTRONS_PER_ADU = "ElectronsPerADU"; + const char * const EXPOSURE_MAX = "ExposureDurationMax"; + const char * const EXPOSURE_MIN = "ExposureDurationMin"; + const char * const FAN_MODE = "FanMode"; + const char * const FAN_MODE_OFF = "Off"; + const char * const FAN_MODE_QUIET = "Quiet"; + const char * const FAN_MODE_FULL = "Full"; + const char * const FILTER_WHEEL_POSITION = "FilterWheelPosition"; + const char * const FILTER_WHEEL_POSITIONS = "FilterWheelPositions"; + const char * const FULL_WELL_CAPACITY = "FullWellCapacity"; + const char * const GAIN_MODE = "GainMode"; + const char * const GAIN_MODE_HIGH = "High"; + const char * const GAIN_MODE_LOW = "Low"; + const char * const HAS_FILTER_WHEEL = "HasFilterWheel"; + const char * const HAS_FILTER_WHEEL_NO = "No"; + const char * const HAS_FILTER_WHEEL_YES = "Yes"; + const char * const HAS_SHUTTER = "HasShutter"; + const char * const HAS_SHUTTER_NO = "No"; + const char * const HAS_SHUTTER_YES = "Yes"; + const char * const LED_SETTING = "LEDSetting"; + const char * const LED_SETTING_ENABLED = "Enabled"; + const char * const LED_SETTING_DISABLED = "Disabled"; + const char * const MAX_ADU = "MaxADU"; + const char * const MODEL_NAME = "ModelName"; + const char * const MODEL_NUMBER = "ModelNumber"; + const char * const OPEN_SHUTTER = "OpenShutterDuringExposure"; + const char * const OPEN_SHUTTER_NO = "No"; + const char * const OPEN_SHUTTER_YES = "Yes"; + const char * const PCB_TEMPERATURE = "PCBTemperature"; + const char * const PIXEL_SIZE_X = "PixelSizeX"; + const char * const PIXEL_SIZE_Y = "PixelSizeY"; + const char * const PRE_EXPOSURE_FLUSH = "PreExposureFlush"; + const char * const PRE_EXPOSURE_FLUSH_NONE = "None"; + const char * const PRE_EXPOSURE_FLUSH_MODEST = "Modest"; + const char * const PRE_EXPOSURE_FLUSH_NORMAL = "Normal"; + const char * const PRE_EXPOSURE_FLUSH_AGGRESSIVE = "Aggressive"; + const char * const PRE_EXPOSURE_FLUSH_VERY_AGGRESSIVE = "Very Aggressive"; + const char * const READOUT_MODE_FAST_READOUT = "Fast Readout"; + const char * const READOUT_MODE_HIGH_QUALITY = "High Quality"; + const char * const SERIAL_NUMBER = "SerialNumber"; + const char * const SHUTTER_PRIORITY = "ShutterPriority"; + const char * const SHUTTER_PRIORITY_ELECTRONIC = "Electronic"; + const char * const SHUTTER_PRIORITY_MECHANICAL = "Mechanical"; + const char * const SOUND_SETTING = "SoundSetting"; + const char * const SOUND_SETTING_ENABLED = "Enabled"; + const char * const SOUND_SETTING_DISABLED = "Disabled"; +} + +#pragma endregion + +#pragma region [ Windows DLL Entry Code ] + +// Windows DLL entry code +#ifdef WIN32 +BOOL APIENTRY DllMain( HANDLE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/ ) +{ + switch( ul_reason_for_call ) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} +#endif + +#pragma endregion + +#pragma region [ Exported MMDevice API ] + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// List all supported hardware devices here +// +MODULE_API void InitializeModuleData() +{ + /// Register a device class provided by the device adapter library. + /** + * To be called in the device adapter module's implementation of + * InitializeModuleData(). + * + * Calling this function indicates that the module provides a device with the + * given name and type, and provides a user-visible description string. + */ + RegisterDevice(QSIString::DEVICE_NAME, MM::CameraDevice, QSIString::DEVICE_DESCRIPTION); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +MODULE_API MM::Device * CreateDevice( const char * deviceName ) +{ + if( deviceName == 0 ) + return 0; + + // Decide which device class to create based on the deviceName parameter + if( strcmp( deviceName, QSIString::DEVICE_NAME ) == 0 ) + { + // Create camera + return new QSICameraAdapter(); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +MODULE_API void DeleteDevice( MM::Device * pDevice ) +{ + delete pDevice; +} + +#pragma endregion + +#pragma region [ QSICameraAdapter - Constructor / Desctructor ] + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// QSICameraAdapter constructor +// +// Setup default all variables and create device properties required to exist +// before intialization. In this case, no such properties were required. All +// properties will be created in the Initialize() method. +// +// As a general guideline Micro-Manager devices do not access hardware in the +// the constructor. We should do as little as possible in the constructor and +// perform most of the initialization in the Initialize() method. +// +QSICameraAdapter::QSICameraAdapter() : + m_handle( QSI_HANDLE_INVALID ), + m_imageBinning( 1 ), + m_imageMaxX( 1 ), + m_imageMaxY( 1 ), + m_imageNumX( 1 ), + m_imageNumY( 1 ), + m_imageStartX( 0 ), + m_imageStartY( 0 ), + m_initialized( false ), + m_exposureDuration( 10 ), + m_exposureDurationMax( 1000 ), + m_exposureDurationMin( 10 ), + m_exposureOpenShutter( true ), + m_pImageBuffer( 0 ), + m_pixelSizeX( 0 ), + m_pixelSizeY( 0 ), + m_status( QSI_OK ) +{ + int response; + + // Call the base class method to set-up default error codes/messages + InitializeDefaultErrorMessages(); + + // Serial number pre-initialization property + response = CreateProperty( QSIString::CONNECT_TO_SERIAL_NUMBER, QSIString::CONNECT_TO_SERIAL_NUMBER_ANY, MM::String, false, 0, true ); + assert( response == DEVICE_OK ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// QSICameraAdapter destructor +// +// If this device used as intended within the Micro-Manager system, +// Shutdown() will be always called before the destructor. But in any case +// we need to make sure that all resources are properly released even if +// Shutdown() was not called. +// +QSICameraAdapter::~QSICameraAdapter() +{ + Shutdown(); +} + +#pragma endregion + +#pragma region [ QSICameraAdapter - MM::Device Methods ] + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Obtains device name. +// +void QSICameraAdapter::GetName( char * name ) const +{ + // We just return the name we use for referring to this device adapter. + CDeviceUtils::CopyLimitedString( name, QSIString::DEVICE_NAME ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Intializes the hardware. +// +// Typically we access and initialize hardware at this point. +// Device properties are typically created here as well. +// +int QSICameraAdapter::Initialize() +{ + char serialNumber[MM::MaxStrLength]; + int imageBufferSize, response; + + + // Don't continue if we've already initialized + if( m_initialized ) + return DEVICE_OK; + + + // Assign meaningful descriptions to error codes + SetErrorText( QSI_NOTSUPPORTED, "Not supported." ); + SetErrorText( QSI_UNRECOVERABLE, "An unrecoverable internal or device error occurred." ); + SetErrorText( QSI_NOFILTER, "No filterwheel available." ); + SetErrorText( QSI_NOMEMORY, "Out of memory." ); + SetErrorText( QSI_BADROWSIZE, "Invalid row size specified." ); + SetErrorText( QSI_BADCOLSIZE, "Invalid column size specified." ); + SetErrorText( QSI_INVALIDBIN, "Invalid bin factor specified." ); + SetErrorText( QSI_NOASYMBIN, "Cannot asymmetric bin." ); + SetErrorText( QSI_BADEXPOSURE, "Invalid exposure duration specified." ); + SetErrorText( QSI_BADBINSIZE, "Invalid bin factor specified." ); + SetErrorText( QSI_NOEXPOSURE, "No prior exposure taken." ); + SetErrorText( QSI_BADRELAYSTATUS, "An error occurred while getting relay status." ); + SetErrorText( QSI_BADABORTRELAYS, "An error occurred while aborting active relays." ); + SetErrorText( QSI_RELAYERROR, "An error occurred while activating relays." ); + SetErrorText( QSI_INVALIDIMAGEPARAMETER, "One or more image parameters are invalid." ); + SetErrorText( QSI_NOIMAGEAVAILABLE, "There is no image available for download." ); + SetErrorText( QSI_NOTCONNECTED, "The camera has not been connected to." ); + SetErrorText( QSI_INVALIDFILTERNUMBER, "Filter position is invalid." ); + SetErrorText( QSI_RECOVERABLE, "A recoverable internal error occurred." ); + SetErrorText( QSI_CONNECTED, "Camera cannot be connected when making this call." ); + SetErrorText( QSI_INVALIDTEMP, "Invalid temperature specified." ); + SetErrorText( QSI_TRIGGERTIMEOUT , "Trigger timed out." ); + SetErrorText( QSI_ERROR_NO_CAMERA, "No camera available." ); + SetErrorText( QSI_ERROR_NO_SHUTTER, "No shutter available." ); + SetErrorText( QSI_ERROR_DOWNLOADING, "Camera is busy downloading an image." ); + SetErrorText( QSI_ERROR_INVALID_HANDLE, "The specified handle is not valid." ); + SetErrorText( QSI_ERROR_BAD_ARGUMENT, "One of the arguments specified is not valid." ); + SetErrorText( QSI_ERROR_NULL_POINTER, "A null pointer was passed as an argument." ); + SetErrorText( QSI_ERROR_INTERNAL_ERROR, "An undefined error occurred." ); + SetErrorText( QSI_ERROR_NOT_IMPLEMENTED, "Not yet implemented." ); + SetErrorText( QSI_ERROR_BUFFER_TOO_SMALL, "The specified buffer is too small." ); + SetErrorText( QSI_ERROR_BUFFER_TOO_LARGE, "The specified buffer is too small." ); + SetErrorText( QSI_ERROR_NOT_ENABLED, "The requested mode is not enabled." ); + SetErrorText( QSI_ERROR_CREATE_HANDLE, "C API failed to create handle." ); + SetErrorText( QSI_ERROR_COMMAND_FAILED, "The command failed." ); + + // Grab the serial number from the pre-initialization property created in the constructor + GetProperty( QSIString::CONNECT_TO_SERIAL_NUMBER, serialNumber ); + + // Connect to the camera + QSI_CreateHandle( &m_handle ); + HANDLE_QSI_ERROR( this, m_status ); + + if( strcmp( QSIString::CONNECT_TO_SERIAL_NUMBER_ANY, serialNumber ) == 0 ) + { + m_status = QSI_Connect( m_handle ); + HANDLE_QSI_ERROR( this, m_status ); + } + else + { + m_status = QSI_SetSerialNumber( m_handle, serialNumber, MM::MaxStrLength ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_Connect( m_handle ); + HANDLE_QSI_ERROR( this, m_status ); + } + + // Get image size info + m_status = QSI_GetImageSizeX( m_handle, &m_imageMaxX ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_GetImageSizeY( m_handle, &m_imageMaxY ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_GetImageStartX( m_handle, &m_imageStartX ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_GetImageStartY( m_handle, &m_imageStartY ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_GetImageNumX( m_handle, &m_imageNumX ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_GetImageNumY( m_handle, &m_imageNumY ); + HANDLE_QSI_ERROR( this, m_status ); + + // Allocate image buffer and clear it for good measure + imageBufferSize = m_imageMaxX * m_imageMaxY * QSI_IMAGE_BYTES_PER_PIXEL; + + m_pImageBuffer = static_cast( malloc( imageBufferSize ) ); + + if( m_pImageBuffer == 0 ) + HANDLE_MM_ERROR( this, DEVICE_OUT_OF_MEMORY, "Out of memory." ); + + memset( m_pImageBuffer, 0, imageBufferSize ); + + // Setup device properties + response = AntiBloomingPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the AntiBlooming property." ); + + response = BinningPropertiesSetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the Binning property." ); + + response = BodyTemperaturePropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the BodyTemperature property." ); + + response = CCDTemperaturePropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the CCDTemperature property." ); + + response = CCDTemperatureSetpointPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the CCDTemperatureSetpoint property." ); + + response = CoolerPowerPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the CoolerPower property." ); + + response = CoolerStatePropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the CoolerState property." ); + + response = DescriptionPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the Description property." ); + + response = DriverInfoPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the DriverInfo property." ); + + response = ExposurePropertiesSetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the Exposure properties." ); + + response = FanModePropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the FanMode property." ); + + response = FilterWheelPropertiesSetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the FilterWheel properties." ); + + response = FullWellCapacityPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the FullWellCapacity property." ); + + response = GainPropertiesSetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the Gain properties." ); + + response = LEDEnabledPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the LEDEnabled property." ); + + response = MaxADUPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the MaxADU property." ); + + response = ModelNamePropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the ModelName property." ); + + response = ModelNumberPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the ModelNumber property." ); + + response = OpenShutterPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the OpenShutter property." ); + + response = PCBTemperaturePropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the PCBTemperature property." ); + + response = PixelSizePropertiesSetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the PixelSize properties." ); + + response = PreExposureFlushPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the PreExposureFlush property." ); + + response = ReadoutModePropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the ReadoutMode property." ); + + response = SerialNumberPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the SerialNumber property." ); + + response = ShutterPropertiesSetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the Shutter properties." ); + + response = SoundEnabledPropertySetup(); + HANDLE_MM_ERROR( this, response, "An error occurred while setting up the SoundEnabled property." ); + + // Synchronize all properties + response = UpdateStatus(); + HANDLE_MM_ERROR( this, response, "An error occured while synchronizing properties." ); + + // Set a flag so we know that we've been properly initialized + m_initialized = true; + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Shuts down (unloads) the device. +// +// Ideally this method will completely unload the device and release all resources. +// Shutdown() may be called multiple times in a row. +// +int QSICameraAdapter::Shutdown() +{ + // Disconnect from camera and release handle + if( m_handle != QSI_HANDLE_INVALID ) + { + QSI_Disconnect( m_handle ); + QSI_ReleaseHandle( m_handle ); + m_handle = QSI_HANDLE_INVALID; + } + + // Free image buffer + if( m_pImageBuffer != 0 ) + { + free( m_pImageBuffer ); + m_pImageBuffer = 0; + } + + m_initialized = false; + + return DEVICE_OK; +} + +#pragma endregion + +#pragma region [ QSICameraAdapter - MM::Camera Methods ] + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Resets the Region of Interest to full frame. +// +int QSICameraAdapter::ClearROI() +{ + m_imageNumX = m_imageMaxX / m_imageBinning; + m_status = QSI_SetImageNumX( m_handle, m_imageNumX ); + HANDLE_QSI_ERROR( this, m_status ); + + m_imageNumY = m_imageMaxY / m_imageBinning; + m_status = QSI_SetImageNumY( m_handle, m_imageNumY ); + HANDLE_QSI_ERROR( this, m_status ); + + m_imageStartX = 0; + m_status = QSI_SetImageStartX( m_handle, m_imageStartX ); + HANDLE_QSI_ERROR( this, m_status ); + + m_imageStartY = 0; + m_status = QSI_SetImageStartY( m_handle, m_imageStartY ); + HANDLE_QSI_ERROR( this, m_status ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Acquires and inserts an image and it's metadata into MMCore's circular buffer +// +int QSICameraAdapter::InsertImage() +{ + char label[MM::MaxStrLength]; + Metadata metadata; + const unsigned char * pImageBuffer; + int response; + const char * pSerializedMetadata; + + // Assemble metadata + this->GetLabel( label ); + + metadata.put( "Camera", label ); + + pSerializedMetadata = metadata.Serialize().c_str(); + + // Download image + pImageBuffer = GetImageBuffer(); + + // Insert received image into MMCore's circular buffer + response = GetCoreCallback()->InsertImage( this, pImageBuffer, m_imageNumX, m_imageNumY, QSI_IMAGE_BYTES_PER_PIXEL, pSerializedMetadata ); + + if( !isStopOnOverflow() && response == DEVICE_BUFFER_OVERFLOW ) + { + GetCoreCallback()->ClearImageBuffer( this ); + return GetCoreCallback()->InsertImage( this, pImageBuffer, m_imageNumX, m_imageNumY, QSI_IMAGE_BYTES_PER_PIXEL, pSerializedMetadata, false ); + } + else + return response; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::IsExposureSequenceable( bool & seq ) const +{ + seq = false; + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns the current binning factor. +// +int QSICameraAdapter::GetBinning() const +{ + return m_imageBinning; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns the bit depth (dynamic range) of the pixel. +// This does not affect the buffer size, it just gives the client application +// a guideline on how to interpret pixel values. +// +unsigned int QSICameraAdapter::GetBitDepth() const +{ + return QSI_IMAGE_BIT_DEPTH; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns the current exposure setting in milliseconds. +// +double QSICameraAdapter::GetExposure() const +{ + return m_exposureDuration; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns pixel data. +// +// The calling program will assume the size of the buffer based on the values +// obtained from GetImageBufferSize(), which in turn should be consistent with +// values returned by GetImageWidth(), GetImageHight() and GetImageBytesPerPixel(). +// The calling program allso assumes that camera never changes the size of +// the pixel buffer on its own. In other words, the buffer can change only if +// appropriate properties are set (such as binning, pixel type, etc.) +// +const unsigned char * QSICameraAdapter::GetImageBuffer() +{ + int imageSize; + + imageSize = m_imageNumX * m_imageNumY; + + m_status = QSI_ReadImage( m_handle, m_pImageBuffer, imageSize ); + + if( m_status ) + { + LogMessage( GetLastQSIError( m_handle ), false ); + + // Set all image values to 0x0101 (257) to denote an error occurred + memset( m_pImageBuffer, 1, m_imageMaxX * m_imageMaxY * QSI_IMAGE_BYTES_PER_PIXEL ); + } + + return reinterpret_cast( m_pImageBuffer ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns the size in bytes of the image buffer. +// +long QSICameraAdapter::GetImageBufferSize() const +{ + return m_imageNumX * m_imageNumY * QSI_IMAGE_BYTES_PER_PIXEL; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns image buffer pixel depth in bytes. +// +unsigned int QSICameraAdapter::GetImageBytesPerPixel() const +{ + return QSI_IMAGE_BYTES_PER_PIXEL; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns image buffer Y-size in pixels. +// +unsigned int QSICameraAdapter::GetImageHeight() const +{ + return m_imageNumY; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns image buffer X-size in pixels. +// +unsigned int QSICameraAdapter::GetImageWidth() const +{ + return m_imageNumX; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +double QSICameraAdapter::GetPixelSizeUm() const +{ + if( m_pixelSizeX == m_pixelSizeY ) + return m_pixelSizeX; + else + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns the actual dimensions of the current ROI. +// +int QSICameraAdapter::GetROI( unsigned int & x, unsigned int & y, unsigned int & xSize, unsigned int & ySize ) +{ + x = m_imageStartX; + y = m_imageStartY; + + xSize = m_imageNumX; + ySize = m_imageNumY; + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Sets binning factor. +// +int QSICameraAdapter::SetBinning( int binF ) +{ + return SetProperty( MM::g_Keyword_Binning, CDeviceUtils::ConvertToString( binF ) ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Sets exposure in milliseconds. +// +void QSICameraAdapter::SetExposure( double exp ) +{ + SetProperty( MM::g_Keyword_Exposure, CDeviceUtils::ConvertToString( exp ) ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Sets the camera Region Of Interest. +// This command will change the dimensions of the image. +// Depending on the hardware capabilities the camera may not be able to configure the +// exact dimensions requested - but should try do as close as possible. +// If the hardware does not have this capability the software should simulate the ROI by +// appropriately cropping each frame. +// +// @param x - top-left corner coordinate +// @param y - top-left corner coordinate +// @param xSize - width +// @param ySize - height +// +int QSICameraAdapter::SetROI( unsigned x, unsigned y, unsigned xSize, unsigned ySize ) +{ + if( xSize == 0 && ySize == 0 ) + { + // According to the sample code, this condition is supposed to clear the ROI + return ClearROI(); + } + else + { + m_status = QSI_SetImageNumX( m_handle, xSize ); + HANDLE_QSI_ERROR( this, m_status ); + m_imageNumX = xSize; + + m_status = QSI_SetImageNumY( m_handle, ySize ); + HANDLE_QSI_ERROR( this, m_status ); + m_imageNumY = ySize; + + m_status = QSI_SetImageStartX( m_handle, x ); + HANDLE_QSI_ERROR( this, m_status ); + m_imageStartX = x; + + m_status = QSI_SetImageStartY( m_handle, y ); + HANDLE_QSI_ERROR( this, m_status ); + m_imageStartY = y; + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Performs exposure. +// +// This function should block during the actual exposure and return immediately afterwards +// (i.e., before readout). This behavior is needed for proper synchronization with the shutter. +// +int QSICameraAdapter::SnapImage() +{ + qsi_bool imageReady; + + m_status = QSI_StartExposure( m_handle, m_exposureDuration / 1000.0, m_exposureOpenShutter ); + HANDLE_QSI_ERROR( this, m_status ); + + imageReady = false; + + if( m_exposureDuration > 2 ) + CDeviceUtils::SleepMs( static_cast( m_exposureDuration ) - 1 ); + + while( !imageReady ) + { + m_status = QSI_GetImageReady( m_handle, &imageReady ); + HANDLE_QSI_ERROR( this, m_status ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Please implement this yourself and do not rely on the base class implementation +// The Base class implementation is deprecated and will be removed shortly +// +int QSICameraAdapter::StartSequenceAcquisition( double interval ) +{ + return base::StartSequenceAcquisition( LONG_MAX, interval, false ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Called by BaseSequenceThread during sequence acquisition. +// +int QSICameraAdapter::ThreadRun() +{ + int response; + + response = SnapImage(); + if( response ) return response; + + response = InsertImage(); + if( response ) return response; + + return response; +} + +#pragma endregion + +#pragma region [ QSICameraAdapter - Property Methods ] + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::AntiBloomingPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + string value; + qsi_anti_bloom antiBloom; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::ANTI_BLOOMING_HIGH ) == 0 ) + m_status = QSI_SetAntiBlooming( m_handle, QSI_ANTI_BLOOM_HIGH ); + else + m_status = QSI_SetAntiBlooming( m_handle, QSI_ANTI_BLOOM_NORMAL ); + + HANDLE_QSI_ERROR( this, m_status ); + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetAntiBlooming( m_handle, &antiBloom ); + HANDLE_QSI_ERROR( this, m_status ); + + if( antiBloom == QSI_ANTI_BLOOM_HIGH ) + pProp->Set( QSIString::ANTI_BLOOMING_HIGH ); + else + pProp->Set( QSIString::ANTI_BLOOMING_NORMAL ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::AntiBloomingPropertySetup() +{ + qsi_bool canSetAntiBlooming; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetCanSetAntiBlooming( m_handle, &canSetAntiBlooming ); + HANDLE_QSI_ERROR( this, m_status ); + + if( canSetAntiBlooming ) + { + propertyAction = new CPropertyAction( this, &QSICameraAdapter::AntiBloomingPropertyHandler ); + response = CreateProperty( QSIString::ANTI_BLOOMING, QSIString::ANTI_BLOOMING_NORMAL, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::ANTI_BLOOMING_NORMAL ); + values.push_back( QSIString::ANTI_BLOOMING_HIGH ); + + response = SetAllowedValues( QSIString::ANTI_BLOOMING, values ); + assert( response == DEVICE_OK ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::BinningPropertiesSetup() +{ + int maxBinX, maxBinY, maxBin, binX, binY; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetMaxBinX( m_handle, &maxBinX ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_GetMaxBinY( m_handle, &maxBinY ); + HANDLE_QSI_ERROR( this, m_status ); + + maxBin = std::min( maxBinX, maxBinY ); + + m_status = QSI_GetBinX( m_handle, &binX ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_GetBinY( m_handle, &binY ); + HANDLE_QSI_ERROR( this, m_status ); + + // Force symmetric binning + if( binX != binY ) + { + m_status = QSI_SetBinY( m_handle, binX ); + HANDLE_QSI_ERROR( this, m_status ); + } + + m_imageBinning = binX; + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::BinningPropertyHandler ); + response = CreateProperty( MM::g_Keyword_Binning, CDeviceUtils::ConvertToString( m_imageBinning ), MM::Integer, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.reserve( maxBin ); + + for( int i = 0; i < maxBin; i++ ) + values.push_back( CDeviceUtils::ConvertToString( i + 1 ) ); + + response = SetAllowedValues( MM::g_Keyword_Binning, values ); + assert( response == DEVICE_OK ); + + response = CreateProperty( QSIString::BINNING_MAX_X, CDeviceUtils::ConvertToString( maxBinX ), MM::Integer, true ); + assert( response == DEVICE_OK ); + + response = CreateProperty( QSIString::BINNING_MAX_Y, CDeviceUtils::ConvertToString( maxBinY ), MM::Integer, true ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::BinningPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + long binSize; + + if( eAct == MM::AfterSet ) + { + pProp->Get( binSize ); + + m_imageBinning = (int) binSize; + + m_status = QSI_SetBinX( m_handle, (short) binSize ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_SetBinY( m_handle, (short) binSize ); + HANDLE_QSI_ERROR( this, m_status ); + + ClearROI(); + } + else if( eAct == MM::BeforeGet ) + { + pProp->Set( (long) m_imageBinning ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::BodyTemperaturePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + double value; + + if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetHeatSinkTemperature( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status );; + + pProp->Set( value ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::BodyTemperaturePropertySetup() +{ + double value; + CPropertyAction * propertyAction; + int response; + + m_status = QSI_GetHeatSinkTemperature( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status );; + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::BodyTemperaturePropertyHandler ); + response = CreateProperty( QSIString::BODY_TEMPERATURE, CDeviceUtils::ConvertToString( 0.0 ), MM::Float, true, propertyAction ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::CCDTemperaturePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + double value; + + if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetCCDTemperature( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status );; + + pProp->Set( value ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::CCDTemperaturePropertySetup() +{ + double value; + CPropertyAction * propertyAction; + int response; + + m_status = QSI_GetCCDTemperature( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status );; + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::CCDTemperaturePropertyHandler ); + response = CreateProperty( MM::g_Keyword_CCDTemperature, CDeviceUtils::ConvertToString( value ), MM::Float, true, propertyAction ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::CCDTemperatureSetpointPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + double value; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + m_status = QSI_SetCCDTemperatureSetpoint( m_handle, value ); + HANDLE_QSI_ERROR( this, m_status );; + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetCCDTemperatureSetpoint( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status );; + + pProp->Set( value ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::CCDTemperatureSetpointPropertySetup() +{ + double value; + CPropertyAction * propertyAction; + int response; + + m_status = QSI_GetCCDTemperatureSetpoint( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status );; + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::CCDTemperatureSetpointPropertyHandler ); + response = CreateProperty( MM::g_Keyword_CCDTemperatureSetPoint, CDeviceUtils::ConvertToString( value ), MM::Float, false, propertyAction ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::CoolerPowerPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + double value; + + if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetCoolerPower( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status );; + + pProp->Set( value ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::CoolerPowerPropertySetup() +{ + double value; + CPropertyAction * propertyAction; + int response; + + m_status = QSI_GetCoolerPower( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status );; + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::CoolerPowerPropertyHandler ); + response = CreateProperty( QSIString::COOLER_POWER, CDeviceUtils::ConvertToString( value ), MM::Float, true, propertyAction ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::CoolerStatePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + std::string value; + qsi_bool coolerOn; + const char * newValue; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::COOLER_STATE_ON ) == 0 ) + coolerOn = 1; + else + coolerOn = 0; + + m_status = QSI_SetCoolerOn( m_handle, coolerOn ); + HANDLE_QSI_ERROR( this, m_status );; + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetCoolerOn( m_handle, &coolerOn ); + HANDLE_QSI_ERROR( this, m_status );; + + if( coolerOn ) + newValue = QSIString::COOLER_STATE_ON; + else + newValue = QSIString::COOLER_STATE_OFF; + + pProp->Set( newValue ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::CoolerStatePropertySetup() +{ + qsi_bool value; + const char * valueString; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetCoolerOn( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status );; + + if( value ) + valueString = QSIString::COOLER_STATE_ON; + else + valueString = QSIString::COOLER_STATE_OFF; + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::CoolerStatePropertyHandler ); + response = CreateProperty( QSIString::COOLER_STATE, valueString, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::COOLER_STATE_OFF ); + values.push_back( QSIString::COOLER_STATE_ON ); + + response = SetAllowedValues( QSIString::COOLER_STATE, values ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::DescriptionPropertySetup() +{ + char value[QSI_LENGTH_DESCRIPTION]; + int response; + + m_status = QSI_GetDescription( m_handle, value, QSI_LENGTH_DESCRIPTION ); + HANDLE_QSI_ERROR( this, m_status ); + + response = CreateProperty( MM::g_Keyword_Description, value, MM::String, true ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::DriverInfoPropertySetup() +{ + char value[QSI_LENGTH_DRIVER_INFO]; + int response; + + m_status = QSI_GetDriverInfo( m_handle, value, QSI_LENGTH_DRIVER_INFO ); + HANDLE_QSI_ERROR( this, m_status ); + + response = CreateProperty( QSIString::DRIVER_INFO, value, MM::String, true ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::ExposurePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + double value; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + m_exposureDuration = value; + } + else if( eAct == MM::BeforeGet ) + { + pProp->Set( m_exposureDuration ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::ExposurePropertiesSetup() +{ + CPropertyAction * propertyAction; + int response; + + m_status = QSI_GetMaxExposureTime( m_handle, &m_exposureDurationMax ); + HANDLE_QSI_ERROR( this, m_status ); + m_exposureDurationMax *= 1000; // Convert to milliseconds + + m_status = QSI_GetMinExposureTime( m_handle, &m_exposureDurationMin ); + HANDLE_QSI_ERROR( this, m_status ); + m_exposureDurationMin *= 1000; // Convert to milliseconds + + if( m_exposureDuration < m_exposureDurationMin ) + m_exposureDuration = m_exposureDurationMin; + else if( m_exposureDuration > m_exposureDurationMax ) + m_exposureDuration = m_exposureDurationMax; + + response = CreateProperty( QSIString::EXPOSURE_MAX, CDeviceUtils::ConvertToString( m_exposureDurationMax ), MM::String, true ); + assert( response == DEVICE_OK ); + + response = CreateProperty( QSIString::EXPOSURE_MIN, CDeviceUtils::ConvertToString( m_exposureDurationMin ), MM::String, true ); + assert( response == DEVICE_OK ); + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::ExposurePropertyHandler ); + response = CreateProperty( MM::g_Keyword_Exposure, CDeviceUtils::ConvertToString( m_exposureDuration ), MM::Float, false, propertyAction ); + assert( response == DEVICE_OK ); + + response = SetPropertyLimits( MM::g_Keyword_Exposure, m_exposureDurationMin, m_exposureDurationMax ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::FanModePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + string value; + qsi_fan_mode fanMode; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::FAN_MODE_OFF ) == 0 ) + m_status = QSI_SetFanMode( m_handle, QSI_FAN_OFF ); + else if( value.compare( QSIString::FAN_MODE_QUIET ) == 0 ) + m_status = QSI_SetFanMode( m_handle, QSI_FAN_QUIET ); + else + m_status = QSI_SetFanMode( m_handle, QSI_FAN_FULL ); + + HANDLE_QSI_ERROR( this, m_status ); + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetFanMode( m_handle, &fanMode ); + HANDLE_QSI_ERROR( this, m_status ); + + if( fanMode == QSI_FAN_OFF ) + pProp->Set( QSIString::FAN_MODE_OFF ); + else if( fanMode == QSI_FAN_QUIET ) + pProp->Set( QSIString::FAN_MODE_QUIET ); + else + pProp->Set( QSIString::FAN_MODE_FULL ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::FanModePropertySetup() +{ + CPropertyAction * propertyAction; + int response; + vector values; + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::FanModePropertyHandler ); + response = CreateProperty( QSIString::FAN_MODE, QSIString::FAN_MODE_QUIET, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::FAN_MODE_OFF ); + values.push_back( QSIString::FAN_MODE_QUIET ); + values.push_back( QSIString::FAN_MODE_FULL ); + + response = SetAllowedValues( QSIString::FAN_MODE, values ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::FilterWheelPositionPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + long value; + int position; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + position = static_cast( value ); + + m_status = QSI_SetFilterWheelPosition( m_handle, position - 1 ); + HANDLE_QSI_ERROR( this, m_status ); + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetFilterWheelPosition( m_handle, &position ); + HANDLE_QSI_ERROR( this, m_status ); + + pProp->Set( CDeviceUtils::ConvertToString( position + 1 ) ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::FilterWheelPropertiesSetup() +{ + qsi_bool hasFilterWheel; + const char * hasFilterWheelString; + int filterWheelPositionCount; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetHasFilterWheel( m_handle, &hasFilterWheel ); + HANDLE_QSI_ERROR( this, m_status ); + + if( hasFilterWheel ) + hasFilterWheelString = QSIString::HAS_FILTER_WHEEL_YES; + else + hasFilterWheelString = QSIString::HAS_FILTER_WHEEL_NO; + + response = CreateProperty( QSIString::HAS_FILTER_WHEEL, hasFilterWheelString , MM::String, true ); + assert( response == DEVICE_OK ); + + if( hasFilterWheel ) + { + m_status = QSI_GetFilterWheelPositionCount( m_handle, &filterWheelPositionCount ); + HANDLE_QSI_ERROR( this, m_status ); + + response = CreateProperty( QSIString::FILTER_WHEEL_POSITIONS, CDeviceUtils::ConvertToString( filterWheelPositionCount ), MM::Integer, true ); + assert( response == DEVICE_OK ); + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::FilterWheelPositionPropertyHandler ); + response = CreateProperty( QSIString::FILTER_WHEEL_POSITION, CDeviceUtils::ConvertToString( 1 ), MM::Integer, false, propertyAction ); + assert( response == DEVICE_OK ); + + for( int i = 0; i < filterWheelPositionCount; i++ ) + values.push_back( CDeviceUtils::ConvertToString( i + 1 ) ); + + response = SetAllowedValues( QSIString::FILTER_WHEEL_POSITION, values ); + assert( response == DEVICE_OK ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::FullWellCapacityPropertySetup() +{ + double value; + int response; + + m_status = QSI_GetFullWellCapacity( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status ); + + response = CreateProperty( QSIString::FULL_WELL_CAPACITY, CDeviceUtils::ConvertToString( value ), MM::Float, true ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::GainPropertiesSetup() +{ + qsi_bool canSetGain; + double gain; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetCanSetGainMode( m_handle, &canSetGain ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_GetElectronsPerADU( m_handle, &gain ); + HANDLE_QSI_ERROR( this, m_status ); + + if( canSetGain ) + { + propertyAction = new CPropertyAction( this, &QSICameraAdapter::GainModePropertyHandler ); + response = CreateProperty( QSIString::GAIN_MODE, QSIString::GAIN_MODE_HIGH, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::GAIN_MODE_HIGH ); + values.push_back( QSIString::GAIN_MODE_LOW ); + + response = SetAllowedValues( QSIString::GAIN_MODE, values ); + assert( response == DEVICE_OK ); + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::GainPropertyHandler ); + response = CreateProperty( MM::g_Keyword_Gain, CDeviceUtils::ConvertToString( gain ), MM::Float, true, propertyAction ); + assert( response == DEVICE_OK ); + } + else + { + response = CreateProperty( MM::g_Keyword_Gain, CDeviceUtils::ConvertToString( gain ), MM::Float, true ); + assert( response == DEVICE_OK ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::GainPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + double value; + + if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetElectronsPerADU( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status ); + + pProp->Set( value ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::GainModePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + string value; + qsi_gain_mode gain; + int response; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::GAIN_MODE_HIGH ) == 0 ) + m_status = QSI_SetGainMode( m_handle, QSI_GAIN_MODE_HIGH ); + else + m_status = QSI_SetGainMode( m_handle, QSI_GAIN_MODE_LOW ); + + HANDLE_QSI_ERROR( this, m_status ); + + response = UpdateProperty( MM::g_Keyword_Gain ); + HANDLE_MM_ERROR( this, response, "An error occurred while updating the Gain property." ); + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetGainMode( m_handle, &gain ); + HANDLE_QSI_ERROR( this, m_status ); + + if( gain == QSI_GAIN_MODE_HIGH ) + pProp->Set( QSIString::GAIN_MODE_HIGH ); + else + pProp->Set( QSIString::GAIN_MODE_LOW ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::LEDEnabledPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + string value; + qsi_bool enabled; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::LED_SETTING_ENABLED ) == 0 ) + enabled = 1; + else + enabled = 0; + + m_status = QSI_SetLEDEnabled( m_handle, enabled ); + HANDLE_QSI_ERROR( this, m_status ); + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetLEDEnabled( m_handle, &enabled ); + HANDLE_QSI_ERROR( this, m_status ); + + if( enabled ) + pProp->Set( QSIString::LED_SETTING_ENABLED ); + else + pProp->Set( QSIString::LED_SETTING_DISABLED ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::LEDEnabledPropertySetup() +{ + qsi_bool canSetLEDEnabled; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetCanSetLEDEnabled( m_handle, &canSetLEDEnabled ); + HANDLE_QSI_ERROR( this, m_status ); + + if( canSetLEDEnabled ) + { + propertyAction = new CPropertyAction( this, &QSICameraAdapter::LEDEnabledPropertyHandler ); + response = CreateProperty( QSIString::LED_SETTING, QSIString::LED_SETTING_ENABLED, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::LED_SETTING_ENABLED ); + values.push_back( QSIString::LED_SETTING_DISABLED ); + + response = SetAllowedValues( QSIString::LED_SETTING, values ); + assert( response == DEVICE_OK ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::MaxADUPropertySetup() +{ + int value; + int response; + + m_status = QSI_GetMaxADU( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status ); + + response = CreateProperty( QSIString::MAX_ADU, CDeviceUtils::ConvertToString( value ), MM::Float, true ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::ModelNamePropertySetup() +{ + char value[QSI_LENGTH_MODEL_NAME]; + int response; + + m_status = QSI_GetModelName( m_handle, value, QSI_LENGTH_MODEL_NAME ); + HANDLE_QSI_ERROR( this, m_status );; + + response = CreateProperty( QSIString::MODEL_NAME, value, MM::String, true ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::ModelNumberPropertySetup() +{ + char value[QSI_LENGTH_MODEL_NUMBER]; + int response; + + m_status = QSI_GetModelNumber( m_handle, value, QSI_LENGTH_MODEL_NUMBER ); + HANDLE_QSI_ERROR( this, m_status );; + + response = CreateProperty( QSIString::MODEL_NUMBER, value, MM::String, true ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::OpenShutterPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + string value; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::OPEN_SHUTTER_YES ) == 0 ) + m_exposureOpenShutter = true; + else + m_exposureOpenShutter = false; + } + else if( eAct == MM::BeforeGet ) + { + if( m_exposureOpenShutter ) + pProp->Set( QSIString::OPEN_SHUTTER_YES ); + else + pProp->Set( QSIString::OPEN_SHUTTER_NO ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::OpenShutterPropertySetup() +{ + CPropertyAction * propertyAction; + int response; + vector values; + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::OpenShutterPropertyHandler ); + response = CreateProperty( QSIString::OPEN_SHUTTER, QSIString::OPEN_SHUTTER_YES, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::OPEN_SHUTTER_NO ); + values.push_back( QSIString::OPEN_SHUTTER_YES ); + + response = SetAllowedValues( QSIString::OPEN_SHUTTER, values ); + assert( response == DEVICE_OK ); + + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::PCBTemperaturePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + double value; + + if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetPCBTemperature( m_handle, &value ); + HANDLE_QSI_ERROR( this, m_status ); + + pProp->Set( CDeviceUtils::ConvertToString( value ) ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::PCBTemperaturePropertySetup() +{ + qsi_bool canGetPCBTemperature; + CPropertyAction * propertyAction; + int response; + + m_status = QSI_GetCanGetPCBTemperature( m_handle, &canGetPCBTemperature ); + HANDLE_QSI_ERROR( this, m_status ); + + if( canGetPCBTemperature ) + { + propertyAction = new CPropertyAction( this, &QSICameraAdapter::PCBTemperaturePropertyHandler ); + response = CreateProperty( QSIString::PCB_TEMPERATURE, CDeviceUtils::ConvertToString( 0 ), MM::Float, true, propertyAction ); + assert( response == DEVICE_OK ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::PixelSizePropertiesSetup() +{ + int response; + + m_status = QSI_GetPixelSizeX( m_handle, &m_pixelSizeX ); + HANDLE_QSI_ERROR( this, m_status ); + + m_status = QSI_GetPixelSizeY( m_handle, &m_pixelSizeY ); + HANDLE_QSI_ERROR( this, m_status ); + + response = CreateProperty( QSIString::PIXEL_SIZE_X, CDeviceUtils::ConvertToString( m_pixelSizeX ), MM::Float, true ); + assert( response == DEVICE_OK ); + + response = CreateProperty( QSIString::PIXEL_SIZE_Y, CDeviceUtils::ConvertToString( m_pixelSizeY ), MM::Float, true ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::PreExposureFlushPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + string value; + qsi_pre_exposure_flush preExposureFlush; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::PRE_EXPOSURE_FLUSH_NONE ) == 0 ) + preExposureFlush = QSI_FLUSH_NONE; + else if( value.compare( QSIString::PRE_EXPOSURE_FLUSH_MODEST ) == 0 ) + preExposureFlush = QSI_FLUSH_MODEST; + else if( value.compare( QSIString::PRE_EXPOSURE_FLUSH_AGGRESSIVE ) == 0 ) + preExposureFlush = QSI_FLUSH_AGGRESSIVE; + else if( value.compare( QSIString::PRE_EXPOSURE_FLUSH_VERY_AGGRESSIVE ) == 0 ) + preExposureFlush = QSI_FLUSH_VERY_AGGRESSIVE; + else + preExposureFlush = QSI_FLUSH_NORMAL; + + m_status = QSI_SetPreExposureFlush( m_handle, preExposureFlush ); + HANDLE_QSI_ERROR( this, m_status ); + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetPreExposureFlush( m_handle, &preExposureFlush ); + HANDLE_QSI_ERROR( this, m_status ); + + if( preExposureFlush == QSI_FLUSH_NONE ) + pProp->Set( QSIString::PRE_EXPOSURE_FLUSH_NONE ); + else if( preExposureFlush == QSI_FLUSH_MODEST ) + pProp->Set( QSIString::PRE_EXPOSURE_FLUSH_MODEST ); + else if( preExposureFlush == QSI_FLUSH_AGGRESSIVE ) + pProp->Set( QSIString::PRE_EXPOSURE_FLUSH_AGGRESSIVE ); + else if( preExposureFlush == QSI_FLUSH_VERY_AGGRESSIVE ) + pProp->Set( QSIString::PRE_EXPOSURE_FLUSH_VERY_AGGRESSIVE ); + else + pProp->Set( QSIString::PRE_EXPOSURE_FLUSH_NORMAL ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::PreExposureFlushPropertySetup() +{ + qsi_bool canSetPreExposureFlush; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetCanSetPreExposureFlush( m_handle, &canSetPreExposureFlush ); + HANDLE_QSI_ERROR( this, m_status ); + + if( canSetPreExposureFlush ) + { + propertyAction = new CPropertyAction( this, &QSICameraAdapter::PreExposureFlushPropertyHandler ); + response = CreateProperty( QSIString::PRE_EXPOSURE_FLUSH, QSIString::PRE_EXPOSURE_FLUSH_NORMAL, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::PRE_EXPOSURE_FLUSH_NONE ); + values.push_back( QSIString::PRE_EXPOSURE_FLUSH_MODEST ); + values.push_back( QSIString::PRE_EXPOSURE_FLUSH_NORMAL ); + values.push_back( QSIString::PRE_EXPOSURE_FLUSH_AGGRESSIVE ); + values.push_back( QSIString::PRE_EXPOSURE_FLUSH_VERY_AGGRESSIVE ); + + response = SetAllowedValues( QSIString::PRE_EXPOSURE_FLUSH, values ); + assert( response == DEVICE_OK ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::ReadoutModePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + string value; + qsi_readout_speed readoutSpeed; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::READOUT_MODE_FAST_READOUT ) == 0 ) + m_status = QSI_SetReadoutSpeed( m_handle, QSI_READOUT_FAST ); + else + m_status = QSI_SetReadoutSpeed( m_handle, QSI_READOUT_HIGH_QUALITY ); + + HANDLE_QSI_ERROR( this, m_status ); + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetReadoutSpeed( m_handle, &readoutSpeed ); + HANDLE_QSI_ERROR( this, m_status ); + + if( readoutSpeed == QSI_READOUT_FAST ) + pProp->Set( QSIString::READOUT_MODE_FAST_READOUT ); + else + pProp->Set( QSIString::READOUT_MODE_HIGH_QUALITY ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::ReadoutModePropertySetup() +{ + qsi_bool canSetReadoutSpeed; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetCanSetReadoutSpeed( m_handle, &canSetReadoutSpeed ); + HANDLE_QSI_ERROR( this, m_status ); + + propertyAction = new CPropertyAction( this, &QSICameraAdapter::ReadoutModePropertyHandler ); + response = CreateProperty( MM::g_Keyword_ReadoutMode, QSIString::READOUT_MODE_HIGH_QUALITY, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::READOUT_MODE_HIGH_QUALITY ); + + if( canSetReadoutSpeed ) + values.push_back( QSIString::READOUT_MODE_FAST_READOUT ); + + response = SetAllowedValues( MM::g_Keyword_ReadoutMode, values ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::ShutterPropertiesSetup() +{ + qsi_bool hasShutter, canSetShutterPriority; + const char * hasShutterString; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetHasShutter( m_handle, &hasShutter ); + HANDLE_QSI_ERROR( this, m_status ); + + if( hasShutter ) + hasShutterString = QSIString::HAS_SHUTTER_YES; + else + hasShutterString = QSIString::HAS_SHUTTER_NO; + + response = CreateProperty( QSIString::HAS_SHUTTER, hasShutterString, MM::String, true ); + assert( response == DEVICE_OK ); + + m_status = QSI_GetCanSetShutterPriority( m_handle, &canSetShutterPriority ); + HANDLE_QSI_ERROR( this, m_status ); + + if( hasShutter && canSetShutterPriority ) + { + propertyAction = new CPropertyAction( this, &QSICameraAdapter::ShutterPriorityPropertyHandler ); + response = CreateProperty( QSIString::SHUTTER_PRIORITY, QSIString::SHUTTER_PRIORITY_ELECTRONIC, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::SHUTTER_PRIORITY_ELECTRONIC ); + values.push_back( QSIString::SHUTTER_PRIORITY_MECHANICAL ); + + response = SetAllowedValues( QSIString::SHUTTER_PRIORITY, values ); + assert( response == DEVICE_OK ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::ShutterPriorityPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + string value; + qsi_shutter_priority shutterPriority; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::SHUTTER_PRIORITY_ELECTRONIC ) == 0 ) + shutterPriority = QSI_SHUTTER_PRIORITY_ELECTRONIC; + else + shutterPriority = QSI_SHUTTER_PRIORITY_MECHANICAL; + + m_status = QSI_SetShutterPriority( m_handle, shutterPriority ); + HANDLE_QSI_ERROR( this, m_status ); + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetShutterPriority( m_handle, &shutterPriority ); + HANDLE_QSI_ERROR( this, m_status ); + + if( shutterPriority == QSI_SHUTTER_PRIORITY_ELECTRONIC ) + pProp->Set( QSIString::SHUTTER_PRIORITY_ELECTRONIC ); + else + pProp->Set( QSIString::SHUTTER_PRIORITY_MECHANICAL ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::SerialNumberPropertySetup() +{ + char serialNumber[QSI_LENGTH_SERIAL_NUMBER]; + int response; + + m_status = QSI_GetSerialNumber( m_handle, serialNumber, QSI_LENGTH_SERIAL_NUMBER ); + HANDLE_QSI_ERROR( this, m_status ); + + response = CreateProperty( QSIString::SERIAL_NUMBER, serialNumber, MM::String, true ); + assert( response == DEVICE_OK ); + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::SoundEnabledPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ) +{ + string value; + qsi_bool enabled; + + if( eAct == MM::AfterSet ) + { + pProp->Get( value ); + + if( value.compare( QSIString::SOUND_SETTING_ENABLED ) == 0 ) + enabled = 1; + else + enabled = 0; + + m_status = QSI_SetSoundEnabled( m_handle, enabled ); + HANDLE_QSI_ERROR( this, m_status ); + } + else if( eAct == MM::BeforeGet ) + { + m_status = QSI_GetSoundEnabled( m_handle, &enabled ); + HANDLE_QSI_ERROR( this, m_status ); + + if( enabled ) + pProp->Set( QSIString::SOUND_SETTING_ENABLED ); + else + pProp->Set( QSIString::SOUND_SETTING_DISABLED ); + } + + return DEVICE_OK; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +int QSICameraAdapter::SoundEnabledPropertySetup() +{ + qsi_bool canSetSoundEnabled; + CPropertyAction * propertyAction; + int response; + vector values; + + m_status = QSI_GetCanSetSoundEnabled( m_handle, &canSetSoundEnabled ); + HANDLE_QSI_ERROR( this, m_status ); + + if( canSetSoundEnabled ) + { + propertyAction = new CPropertyAction( this, &QSICameraAdapter::SoundEnabledPropertyHandler ); + response = CreateProperty( QSIString::SOUND_SETTING, QSIString::SOUND_SETTING_ENABLED, MM::String, false, propertyAction ); + assert( response == DEVICE_OK ); + + values.push_back( QSIString::SOUND_SETTING_ENABLED ); + values.push_back( QSIString::SOUND_SETTING_DISABLED ); + + response = SetAllowedValues( QSIString::SOUND_SETTING, values ); + assert( response == DEVICE_OK ); + } + + return DEVICE_OK; +} + +#pragma endregion diff --git a/DeviceAdapters/QSI/QSICameraAdapter.h b/DeviceAdapters/QSI/QSICameraAdapter.h new file mode 100644 index 000000000..19b59e220 --- /dev/null +++ b/DeviceAdapters/QSI/QSICameraAdapter.h @@ -0,0 +1,160 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: QSICameraAdapter.h +// PROJECT: Micro-Manager +// SUBSYSTEM: DeviceAdapters +//----------------------------------------------------------------------------- +// DESCRIPTION: QSI camera adapter for use with Micro-Manager +// +// AUTHOR: Mike Barry, http://www.qsimaging.com +// +// COPYRIGHT: Quantum Scientific Imaging, Inc. 2013 +// +// LICENSE: This file is distributed under the BSD license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// + +#ifndef _QSICAMERAADAPTER_H_ +#define _QSICAMERAADAPTER_H_ + +#include "DeviceBase.h" +#include "ImgBuffer.h" +#include "ModuleInterface.h" + +#include "QSICameraCLib.h" +#include "QSIError.h" + +class QSICameraAdapter : public CCameraBase +{ +private: + + typedef CCameraBase base; + +public: + + ///////////////////////////////////////////////////////////////////////////// + // Constructor / Destructor + // + QSICameraAdapter(); + ~QSICameraAdapter(); + + ///////////////////////////////////////////////////////////////////////////// + // MMDevice API + // + int Initialize(); + int Shutdown(); + void GetName( char * name ) const; + + ///////////////////////////////////////////////////////////////////////////// + // Camera API + // + int ClearROI(); + unsigned int GetBitDepth() const; + const unsigned char * GetImageBuffer(); + long GetImageBufferSize() const; + int GetBinning() const; + double GetExposure() const; + unsigned int GetImageBytesPerPixel() const; + unsigned int GetImageHeight() const; + unsigned int GetImageWidth() const; + double GetPixelSizeUm() const; + int GetROI( unsigned int & x, unsigned int & y, unsigned int & xSize, unsigned int & ySize ); + int InsertImage(); + int IsExposureSequenceable( bool & seq ) const; + int SetBinning( int binSize ); + void SetExposure( double exp ); + int SetROI( unsigned int x, unsigned int y, unsigned int xSize, unsigned int ySize ); + int SnapImage(); + int StartSequenceAcquisition( double interval ); + int ThreadRun(); + + ///////////////////////////////////////////////////////////////////////////// + // Property setup and handling + // + int AntiBloomingPropertySetup(); + int AntiBloomingPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int BinningPropertiesSetup(); + int BinningPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int BodyTemperaturePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int BodyTemperaturePropertySetup(); + int CCDTemperaturePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int CCDTemperaturePropertySetup(); + int CCDTemperatureSetpointPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int CCDTemperatureSetpointPropertySetup(); + int DescriptionPropertySetup(); + int DriverInfoPropertySetup(); + int CoolerStatePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int CoolerStatePropertySetup(); + int CoolerPowerPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int CoolerPowerPropertySetup(); + int ExposurePropertiesSetup(); + int ExposurePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int FanModePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int FanModePropertySetup(); + int FilterWheelPositionPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int FilterWheelPropertiesSetup(); + int FullWellCapacityPropertySetup(); + int GainPropertiesSetup(); + int GainPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int GainModePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int LEDEnabledPropertySetup(); + int LEDEnabledPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int MaxADUPropertySetup(); + int ModelNamePropertySetup(); + int ModelNumberPropertySetup(); + int OpenShutterPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int OpenShutterPropertySetup(); + int PCBTemperaturePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int PCBTemperaturePropertySetup(); + int PixelSizePropertiesSetup(); + int PreExposureFlushPropertySetup(); + int PreExposureFlushPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int ReadoutModePropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int ReadoutModePropertySetup(); + int SerialNumberPropertySetup(); + int ShutterPropertiesSetup(); + int ShutterPriorityPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + int SoundEnabledPropertySetup(); + int SoundEnabledPropertyHandler( MM::PropertyBase * pProp, MM::ActionType eAct ); + +private: + +#pragma region [ Private Members ] + + static const int QSI_IMAGE_BYTES_PER_PIXEL = 2; + static const int QSI_IMAGE_BIT_DEPTH = QSI_IMAGE_BYTES_PER_PIXEL * 8; + + bool m_initialized; + + qsi_handle m_handle; + qsi_status m_status; + + bool m_exposureOpenShutter; + double m_exposureDuration; + double m_exposureDurationMax; + double m_exposureDurationMin; + + int m_imageBinning; + int m_imageMaxX; + int m_imageMaxY; + int m_imageNumX; + int m_imageNumY; + int m_imageStartX; + int m_imageStartY; + unsigned short * m_pImageBuffer; + + double m_pixelSizeX; + double m_pixelSizeY; + +#pragma endregion + +}; + +#endif //_QSICAMERAADAPTER_H_ diff --git a/DeviceAdapters/QSI/QSIImage.h b/DeviceAdapters/QSI/QSIImage.h new file mode 100644 index 000000000..c969b4461 --- /dev/null +++ b/DeviceAdapters/QSI/QSIImage.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +class QSIImage +{ +public: + + QSIImage( unsigned int maxX, unsigned int maxY ) : + m_buffer( 0 ), + m_maxX( 0 ), + m_maxY( 0 ), + m_maxSize( 0 ) + { + m_maxX = maxX; + m_maxY = maxY; + m_maxSize = maxX * maxY; + + m_buffer = (unsigned short *) malloc( m_maxSize * 2 ); + memset( m_buffer, 0, m_maxSize * 2 ); + } + + ~QSIImage( void ) + { + free( m_buffer ); + } + + unsigned short * GetBuffer() + { + return m_buffer; + } + + + +private: + + unsigned short * m_buffer; + unsigned int m_maxX; + unsigned int m_maxY; + unsigned int m_maxSize; + +}; + diff --git a/DeviceAdapters/QSI/QSIToolkit.h b/DeviceAdapters/QSI/QSIToolkit.h new file mode 100644 index 000000000..e23a918bd --- /dev/null +++ b/DeviceAdapters/QSI/QSIToolkit.h @@ -0,0 +1,61 @@ +#pragma once +#ifndef _QSITOOLKIT_H_ +#define _QSITOOLKIT_H_ + +#include + +#include "QSICameraAdapter.h" +#include "QSICameraCLib.h" + +#pragma region [ Macros ] + +/////////////////////////////////////////////////////////////////////////////// +// +#define HANDLE_QSI_ERROR(cameraAdapterReference,errorCode) \ + do \ + { \ + if( (errorCode) ) \ + { \ + (cameraAdapterReference)->LogMessage( GetLastQSIError( m_handle ), false ); \ + return (errorCode); \ + } \ + } \ + while( false ) + +/////////////////////////////////////////////////////////////////////////////// +// +#define HANDLE_MM_ERROR(cameraAdapterReference,errorCode,errorMessage) \ + do \ + { \ + if( (errorCode) ) \ + { \ + (cameraAdapterReference)->LogMessage( (errorMessage), false ); \ + return (errorCode); \ + } \ + } \ + while( false ) + +#pragma endregion + +#pragma region [ Global Methods ] + +/////////////////////////////////////////////////////////////////////////////// +// +std::string GetLastQSIError( qsi_handle handle ) +{ + char errorStringArray[QSI_LENGTH_ERROR_STRING]; + qsi_status response; + + memset( errorStringArray, 0, QSI_LENGTH_ERROR_STRING ); // Not really necessary but it never hurts + + response = QSI_GetLastError( handle, errorStringArray, QSI_LENGTH_ERROR_STRING ); + + if( response == QSI_ERROR_INVALID_HANDLE ) + return std::string( "Unable to get last error due to bad handle" ); + + return std::string( errorStringArray, QSI_LENGTH_ERROR_STRING ); +} + +#pragma endregion + +#endif //_QSITOOLKIT_H_ \ No newline at end of file diff --git a/micromanager.sln b/micromanager.sln index 0874f84bf..8f76bda47 100644 --- a/micromanager.sln +++ b/micromanager.sln @@ -476,6 +476,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ESP32", "DeviceAdapters\ESP EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WOSM", "DeviceAdapters\WOSM\WOSM.vcxproj", "{64E94A9E-B3AE-47EF-8F5A-0356B0E6B9DA}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QSI", "DeviceAdapters\QSI\QSI.vcxproj", "{320AF64E-C4FC-4A97-A7D1-F8233A8A44C7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -1430,6 +1432,10 @@ Global {64E94A9E-B3AE-47EF-8F5A-0356B0E6B9DA}.Debug|x64.Build.0 = Debug|x64 {64E94A9E-B3AE-47EF-8F5A-0356B0E6B9DA}.Release|x64.ActiveCfg = Release|x64 {64E94A9E-B3AE-47EF-8F5A-0356B0E6B9DA}.Release|x64.Build.0 = Release|x64 + {320AF64E-C4FC-4A97-A7D1-F8233A8A44C7}.Debug|x64.ActiveCfg = Debug|x64 + {320AF64E-C4FC-4A97-A7D1-F8233A8A44C7}.Debug|x64.Build.0 = Debug|x64 + {320AF64E-C4FC-4A97-A7D1-F8233A8A44C7}.Release|x64.ActiveCfg = Release|x64 + {320AF64E-C4FC-4A97-A7D1-F8233A8A44C7}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From a58eb730808c9c8f6261e7174bee8a8706fff4be Mon Sep 17 00:00:00 2001 From: Atik-Callum <129527371+Atik-Callum@users.noreply.github.com> Date: Tue, 2 Jan 2024 13:58:46 +0000 Subject: [PATCH 114/141] Added license --- DeviceAdapters/QSI/QSICameraAdapter.cpp | 16 ++------------ DeviceAdapters/QSI/QSICameraAdapter.h | 17 ++------------- DeviceAdapters/QSI/license.txt | 28 +++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 DeviceAdapters/QSI/license.txt diff --git a/DeviceAdapters/QSI/QSICameraAdapter.cpp b/DeviceAdapters/QSI/QSICameraAdapter.cpp index 9f6c71d99..c52873ff5 100644 --- a/DeviceAdapters/QSI/QSICameraAdapter.cpp +++ b/DeviceAdapters/QSI/QSICameraAdapter.cpp @@ -4,21 +4,9 @@ // SUBSYSTEM: DeviceAdapters //----------------------------------------------------------------------------- // DESCRIPTION: QSI camera adapter for use with Micro-Manager -// -// AUTHOR: Mike Barry, http://www.qsimaging.com -// -// COPYRIGHT: Quantum Scientific Imaging, Inc. 2013 +// +// COPYRIGHT: Quantum Scientific Imaging, Inc. 2024 // -// LICENSE: This file is distributed under the BSD license. -// License text is included with the source distribution. -// -// This file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. #include "QSICameraAdapter.h" #include "QSIToolkit.h" diff --git a/DeviceAdapters/QSI/QSICameraAdapter.h b/DeviceAdapters/QSI/QSICameraAdapter.h index 19b59e220..435d9bd25 100644 --- a/DeviceAdapters/QSI/QSICameraAdapter.h +++ b/DeviceAdapters/QSI/QSICameraAdapter.h @@ -4,21 +4,8 @@ // SUBSYSTEM: DeviceAdapters //----------------------------------------------------------------------------- // DESCRIPTION: QSI camera adapter for use with Micro-Manager -// -// AUTHOR: Mike Barry, http://www.qsimaging.com -// -// COPYRIGHT: Quantum Scientific Imaging, Inc. 2013 -// -// LICENSE: This file is distributed under the BSD license. -// License text is included with the source distribution. -// -// This file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// COPYRIGHT: Quantum Scientific Imaging, Inc. 2024 // #ifndef _QSICAMERAADAPTER_H_ diff --git a/DeviceAdapters/QSI/license.txt b/DeviceAdapters/QSI/license.txt new file mode 100644 index 000000000..4571b1712 --- /dev/null +++ b/DeviceAdapters/QSI/license.txt @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2024, QSI + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From fa023097b0840cfd30c6089031efa0b6e9e3d9a4 Mon Sep 17 00:00:00 2001 From: Greg Sawyer Date: Thu, 4 Jan 2024 15:02:36 -0600 Subject: [PATCH 115/141] -(MicroDrive) Added support for new products. -(MicroDrive) Updated device initialization. New algorithm for selecting which device and axis gets selected when a new device adpater is initialized. -(Madlib) Fixed a bug with handle acquisition. --- .../MCL_MicroDrive/AcquireDevice.cpp | 292 ++++++++ DeviceAdapters/MCL_MicroDrive/AcquireDevice.h | 3 + .../MCL_MicroDrive/HandleListType.h | 1 + .../MCL_MicroDrive/MCL_MicroDrive.cpp | 7 +- .../MCL_MicroDrive/MCL_MicroDrive.h | 57 +- .../MCL_MicroDrive/MCL_MicroDrive.vcxproj | 4 + .../MCL_MicroDrive.vcxproj.filters | 12 + DeviceAdapters/MCL_MicroDrive/MadTweezer.cpp | 656 ++++++++++++++++++ DeviceAdapters/MCL_MicroDrive/MadTweezer.h | 99 +++ DeviceAdapters/MCL_MicroDrive/MicroDrive.h | 89 ++- .../MCL_MicroDrive/MicroDrive64.lib | Bin 1682462 -> 1866886 bytes .../MCL_MicroDrive/MicroDriveXYStage.cpp | 558 +++++++++------ .../MCL_MicroDrive/MicroDriveXYStage.h | 42 +- .../MCL_MicroDrive/MicroDriveZStage.cpp | 470 +++++++------ .../MCL_MicroDrive/MicroDriveZStage.h | 35 +- DeviceAdapters/MCL_NanoDrive/MCL_Common.cpp | 7 + DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.h | 3 +- .../MCL_NanoDrive/MCL_NanoDrive_XYStage.cpp | 13 +- .../MCL_NanoDrive/MCL_NanoDrive_XYStage.h | 3 +- .../MCL_NanoDrive/MCL_NanoDrive_ZStage.cpp | 20 +- .../MCL_NanoDrive/MCL_NanoDrive_ZStage.h | 3 +- 21 files changed, 1884 insertions(+), 490 deletions(-) create mode 100644 DeviceAdapters/MCL_MicroDrive/AcquireDevice.cpp create mode 100644 DeviceAdapters/MCL_MicroDrive/AcquireDevice.h create mode 100644 DeviceAdapters/MCL_MicroDrive/MadTweezer.cpp create mode 100644 DeviceAdapters/MCL_MicroDrive/MadTweezer.h diff --git a/DeviceAdapters/MCL_MicroDrive/AcquireDevice.cpp b/DeviceAdapters/MCL_MicroDrive/AcquireDevice.cpp new file mode 100644 index 000000000..6bba3df35 --- /dev/null +++ b/DeviceAdapters/MCL_MicroDrive/AcquireDevice.cpp @@ -0,0 +1,292 @@ +/* +File: AcquireDevice.cpp +Copyright: Mad City Labs Inc., 2023 +License: Distributed under the BSD license. +*/ + +// MCL headers +#include "MicroDrive.h" +#include "AcquireDevice.h" +#include "MCL_MicroDrive.h" + +// List/heap headers +#include "handle_list_if.h" +#include "HandleListType.h" + +#include +using namespace std; + +static int ChooseAvailableXYStageAxes(unsigned short pid, unsigned char axisBitmap, int handle, bool useStrictChoices); + +static int ChooseAvailableZStageAxis(unsigned short pid, unsigned char axisBitmap, int handle, bool useStrictChoices); + +static int ChooseAvailableMadTweezerStageAxis(unsigned short pid, unsigned char axisBitmap, int handle, bool useStrictChoices); + +static bool FindMatchingDevice(int deviceAdapterType, int &deviceAdapterHandle, int &deviceAdapterAxis); + +static bool FindMatchingDeviceInList(int deviceAdapterType, int *handles, int handlesCount, bool useStrictMatcingCriteria, int &deviceAdapterHandle, int &deviceAdapterAxis); + +static void ReleaseUnusedDevices(); + + +int AcquireDeviceHandle(int deviceType, int &deviceAdapterHandle, int &deviceAdapterAxis) +{ + deviceAdapterAxis = 0; + deviceAdapterHandle = 0; + + // Attempt to find a device that can perform the deviceType role. + bool foundDevice = FindMatchingDevice(deviceType, deviceAdapterHandle, deviceAdapterAxis); + if (foundDevice) + { + // If we found a device add it to our list. + HandleListType newDeviceAdapter(deviceAdapterHandle, deviceType, deviceAdapterAxis, (deviceType == XYSTAGE_TYPE ? (deviceAdapterAxis + 1) : 0)); + HandleListAddToLockedList(newDeviceAdapter); + } + + // Release devices that are not needed. + ReleaseUnusedDevices(); + + return foundDevice ? MCL_SUCCESS : MCL_INVALID_HANDLE; +} + +bool FindMatchingDevice(int deviceAdapterType, int &deviceAdapterHandle, int &deviceAdapterAxis) +{ + bool foundDevice = false; + deviceAdapterHandle = 0; + deviceAdapterAxis = 0; + + // First search through our existing devices to find appropriate matching axes. + int existingHandlesCount = MCL_NumberOfCurrentHandles(); + if (existingHandlesCount != 0) + { + int *existingHandles = new int[existingHandlesCount]; + existingHandlesCount = MCL_GetAllHandles(existingHandles, existingHandlesCount); + foundDevice = FindMatchingDeviceInList(deviceAdapterType, existingHandles, existingHandlesCount, true, deviceAdapterHandle, deviceAdapterAxis); + delete[] existingHandles; + + if (foundDevice) + return true; + } + + // Next search through all available Micro-Drive systems. + int handlesCount = MCL_GrabAllHandles(); + if (handlesCount == 0) + { + return false; + } + int* handles = new int[handlesCount]; + handlesCount = MCL_GetAllHandles(handles, handlesCount); + foundDevice = FindMatchingDeviceInList(deviceAdapterType, handles, handlesCount, true, deviceAdapterHandle, deviceAdapterAxis); + + // Lastly if we have not found the device, search through all available Nano-Drive systems with relaxed criteria. + if (!foundDevice) + { + foundDevice = FindMatchingDeviceInList(deviceAdapterType, handles, handlesCount, false, deviceAdapterHandle, deviceAdapterAxis); + } + delete[] handles; + + return foundDevice; +} + +bool FindMatchingDeviceInList(int deviceAdapterType, int *handles, int handlesCount, bool useStrictMatcingCriteria, int &deviceAdapterHandle, int &deviceAdapterAxis) +{ + deviceAdapterAxis = 0; + for (int ii = 0; ii < handlesCount; ii++) + { + unsigned short pid = 0; + unsigned char axisBitmap = 0; + + if (MCL_GetProductID(&pid, handles[ii]) != MCL_SUCCESS) + continue; + if (MCL_GetAxisInfo(&axisBitmap, handles[ii]) != MCL_SUCCESS) + continue; + + if (deviceAdapterType == XYSTAGE_TYPE) + deviceAdapterAxis = ChooseAvailableXYStageAxes(pid, axisBitmap, handles[ii], useStrictMatcingCriteria); + else if (deviceAdapterType == STAGE_TYPE) + deviceAdapterAxis = ChooseAvailableZStageAxis(pid, axisBitmap, handles[ii], useStrictMatcingCriteria); + else if(deviceAdapterType == MADTWEEZER_TYPE) + deviceAdapterAxis = ChooseAvailableMadTweezerStageAxis(pid, axisBitmap, handles[ii], useStrictMatcingCriteria); + + if (deviceAdapterAxis != 0) + { + deviceAdapterHandle = handles[ii]; + break; + } + } + return deviceAdapterAxis != 0; +} + +void ReleaseUnusedDevices() +{ + int existingHandlesCount = MCL_NumberOfCurrentHandles(); + if (existingHandlesCount != 0) + { + int *existingHandles = new int[existingHandlesCount]; + existingHandlesCount = MCL_GetAllHandles(existingHandles, existingHandlesCount); + + // Iterate through the devices and release those that are not in use. + for (int i = 0; i < existingHandlesCount; i++) + { + if (!HandleExistsOnLockedList(existingHandles[i])) + { + MCL_ReleaseHandle(existingHandles[i]); + } + } + delete[] existingHandles; + } +} + + +int ChooseAvailableXYStageAxes(unsigned short pid, unsigned char axisBitmap, int handle, bool useStrictChoices) +{ + int ordersize = 3; + int order[] = { M1AXIS, M4AXIS}; + int strictOrder[] = { M1AXIS, 0 }; + + switch (pid) + { + case MADTWEEZER: + return 0; + case MICRODRIVE: + case NC_MICRODRIVE: + case MICRODRIVE3: + case MICRODRIVE4: + order[1] = 0; + break; + // Use the standard order. + default: + break; + } + + int *chosenOrder = useStrictChoices ? strictOrder : order; + int axis = 0; + for (int ii = 0; ii < ordersize; ii++) + { + if (chosenOrder[ii] == 0) + break; + + // Check that both axes are valid. + int xBitmap = 0x1 << (chosenOrder[ii] - 1); + int yBitmap = 0x1 << chosenOrder[ii]; + if (((axisBitmap & xBitmap) != xBitmap) || + ((axisBitmap & yBitmap) != yBitmap)) + continue; + + HandleListType device(handle, XYSTAGE_TYPE, chosenOrder[ii], chosenOrder[ii] + 1); + if (HandleExistsOnLockedList(device) == false) + { + axis = chosenOrder[ii]; + break; + } + } + return axis; +} + + +int ChooseAvailableZStageAxis(unsigned short pid, unsigned char axisBitmap, int handle, bool useStrictChoices) +{ + int ordersize = 6; + int order[] = { M1AXIS, M2AXIS, M3AXIS, M4AXIS, M5AXIS, M6AXIS }; + int strictOrder[] = { M3AXIS, M4AXIS, M5AXIS, M6AXIS, 0, 0 }; + + switch (pid) + { + // These devices should be used as XY Stage devices. + case MICRODRIVE: + case NC_MICRODRIVE: + return 0; + case MICRODRIVE3: + { + int neworder[] = { M3AXIS, M2AXIS, M1AXIS, 0, 0, 0 }; + copy(neworder, neworder + ordersize, order); + break; + } + // For 4 and 6 axis systems leave M1/M2 for an XY Stage. + case MICRODRIVE4: + { + int neworder[] = { M3AXIS, M4AXIS, 0, 0, 0, 0 }; + copy(neworder, neworder + ordersize, order); + break; + } + case MICRODRIVE6: + { + int neworder[] = { M3AXIS, M6AXIS, M4AXIS, M5AXIS, 0, 0 }; + copy(neworder, neworder + ordersize, order); + break; + } + case MICRODRIVE1: + { + int neworder[] = { M1AXIS, M2AXIS, M3AXIS, 0, 0, 0 }; + copy(neworder, neworder + ordersize, order); + break; + } + case MADTWEEZER: + { + // The strictOrder and order for MadTweezer are identical. + int neworder[] = { M1AXIS, 0, 0, 0, 0, 0 }; + copy(neworder, neworder + ordersize, order); + copy(neworder, neworder + ordersize, strictOrder); + break; + } + // Use the standard order. + default: + break; + } + + int *chosenOrder = useStrictChoices ? strictOrder : order; + int axis = 0; + for (int ii = 0; ii < ordersize; ii++) + { + if (chosenOrder[ii] == 0) + break; + + // Check that the axis is valid. + int bitmap = 0x1 << (chosenOrder[ii] - 1); + if ((axisBitmap & bitmap) != bitmap) + continue; + + // Check if a matching device is already in our list of controlled devices. + HandleListType device(handle, STAGE_TYPE, chosenOrder[ii], 0); + if (HandleExistsOnLockedList(device) == false) + { + // If there is no conflict we can choose + axis = chosenOrder[ii]; + break; + } + } + return axis; +} + +int ChooseAvailableMadTweezerStageAxis(unsigned short pid, unsigned char axisBitmap, int handle, bool useStrictChoices) +{ + int ordersize = 1; + int order[] = { M2AXIS }; + int strictOrder[] = { M2AXIS }; + + if (pid != MADTWEEZER) + return 0; + + int *chosenOrder = useStrictChoices ? strictOrder : order; + int axis = 0; + for (int ii = 0; ii < ordersize; ii++) + { + if (chosenOrder[ii] == 0) + break; + + // Check that the axis is valid. + int bitmap = 0x1 << (chosenOrder[ii] - 1); + if ((axisBitmap & bitmap) != bitmap) + continue; + + // Check if a matching device is already in our list of controlled devices. + HandleListType device(handle, MADTWEEZER_TYPE, chosenOrder[ii], 0); + if (HandleExistsOnLockedList(device) == false) + { + // If there is no conflict we can choose + axis = chosenOrder[ii]; + break; + } + } + return axis; +} \ No newline at end of file diff --git a/DeviceAdapters/MCL_MicroDrive/AcquireDevice.h b/DeviceAdapters/MCL_MicroDrive/AcquireDevice.h new file mode 100644 index 000000000..cb3ae4166 --- /dev/null +++ b/DeviceAdapters/MCL_MicroDrive/AcquireDevice.h @@ -0,0 +1,3 @@ +#pragma once + +int AcquireDeviceHandle(int deviceType, int &deviceAdapterHandle, int &deviceAdapterAxis); \ No newline at end of file diff --git a/DeviceAdapters/MCL_MicroDrive/HandleListType.h b/DeviceAdapters/MCL_MicroDrive/HandleListType.h index 547d72b76..1ec7851a1 100644 --- a/DeviceAdapters/MCL_MicroDrive/HandleListType.h +++ b/DeviceAdapters/MCL_MicroDrive/HandleListType.h @@ -7,6 +7,7 @@ License: Distributed under the BSD license. #define XYSTAGE_TYPE 1 #define STAGE_TYPE 2 +#define MADTWEEZER_TYPE 3 #define IGNORED_AXIS 0 class HandleListType diff --git a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.cpp b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.cpp index aa0f546a4..b9022da18 100644 --- a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.cpp +++ b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.cpp @@ -1,11 +1,12 @@ /* File: MCL_MicroDrive.cpp -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #include "MicroDriveZStage.h" #include "MicroDriveXYStage.h" +#include "MadTweezer.h" #include "ModuleInterface.h" #include "handle_list_if.h" @@ -40,6 +41,7 @@ MODULE_API void InitializeModuleData() { RegisterDevice(g_XYStageDeviceName, MM::XYStageDevice, "XY positioning"); RegisterDevice(g_StageDeviceName, MM::StageDevice, "Z positioning"); + RegisterDevice(g_DeviceMadTweezerName, MM::GenericDevice, "Rotational Axis"); } MODULE_API MM::Device* CreateDevice(const char* deviceName) @@ -52,6 +54,9 @@ MODULE_API MM::Device* CreateDevice(const char* deviceName) if (strcmp(deviceName, g_XYStageDeviceName) == 0) return new MicroDriveXYStage(); + + if (strcmp(deviceName, g_DeviceMadTweezerName) == 0) + return new MadTweezer(); // ...supplied name not recognized return 0; diff --git a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.h b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.h index 0bccfc32f..ebd5b7b93 100644 --- a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.h +++ b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.h @@ -1,6 +1,6 @@ /* File: MCL_MicroDrive.h -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #pragma once @@ -25,8 +25,21 @@ License: Distributed under the BSD license. #define MICRODRIVE4 0x2504 #define MICRODRIVE6 0x2506 #define NC_MICRODRIVE 0x3500 +#define MADTWEEZER 0x2522 -static const char* g_StageDeviceName = "MicroDrive Z Stage"; +#define STANDARD_MOVE_TYPE 1 +#define CALIBRATE_TYPE 2 +#define HOME_TYPE 3 +#define RETURN_TO_ORIGIN_TYPE 4 +#define FIND_EPI_TYPE 5 + +#define MIN_VEL_HIGH_SPEED 1.5708 +#define MAX_VEL_HIGH_SPEED 125.6637 +#define MIN_VEL_HIGH_PRECISION 0.1964 +#define MAX_VEL_HIGH_PRECISION 6.2831 + + +static const char* g_StageDeviceName = "MicroDrive Z Stage"; static const char* g_XYStageDeviceName = "MicroDrive XY Stage"; static const char* g_Keyword_SetPosXmm = "Set position X axis (mm)"; @@ -35,8 +48,10 @@ static const char* g_Keyword_SetPosZmm = "Set position Z axis (mm)"; static const char* g_Keyword_SetRelativePosXmm = "Move X axis (mm)"; static const char* g_Keyword_SetRelativePosYmm = "Move Y axis (mm)"; static const char* g_Keyword_SetRelativePosZmm = "Move Z axis (mm)"; -static const char* g_Keyword_SetOriginHere = "Set origin here"; -static const char* g_Keyword_Calibrate = "Calibrate"; +static const char* g_Keyword_MaxVelocity = "Maximum velocity (mm/s)"; +static const char* g_Keyword_MinVelocity = "Minimum velocity (mm/s)"; +static const char* g_Keyword_SetOriginHere = "Set origin here"; +static const char* g_Keyword_Calibrate = "Calibrate"; static const char* g_Keyword_ReturnToOrigin = "Return to origin"; static const char* g_Keyword_PositionTypeAbsRel = "Position type (absolute/relative)"; static const char* g_Keyword_Encoded = "EncodersPresent"; @@ -45,11 +60,41 @@ static const char* g_Keyword_ImRetry = "IM number of retries"; static const char* g_Keyword_ImTolerance = "IM tolerance in Um"; static const char* g_Keyword_IsTirfModuleAxis = "TIRF module axis"; static const char* g_Keyword_IsTirfModuleAxis1 = "TIRF module axis1"; -static const char* g_Keyword_IsTirfModuleAxis2 = "TIRF module axis2"; +static const char* g_Keyword_IsTirfModuleAxis2 = "TIRF module axis2"; +static const char* g_Keyword_DistanceToEpi = "Distance to epi"; +static const char* g_Keyword_FindEpi = "Find Epi"; + + +// Mad Tweezer +static const char* g_DeviceMadTweezerName = "Mad-Tweezer"; +static const char* ZMadTweezerName = "Mad-Tweezer Z Stage"; +static const char* g_Keyword_HighSpeedMode = "High Speed"; +static const char* g_Keyword_HighPrecisionMode = "High Precision"; +static const char* g_Keyword_MaxVelocityHighSpeed = "Maximum high speed velocity (rad/s)"; +static const char* g_Keyword_MinVelocityHighSpeed = "Minimum high speed velocity (rad/s)"; +static const char* g_Keyword_MaxVelocityHighPrecision = "Maximum high precision velocity (rad/s)"; +static const char* g_Keyword_MinVelocityHighPrecision = "Minimum high precision velocity (rad/s)"; +static const char* g_Keyword_Home = "Home"; +static const char* g_Keyword_Mode = "Mode"; +static const char* g_Keyword_Direction = "Direction"; +static const char* g_Keyword_Location = "Location (milliradians)"; +static const char* g_Keyword_Velocity = "Velocity (radians/s)"; +static const char* g_Keyword_WaitTime = "Wait Time"; +static const char* g_Keyword_Rotations = "Rotations"; +static const char* g_Keyword_Steps = "Steps"; +static const char* g_Keyword_Milliradians = "Milliradians"; + +// Common +static const char* g_Keyword_Handle = "Handle"; +static const char* g_Keyword_Serial_Num = "Serial Number"; +static const char* g_Keyword_ProductID = "Product ID"; +static const char* g_Keyword_Stop = "Stop"; static const char* g_Listword_No = "No"; static const char* g_Listword_Yes = "Yes"; static const char* g_Listword_AbsPos = "Absolute Position"; static const char* g_Listword_RelPos = "Relative Position"; +static const char* g_Listword_Clockwise = "Clockwise"; +static const char* g_Listword_CounterClockwise = "Counterclockwise"; + -static const char* g_Keyword_FindEpi = "Find Epi"; diff --git a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj index 2b91df5dc..0b5ef89c1 100644 --- a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj +++ b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj @@ -85,16 +85,20 @@ + + + + diff --git a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj.filters b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj.filters index ca0a24ec6..b3190bca9 100644 --- a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj.filters +++ b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj.filters @@ -29,6 +29,12 @@ Source Files + + Source Files + + + Source Files + @@ -52,5 +58,11 @@ Header Files + + Header Files + + + Header Files + \ No newline at end of file diff --git a/DeviceAdapters/MCL_MicroDrive/MadTweezer.cpp b/DeviceAdapters/MCL_MicroDrive/MadTweezer.cpp new file mode 100644 index 000000000..80514ea26 --- /dev/null +++ b/DeviceAdapters/MCL_MicroDrive/MadTweezer.cpp @@ -0,0 +1,656 @@ +/* +File: MadTweezer.cpp +Copyright: Mad City Labs Inc., 2023 +License: Distributed under the BSD license. +*/ + +#include "AcquireDevice.h" +#include "MadTweezer.h" +#include "MCL_MicroDrive.h" +#include "HandleListType.h" +#include "DeviceUtils.h" + +#define _USE_MATH_DEFINES +#include +#include + +#include +#include + +using namespace std; + +MadTweezer::MadTweezer() : + handle_(0), + serialNumber_(0), + pid_(0), + axis_(0), + encoderResolution_(0.0), + stepSize_rad_(0.0), + maxVelocity_(0.0), + minVelocity_(0.0), + location_mrad_(0.0), + max_mrad_(0.0), + velocity_rad_(0.0), + units_(0), + mode_(0), + busy_(false), + initialized_(false), + encoded_(false), + home_(false), + direction_(1) // clockwise +{ + InitializeDefaultErrorMessages(); + + // MCL error messages + SetErrorText(MCL_GENERAL_ERROR, "MCL Error: General Error"); + SetErrorText(MCL_DEV_ERROR, "MCL Error: Error transferring data to device"); + SetErrorText(MCL_DEV_NOT_ATTACHED, "MCL Error: Device not attached"); + SetErrorText(MCL_USAGE_ERROR, "MCL Error: Using a function from library device does not support"); + SetErrorText(MCL_DEV_NOT_READY, "MCL Error: Device not ready"); + SetErrorText(MCL_ARGUMENT_ERROR, "MCL Error: Argument out of range"); + SetErrorText(MCL_INVALID_AXIS, "MCL Error: Invalid axis"); + SetErrorText(MCL_INVALID_HANDLE, "MCL Error: Handle not valid"); + SetErrorText(MCL_INVALID_DRIVER, "MCL Error: Invalid Driver"); + SetErrorText(INVALID_VELOCITY, "Velocity must be between max and min velocity for current mode"); + SetErrorText(INVALID_LOCATION, "Location must be between 0 mrad and 6,238.0249 mrad"); +} + + +MadTweezer::~MadTweezer() +{ + Shutdown(); +} + + +bool MadTweezer::Busy() +{ + return busy_; +} + + +void MadTweezer::GetName(char* pszName) const +{ + CDeviceUtils::CopyLimitedString(pszName, g_DeviceMadTweezerName); +} + +int MadTweezer::Initialize() +{ + int err = DEVICE_OK; + + HandleListLock(); + err = InitDeviceAdapter(); + HandleListUnlock(); + + return err; +} + + +int MadTweezer::InitDeviceAdapter() +{ + if (initialized_) + return DEVICE_OK; + + // Attempt to acquire a device/axis for this adapter. + int ret = MCL_SUCCESS; + ret = AcquireDeviceHandle(MADTWEEZER_TYPE, handle_, axis_); + if (ret != MCL_SUCCESS) + return ret; + + // Query device information + serialNumber_ = MCL_GetSerialNumber(handle_); + + ret = MCL_GetProductID(&pid_, handle_); + if (ret != MCL_SUCCESS) + return ret; + + double ignore1, ignore2; + ret = MCL_MDAxisInformation(axis_, &encoderResolution_, &stepSize_rad_, &maxVelocity_, &ignore1, + &ignore2, &minVelocity_, &units_, handle_); + if (ret != MCL_SUCCESS) + return ret; + + if (maxVelocity_ <= 100) + velocity_rad_ = MAX_VEL_HIGH_PRECISION; + else + velocity_rad_ = maxVelocity_; + max_mrad_ = 2 * M_PI * 1000; + + // Create properties + int err = DEVICE_OK; + + err = CreateMadTweezerProperties(); + if (err != DEVICE_OK) + return err; + + err = UpdateStatus(); + if (err != DEVICE_OK) + return err; + + initialized_ = true; + + return DEVICE_OK; +} + + +int MadTweezer::CreateMadTweezerProperties() +{ + int err; + char iToChar[25]; + + vector yesNoList; + yesNoList.push_back(g_Listword_No); + yesNoList.push_back(g_Listword_Yes); + + /// Read only properties + + // Name property + err = CreateProperty(MM::g_Keyword_Name, g_DeviceMadTweezerName, MM::String, true); + if (err != DEVICE_OK) + return err; + + // Description Property + err = CreateProperty(MM::g_Keyword_Description, "Rotational Axis Driver", MM::String, true); + if (err != DEVICE_OK) + return err; + + // Device handle + sprintf(iToChar, "%d", handle_); + err = CreateProperty(g_Keyword_Handle, iToChar, MM::String, true); + if (err != DEVICE_OK) + return err; + + // Product ID + sprintf(iToChar, "%hu", pid_); + err = CreateProperty(g_Keyword_ProductID, iToChar, MM::String, true); + if (err != DEVICE_OK) + return err; + + // Serial Number + sprintf(iToChar, "%d", serialNumber_); + err = CreateProperty(g_Keyword_Serial_Num, iToChar, MM::String, true); + if (err != DEVICE_OK) + return err; + + // Maximum velocity + err = CreateProperty(g_Keyword_MaxVelocityHighSpeed, "125.6637", MM::Float, true); + if (err != DEVICE_OK) + return err; + + // Minumum velocity + err = CreateProperty(g_Keyword_MinVelocityHighSpeed, "1.5708", MM::Float, true); + if (err != DEVICE_OK) + return err; + + // Maximum velocity + err = CreateProperty(g_Keyword_MaxVelocityHighPrecision, "6.2831", MM::Float, true); + if (err != DEVICE_OK) + return err; + + // Minumum velocity + err = CreateProperty(g_Keyword_MinVelocityHighPrecision, "0.1964", MM::Float, true); + if (err != DEVICE_OK) + return err; + + // Home + CPropertyAction* pAct = new CPropertyAction(this, &MadTweezer::OnHome); + err = CreateProperty(g_Keyword_Home, g_Listword_No, MM::String, false, pAct); + if (err != DEVICE_OK) + return err; + err = SetAllowedValues(g_Keyword_Home, yesNoList); + + + // Location + sprintf(iToChar, "%f", location_mrad_); + pAct = new CPropertyAction(this, &MadTweezer::OnLocation); + err = CreateProperty(g_Keyword_Location, iToChar, MM::Float, false, pAct); + if (err != DEVICE_OK) + return err; + + // Mode + vector modeList; + string mode; + modeList.push_back(g_Keyword_HighSpeedMode); + modeList.push_back(g_Keyword_HighPrecisionMode); + GetMode(); + if (mode_ == HIGH_SPEED) + mode = g_Keyword_HighSpeedMode; + else if (mode_ == HIGH_PRECISION) + mode = g_Keyword_HighPrecisionMode; + + pAct = new CPropertyAction(this, &MadTweezer::OnMode); + err = CreateProperty(g_Keyword_Mode, mode.c_str(), MM::String, false, pAct); + if (err != DEVICE_OK) + return err; + err = SetAllowedValues(g_Keyword_Mode, modeList); + + // Direction + vector directionList; + directionList.push_back(g_Listword_Clockwise); + directionList.push_back(g_Listword_CounterClockwise); + pAct = new CPropertyAction(this, &MadTweezer::OnDirection); + err = CreateProperty(g_Keyword_Direction, g_Listword_Clockwise, MM::String, false, pAct); + if (err != DEVICE_OK) + return err; + err = SetAllowedValues(g_Keyword_Direction, directionList); + + // Rotation + sprintf(iToChar, "%f", 0.0); + pAct = new CPropertyAction(this, &MadTweezer::OnRotation); + err = CreateProperty(g_Keyword_Rotations, iToChar, MM::Float, false, pAct); + if (err != DEVICE_OK) + return err; + + // Velocity + sprintf(iToChar, "%f", velocity_rad_); + pAct = new CPropertyAction(this, &MadTweezer::OnVelocity); + err = CreateProperty(g_Keyword_Velocity, iToChar, MM::Float, false, pAct); + if (err != DEVICE_OK) + return err; + + // Steps + sprintf(iToChar, "%d", 0); + pAct = new CPropertyAction(this, &MadTweezer::OnSteps); + err = CreateProperty(g_Keyword_Steps, iToChar, MM::Integer, false, pAct); + if (err != DEVICE_OK) + return err; + + // Milliradians + sprintf(iToChar, "%f", 0.0); + pAct = new CPropertyAction(this, &MadTweezer::OnMrad); + err = CreateProperty(g_Keyword_Milliradians, iToChar, MM::Float, false, pAct); + if (err != DEVICE_OK) + return err; + + // Stop + vector stopList; + stopList.push_back(" "); + stopList.push_back(g_Keyword_Stop); + pAct = new CPropertyAction(this, &MadTweezer::OnStop); + err = CreateProperty(g_Keyword_Stop, " ", MM::String, false, pAct); + if (err != DEVICE_OK) + return err; + err = SetAllowedValues(g_Keyword_Stop, stopList); + + return DEVICE_OK; +} + + +int MadTweezer::Shutdown() { + HandleListLock(); + + HandleListType device(handle_, MADTWEEZER_TYPE, axis_, 0); + HandleListRemoveSingleItem(device); + if (!HandleExistsOnLockedList(handle_)) + { + MCL_ReleaseHandle(handle_); + } + handle_ = 0; + initialized_ = false; + + HandleListUnlock(); + + return DEVICE_OK; +} + + +int MadTweezer::GetMode() { + int err, mode; + err = MCL_MDGetMode(axis_, &mode, handle_); + if (err != MCL_SUCCESS) + return err; + else + { + mode_ = mode; + return DEVICE_OK; + } +} + + +int MadTweezer::GetLocation() { + int err, microSteps; + double tempLocation; + double rotation = max_mrad_; + + err = MCL_MDCurrentPositionM(axis_, µSteps, handle_); + if (err != MCL_SUCCESS) + { + return err; + } + if (mode_ == HIGH_SPEED) { + tempLocation = microSteps * 0.007853981633974483; + location_mrad_ = fmod(tempLocation, rotation) * 1000.0; + } + else if (mode_ == HIGH_PRECISION) { + tempLocation = microSteps * 0.0009817477042468104; + location_mrad_ = fmod(tempLocation, rotation) * 1000.0; + } + + return DEVICE_OK; +} + + +int MadTweezer::SetMode(int mode) { + int err; + + err = MCL_MDSetMode(axis_, mode, handle_); + if (err != MCL_SUCCESS) + return err; + + mode_ = mode; + return DEVICE_OK; +} + + +int MadTweezer::SetLocation(double mrad) { + int err; + GetLocation(); + double initLocation = location_mrad_; + double distance = 0.0; + double distanceRad; + if (direction_ == 1 && mrad > initLocation) + { + distance = mrad - initLocation; + } + else if (direction_ == 1 && mrad < initLocation) + { + distance += (max_mrad_ - initLocation); + distance += (mrad); + } + else if (direction_ == -1 && mrad < initLocation) + { + distance = mrad - initLocation; + } + else if (direction_ == -1 && mrad > initLocation) + { + distance -= initLocation; + distance -= max_mrad_ - mrad; + } + + distanceRad = distance / 1000.0; + if ((fabs(distanceRad) <= stepSize_rad_) || (fabs(6.283185 - distanceRad) <= stepSize_rad_)) { + err = GetLocation(); + if (err != MCL_SUCCESS) + return err; + if (location_mrad_ == 0) + home_ = true; + else + home_ = false; + + return DEVICE_OK; + } + + err = MCL_MDMove(axis_, velocity_rad_, distanceRad, handle_); + if (err != MCL_SUCCESS) + { + return err; + } + + err = GetLocation(); + if (err != MCL_SUCCESS) + { + return err; + } + + if (location_mrad_ != 0) + home_ = false; + else + home_ = true; + + return DEVICE_OK; +} + + +int MadTweezer::UpdateVelocity() { + int err; + double tempEncoderRes, tempStepSize, tempMaxVel, ignore1, ignore2, tempMinVel; + int tempUnits; + char iToChar[25]; + err = MCL_MDAxisInformation(axis_, &tempEncoderRes, &tempStepSize, &tempMaxVel, &ignore1, + &ignore2, &tempMinVel, &tempUnits, handle_); + if (err != MCL_SUCCESS) + return err; + if (mode_ == HIGH_SPEED) { + minVelocity_ = MIN_VEL_HIGH_SPEED; + maxVelocity_ = MAX_VEL_HIGH_SPEED; + } + else if (mode_ == HIGH_PRECISION) { + minVelocity_ = MIN_VEL_HIGH_PRECISION; + maxVelocity_ = MAX_VEL_HIGH_PRECISION; + } + + sprintf(iToChar, "%f", maxVelocity_); + SetProperty(g_Keyword_Velocity, iToChar); + + return DEVICE_OK; +} + + +int MadTweezer::UpdateLocation() { + char iToChar[25]; + + GetLocation(); + sprintf(iToChar, "%f", location_mrad_); + SetProperty(g_Keyword_Location, iToChar); + + return DEVICE_OK; +} + + +int MadTweezer::OnMode(MM::PropertyBase* pProp, MM::ActionType eAct) { + int err; + string mode; + + if (eAct == MM::BeforeGet) + { + if (mode_ == HIGH_SPEED) + pProp->Set(g_Keyword_HighSpeedMode); + else if (mode_ == HIGH_PRECISION) + pProp->Set(g_Keyword_HighPrecisionMode); + } + else if (eAct == MM::AfterSet) + { + + pProp->Get(mode); + if (mode.compare(g_Keyword_HighSpeedMode) == 0) + { + err = SetMode(HIGH_SPEED); + if (err != MCL_SUCCESS) + return err; + mode_ = HIGH_SPEED; + UpdateVelocity(); + } + if (mode.compare(g_Keyword_HighPrecisionMode) == 0) + { + err = SetMode(HIGH_PRECISION); + if (err != MCL_SUCCESS) + return err; + mode_ = HIGH_PRECISION; + UpdateVelocity(); + } + } + + return DEVICE_OK; +} + + +int MadTweezer::OnLocation(MM::PropertyBase* pProp, MM::ActionType eAct) { + int err; + double location; + + if (eAct == MM::BeforeGet) + { + GetLocation(); + pProp->Set(location_mrad_); + } + else if (eAct == MM::AfterSet) + { + pProp->Get(location); + + if (location < 0 || location > 6238) + { + return INVALID_LOCATION; + } + err = SetLocation(location); + if (err != DEVICE_OK) + return err; + location_mrad_ = location; + if (location != 0) { + home_ = false; + } + } + + return DEVICE_OK; +} + + +int MadTweezer::OnHome(MM::PropertyBase* pProp, MM::ActionType eAct) { + int err; + string input; + + if (eAct == MM::BeforeGet) + { + if (home_) + pProp->Set(g_Listword_Yes); + else + pProp->Set(g_Listword_No); + } + else if (eAct == MM::AfterSet) + { + pProp->Get(input); + + if (input.compare(g_Listword_Yes) == 0) + { + err = MCL_MDFindHome(axis_, handle_); + if (err != MCL_SUCCESS) + return err; + home_ = true; + location_mrad_ = 0; + } + } + return DEVICE_OK; +} + + +int MadTweezer::OnDirection(MM::PropertyBase* pProp, MM::ActionType eAct) { + string input; + + // 1 for Clockwise, -1 for Counteclockwise + if (eAct == MM::BeforeGet) + { + if (direction_ == 1) + pProp->Set(g_Listword_Clockwise); + else if (direction_ == -1) + pProp->Set(g_Listword_CounterClockwise); + } + else if (eAct == MM::AfterSet) + { + pProp->Get(input); + + if (input.compare(g_Listword_Clockwise) == 0) + direction_ = 1; + else if (input.compare(g_Listword_CounterClockwise) == 0) + direction_ = -1; + } + return DEVICE_OK; +} + + +int MadTweezer::OnVelocity(MM::PropertyBase* pProp, MM::ActionType eAct) { + double vel; + + if (eAct == MM::BeforeGet) + pProp->Set(velocity_rad_); + else if (eAct == MM::AfterSet) + { + pProp->Get(vel); + + if (vel < minVelocity_ || vel > maxVelocity_) { + return INVALID_VELOCITY; + } + + velocity_rad_ = vel; + } + + return DEVICE_OK; +} + + +int MadTweezer::OnRotation(MM::PropertyBase* pProp, MM::ActionType eAct) { + int err; + double rotations; + double radToMove; + + if (eAct == MM::AfterSet) + { + pProp->Get(rotations); + + radToMove = rotations * 2 * M_PI; + err = MCL_MDMove(axis_, velocity_rad_, radToMove * direction_, handle_); + if (err != MCL_SUCCESS) + return err; + //MCL_MicroDriveWait(handle_); + UpdateLocation(); + } + + return DEVICE_OK; +} + + +int MadTweezer::OnSteps(MM::PropertyBase* pProp, MM::ActionType eAct) { + int err; + long steps; + + if (eAct == MM::AfterSet) + { + pProp->Get(steps); + + err = MCL_MDMoveM(axis_, velocity_rad_, steps * direction_, handle_); + if (err != MCL_SUCCESS) + return err; + //MCL_MicroDriveWait(handle_); + UpdateLocation(); + } + return DEVICE_OK; +} + + +int MadTweezer::OnMrad(MM::PropertyBase* pProp, MM::ActionType eAct) { + int err; + double mrad; + double rad; + + if (eAct == MM::AfterSet) + { + pProp->Get(mrad); + + rad = mrad / 1000.0; + err = MCL_MDMoveR(axis_, velocity_rad_, rad * direction_, 0, handle_); + if (err != MCL_SUCCESS) + return err; + //MCL_MicroDriveWait(handle_); + UpdateLocation(); + } + return DEVICE_OK; +} + + +int MadTweezer::OnStop(MM::PropertyBase* pProp, MM::ActionType eAct) { + int err = 0; + unsigned short* status = 0; + string input; + if (eAct == MM::AfterSet) + { + pProp->Get(input); + + if (input.compare(g_Keyword_Stop) == 0) + { + err = MCL_MDStop(status, handle_); + if (err != MCL_SUCCESS) + return err; + } + else if (input.compare(" ") == 0) + return DEVICE_OK; + + } + return DEVICE_OK; +} \ No newline at end of file diff --git a/DeviceAdapters/MCL_MicroDrive/MadTweezer.h b/DeviceAdapters/MCL_MicroDrive/MadTweezer.h new file mode 100644 index 000000000..e9b51170b --- /dev/null +++ b/DeviceAdapters/MCL_MicroDrive/MadTweezer.h @@ -0,0 +1,99 @@ +/* +File: MadTweezer.h +Copyright: Mad City Labs Inc., 2023 +License: Distributed under the BSD license. +*/ + +#ifndef _MADTWEEZER_H_ +#define _MADTWEEZER_H_ + +//MCL Headers +#include "MCL_MicroDrive.h" +#include "MicroDrive.h" + +// MM headers +#include "MMDevice.h" +#include "ModuleInterface.h" +#include "MMDeviceConstants.h" +#include "DeviceBase.h" + +// List headers +#include "handle_list_if.h" + +using namespace std; + + +#define ERR_UNKNOWN_MODE 102 + +#define HIGH_SPEED 1 +#define HIGH_PRECISION 3 + +class MadTweezer : public CGenericBase +{ +public: + MadTweezer(); + ~MadTweezer(); + + // Device Interface + int Initialize(); + int Shutdown(); + + bool Busy(); + void GetName(char* pszName) const; + + // Action Interface + int OnMode(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnLocation(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnHome(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnDirection(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnVelocity(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnRotation(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnSteps(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnStop(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnMrad(MM::PropertyBase* pProp, MM::ActionType eAct); + + // Rotational Stage API + int GetLocation(); + int SetLocation(double mrad); + int GetMode(); + int SetMode(int mode); + int UpdateVelocity(); + int UpdateLocation(); + +private: + //Initialization + int CreateMadTweezerProperties(); + int InitDeviceAdapter(); + + //Device Information + int handle_; + int serialNumber_; + unsigned short pid_; + int axis_; + double encoderResolution_; + double stepSize_rad_; + double maxVelocity_; + double minVelocity_; + double location_mrad_; + double max_mrad_; + double velocity_rad_; + int units_; + int mode_; + int direction_; + + //Device State + bool busy_; + bool initialized_; + bool encoded_; + bool home_; +}; + + + + + + + + + +#endif //_MADTWEEZER_H_ \ No newline at end of file diff --git a/DeviceAdapters/MCL_MicroDrive/MicroDrive.h b/DeviceAdapters/MCL_MicroDrive/MicroDrive.h index 6aee2b454..0f9fc5386 100644 --- a/DeviceAdapters/MCL_MicroDrive/MicroDrive.h +++ b/DeviceAdapters/MCL_MicroDrive/MicroDrive.h @@ -1,6 +1,6 @@ /* File: MicroDrive.h -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #pragma once @@ -17,6 +17,7 @@ License: Distributed under the BSD license. #define MCL_INVALID_DRIVER -9 #define INVALID_VELOCITY -10 +#define INVALID_LOCATION -11 #ifdef __cplusplus extern"C" { @@ -31,50 +32,50 @@ License: Distributed under the BSD license. MICRODRIVE_API void MCL_DLLVersion(short *version, short *revision); - MICRODRIVE_API int MCL_InitHandle(); - MICRODRIVE_API int MCL_GrabHandle(short device); - MICRODRIVE_API int MCL_InitHandleOrGetExisting(); - MICRODRIVE_API int MCL_GrabHandleOrGetExisting(short device); - MICRODRIVE_API int MCL_GetHandleBySerial(short serial); - MICRODRIVE_API int MCL_GrabAllHandles(); - MICRODRIVE_API int MCL_GetAllHandles(int *handles, int size); - MICRODRIVE_API int MCL_NumberOfCurrentHandles(); + MICRODRIVE_API int MCL_InitHandle(); + MICRODRIVE_API int MCL_GrabHandle(short device); + MICRODRIVE_API int MCL_InitHandleOrGetExisting(); + MICRODRIVE_API int MCL_GrabHandleOrGetExisting(short device); + MICRODRIVE_API int MCL_GetHandleBySerial(short serial); + MICRODRIVE_API int MCL_GrabAllHandles(); + MICRODRIVE_API int MCL_GetAllHandles(int *handles, int size); + MICRODRIVE_API int MCL_NumberOfCurrentHandles(); MICRODRIVE_API void MCL_ReleaseHandle(int handle); MICRODRIVE_API void MCL_ReleaseAllHandles(); MICRODRIVE_API bool MCL_CorrectDriverVersion(); MICRODRIVE_API void MCL_PrintDriverVersion(); - MICRODRIVE_API int MCL_MicroDriveMoveStatus(int *isMoving, int handle); - MICRODRIVE_API int MCL_MicroDriveWait(int handle); - MICRODRIVE_API int MCL_MicroDriveGetWaitTime(int *wait, int handle); + MICRODRIVE_API int MCL_MicroDriveMoveStatus(int *isMoving, int handle); + MICRODRIVE_API int MCL_MicroDriveWait(int handle); + MICRODRIVE_API int MCL_MicroDriveGetWaitTime(int *wait, int handle); - MICRODRIVE_API int MCL_MDStatus(unsigned short* status, int handle); - MICRODRIVE_API int MCL_MDStop(unsigned short* status, int handle); - MICRODRIVE_API int MCL_MDMoveThreeAxesM( + MICRODRIVE_API int MCL_MDStatus(unsigned short* status, int handle); + MICRODRIVE_API int MCL_MDStop(unsigned short* status, int handle); + MICRODRIVE_API int MCL_MDMoveThreeAxesM( int axis1, double velocity1, int microSteps1, int axis2, double velocity2, int microSteps2, int axis3, double velocity3, int microSteps3, int handle); - MICRODRIVE_API int MCL_MDMoveThreeAxesR( + MICRODRIVE_API int MCL_MDMoveThreeAxesR( int axis1, double velocity1, double distance1, int rounding1, int axis2, double velocity2, double distance2, int rounding2, int axis3, double velocity3, double distance3, int rounding3, int handle); - MICRODRIVE_API int MCL_MDMoveThreeAxes( + MICRODRIVE_API int MCL_MDMoveThreeAxes( int axis1, double velocity1, double distance1, int axis2, double velocity2, double distance2, int axis3, double velocity3, double distance3, int handle); - MICRODRIVE_API int MCL_MDMoveM(unsigned int axis, double velocity, int microSteps, int handle); - MICRODRIVE_API int MCL_MDMoveR(unsigned int axis, double velocity, double distance, int rounding, int handle); - MICRODRIVE_API int MCL_MDMove(unsigned int axis, double velocity, double distance, int handle); - MICRODRIVE_API int MCL_MDSingleStep(unsigned int axis, int direction, int handle); - MICRODRIVE_API int MCL_MDResetEncoders(unsigned short* status, int handle); - MICRODRIVE_API int MCL_MDResetEncoder(unsigned int axis, unsigned short* status, int handle); - MICRODRIVE_API int MCL_MDReadEncoders(double* e1, double* e2, double *e3, double *e4, int handle); - MICRODRIVE_API int MCL_MDCurrentPositionM(unsigned int axis, int *microSteps, int handle); - MICRODRIVE_API int MCL_MDInformation( + MICRODRIVE_API int MCL_MDMoveM(unsigned int axis, double velocity, int microSteps, int handle); + MICRODRIVE_API int MCL_MDMoveR(unsigned int axis, double velocity, double distance, int rounding, int handle); + MICRODRIVE_API int MCL_MDMove(unsigned int axis, double velocity, double distance, int handle); + MICRODRIVE_API int MCL_MDSingleStep(unsigned int axis, int direction, int handle); + MICRODRIVE_API int MCL_MDResetEncoders(unsigned short* status, int handle); + MICRODRIVE_API int MCL_MDResetEncoder(unsigned int axis, unsigned short* status, int handle); + MICRODRIVE_API int MCL_MDReadEncoders(double* e1, double* e2, double *e3, double *e4, int handle); + MICRODRIVE_API int MCL_MDCurrentPositionM(unsigned int axis, int *microSteps, int handle); + MICRODRIVE_API int MCL_MDInformation( double* encoderResolution, double* stepSize, double* maxVelocity, @@ -82,19 +83,35 @@ License: Distributed under the BSD license. double* maxVelocityThreeAxis, double* minVelocity, int handle); - MICRODRIVE_API int MCL_MDEncodersPresent( + MICRODRIVE_API int MCL_MDAxisInformation( + int axis, + double* encoderResolution, + double* stepSize, + double* maxVelocity, + double* maxVelocityTwoAxis, + double* maxVelocityThreeAxis, + double* minVelocity, + int* units, + int handle); + MICRODRIVE_API int MCL_MDEncodersPresent( unsigned char* encoderBitmap, int handle); - MICRODRIVE_API int MCL_GetFirmwareVersion(short *version, short *profile, int handle); - MICRODRIVE_API int MCL_GetSerialNumber(int handle); - MICRODRIVE_API void MCL_PrintDeviceInfo(int handle); - MICRODRIVE_API bool MCL_DeviceAttached(int milliseconds, int handle); - MICRODRIVE_API int MCL_GetProductID(unsigned short *PID, int handle); - MICRODRIVE_API int MCL_GetAxisInfo(unsigned char *axis_bitmap, int handle); - MICRODRIVE_API int MCL_GetFullStepSize(double *stepSize, int handle); - MICRODRIVE_API int MCL_GetTirfModuleCalibration(double *calMM, int handle); - MICRODRIVE_API int MCL_GetTirfModuleAxis(int *tirfAxis, int handle); + MICRODRIVE_API int MCL_MDFindHome(int axis, int handle); + MICRODRIVE_API int MCL_MDSetMode(int axis, int mode, int handle); + MICRODRIVE_API int MCL_MDGetMode(int axis, int* mode, int handle); + + MICRODRIVE_API int MCL_GetFirmwareVersion(short *version, short *profile, int handle); + MICRODRIVE_API int MCL_GetSerialNumber(int handle); + MICRODRIVE_API void MCL_PrintDeviceInfo(int handle); + MICRODRIVE_API bool MCL_DeviceAttached(int milliseconds, int handle); + MICRODRIVE_API int MCL_GetProductID(unsigned short *PID, int handle); + MICRODRIVE_API int MCL_GetAxisInfo(unsigned char *axis_bitmap, int handle); + MICRODRIVE_API int MCL_GetFullStepSize(double *stepSize, int handle); + MICRODRIVE_API int MCL_GetTirfModuleCalibration(double *calMM, int handle); + MICRODRIVE_API int MCL_GetTirfModuleAxis(int *tirfAxis, int handle); + + MICRODRIVE_API int MCL_MicroDriveGetWaitTime(int* wait, int handle); #ifdef __cplusplus } diff --git a/DeviceAdapters/MCL_MicroDrive/MicroDrive64.lib b/DeviceAdapters/MCL_MicroDrive/MicroDrive64.lib index 0694e5f4baa1752a3a642967deb5fb1ed386bf93..77b6fe101c4dc9a38f481b39e8a00a21146305c6 100644 GIT binary patch delta 246903 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%0DVVS`u8H_qL;(3+9)){Pr!G1E>K7;jkiUN|) z>SJ?W>nd;zudPq7VwH&RvO%B4K0~1@Ju{RU%CW}+H}CXDYU>*~9ymHjU2MWE40VXtbE`AX3JrIlaV|eQG*Z=_iBSlarRlhNJp<1WShVh?AM`bt+kw6X z%1Z4+&|J`uKs$oI2wDL85-8=Mde9P3-hhYXg4zaW08b0Y4>bv59b20YhbR~zhFgDo@I z9R}mzlmwVcj{1-NeNx@!ApWQteQxO3iz+ME{>Dti~yX7pVJhb|6TSU0LW$A^y1j86j`o)Bu=c+By| zzg(*_y>avEh)*KLOhSx!J}4Vt$R=7h2Lj`>pZ962wSbJ`ek zpXOxCXmQ71Chnd!?vTp`i&`HW@Uu7wb)k5G5a4XjtTiM+)*2F^qsV0yO@TJt7*Ugp z4=*7gt>7R!hkap6f_9W1cmWqzQ=}_LCzWz#FxxRiv=RSxWiTCk{A+E*leM=1p2DSr zE7e#kp4IA3_!}@r15LCMf$(B{;*ri1fr$?`A9v^=9A`JLrYJsgjNg24XX7?%ig#TJ zZN3XMl8dJf5tNq;xA<@R7UG|E?TZej{ZE2R(FW8XMbEg7=3m~OE!8{X-*K^4Y|Lpc zGD)j<;P?k|eClijjpXm=vHIbsolw!jDje%EKaWyz{2HKL@->9xXUwPKVXqeF6oJPz zRb~GT6|PnsbTw8SWG3Lp{M>Kgcu^eWbn=j96bI{{ab>C@6GQn%aj<902~UXP;2OM* ztvJXRb0fgis|J23ii4a&Zu4h~gB;tm6$dGRc;WlRGcWdv;^1_&2U>A3btG_J_bk8g z3Ki%Q&PwVC|ZTPu<3E~Z3rS&wl!uA1Si%a|CWdKw#F@&!;(@nFKqPGj8@@b zvBWpzXuom_0>M@WS!%mUHOM*}2^eIrjd%>gt_rV&o*^fOc%>URwH_NvQ!h^rbw90F zytd)rJ(_yqQ}DxV#t#$qLRPiB4AP^QRxeaPR0Z8;Gu|gBuG)~$BYf4NQmccV1CRA| z^-Q399jeCzp3eUJS8Uyz&@ODRl?JtTq4QMH6+DW1VVRn95w;6%s$RIvsoE?)s(RsT zPBm&VUZkmdp;HtFjq+Sm9mBp!%KuneXMa;A!`Z5APIC59bPOMqO^&sWp;77kgqs}a zX|2JgnuZ~4p-HX5OE!P{WI_Y5)TOlsGg$9YZfUA%SfyspP3{u7ZSRkb6b+wuQE9Nl zQ4|gLxI}UA>F={1OGMKxIiWbn-x}O<`qG59-^6559DLfh;9m)Jd9wQbLFm5SG!4os z|5cgUo@}-ByG)P2sjlB%HG(#Oz?Cx<`=>%HP}KbL-Rg!;tvYBez`)DP=At%aQQ~#7 zTdM;aMZKjD{8RWymtP^fa<{s{mnrp`6sw1qa zr)I44=Y@J0gN~M3X$HCF2y5z~8EaQXsGl(mt_YoA42vp4waybnO^eD~nQD4vXkcca z6MN(&uESNbPU_pYcVBHym8{iTC2QL6(2+s`$0l<{IL+AkNIR{QRed@%y4#CR)|jb{ zjz0c+s#b8)M<2U)OvMnzpq#`*c*!y2p*3Y0jdgBagIi;(AFARCT zY=v9Q#LV7I_0mFkqBxrk^SGI^qw{{4=fON}mfpATTo|86M>aHD4hP-^^8m;U$Iel) ztDs9Xb&joVg}ACAX}adghJNh6-Y^ft_!V*{aW(8`RbY@%J;5`H`4)rbV;E{a_w#G9 z4aRd|?8{??v)u(SRxXf$&xXgl7eI1Ys8zMx-C;N560xKBc{>IM4qU43l4&;qqm7oi2bO5JdIa)%(d%FnIBXz5~vj4Mk8Px558Km+>0)!69oTCI?#->6jexDo-3 zM*s`2gOs*35q7CsM5~iwRsDC%s=Ho??M;R`{|0EQ-^intvs<9fA9^F!x7?zZLz7Lv z@y=TlNszG&n^7nos<#NQ@*{>Yj=xQBlsFwB^}P*)6}L;IU18l`JrP5svtcz9gB+{x z)K>0%SRH?tgi7(?jJvR0_-Xp0kz5mef>OKZ`SNucl805eH zf!X4FB}j@=f4^7kI1Tafy%=i0U#s!yW2K!-?-$G0;qcb`vGU~s(eYb>FTVQ$ObV^g zM^^=a$bq&hTvl8n#n7>DVXy9`??F#XSOP|Ad`3w4>Z2hH$FN#-YuXA3+S&=VE zZrBU2?bo8otOJ89OF6ovw{`pKF}Sc!7o=36Z5yD~*q{Ud62=}dhSux2brO$WFM*dJ z@LB8eXI_?MYzw!?ZxFi}>`vQ&<(?bWwk_^#_6X11BvvoL>XJ?9hP{eKMNoL;(7?MG z_1^TFjl{RUK88lF_(YXOQDr>zsYJ>G z@%pDwfB8&4lj9Q0KNGuZ1hMaPXz+Zc&t4C{1M|SIk+XInYFusf9;CiPe1Ds7ks{b- z@y$8j_HtPH54+%bmp-iA%_*KuyY*M8oMUj3x$7r<+E4n(a(5;8@As&gS7E6TPwj45 zj{jK6e~AlwpZD>!b=San>t3xS=`8^K6wDv(RaaL*_4G&ZzwA|yfp6*f8Gju&uOzj! z3TnM|{XEI3fQH(z4zXP_Oc(vE0}L|2#V|i=yKvBB+m@?gT-yY4zVfr^Y;FPk@y|#k zB;-LGOWKDylzpOpdh9-N*B98}ed2DSjZFi#0Q{{`B?bHFK2aro4gCB2M3wYs@X5aj zp7#r~-7m6R=3#5@9+3aE1H9u`@oN7kebRjPukyCtV0Y555>5`Bjf1)JSM~SB zq0^jq|H|{!-K_zy6xI#4;a7EB3Jx@2S$w4t|3)MJ?MD0ujrfln@t@l~vbyz+@TzEl z$K~9i;}SmkAYLI;UH(UAD|hZe{Nsb_hCdMbMDW!I)niMLE&mSwzJqGpADJkN2<#D< z>kg_5X!j2I{WhQa8wOl|Q&$r&24DW0_@4#7>NooD=w1r`cFm`&mrnM0^`5Wy|E3j% z9~SbIkmrRo2zgD&+d@7P@`aF{LiPwbC?x539i~r6Yawlgdg!P7-;J}!N(8uhFrpyp$@*rbn6u~zqZF8sI$ z=M*HlR+rtGk?CFaM*0oG)w%tHZ5^xsxiYlgk=!0T#y-H~7}AqRO`X`Qr<(m!T1R!^ zMt_-V_dw<)j%}+)Zt{Pfw0ijJ&>1||;ofIM7dJZA;m`%??j+S^Ox6Xe?#0kUIO1dX zTmH3<7ON3rszaSv7n&OkFp5n4iK#k<3oBIpX~CZA_iK%0kPVGXu<3In5U{yz90 zWAhJ#Kg;Id1AmV2Sr&J}{F`mDAI<9?1hB-@NUi)r^+!qOT}oB7#un1E(_O0H8==-J zWlnl`^>}6LuzR-F9#@~W^9@!Po)g@wwp}0Yr`>7ubL%x7G zP3#hk?TIpnsKDpp5|uMO3%!a1fv=YJcBXTq;A7OAv$5fKYU}q3(C+HtRyR)Jn2cA2 zjP@r=>3@`FKJF}Xs>g?CcF;-*GL+5y=(U!^D>*sUP_UAk(tHnhx+^T%Se&2p2Xw)q z)3Ti|Z?KOAEU8J_P^AaD2KRcUxTe;NAU0IB!qZV&Q@b5WShBRXOp~er(8$u!?#b94 z2J%0y!M*BAYpl|mA`k8W?o8Y{xC?L>me#Z=Sz6SmWa%(ZN!7QG$}I?ryF?T(9eAdG z3H>Oksp8v0#ar}Mm@gUdljW=PE#jKdDD2vDvthU$ugMKHv5Qo9Dx7^bP+mwDQ$@4pCn95`>{ zJM>;J@w!tTPpf(y#CKsUOXgMlpvS^5^0=OaO#?jzX2h@0Or^UrnDJq;D?w3p#TJ2v zK+zA-+MExAX2Z-3aTD&-G3?v!!_2H<;O#3`mN-BOisVx#&Ye{}ck2A|F8%tI^}u6N z#jLscg<9S0NknCvMQZJL;m*;k(kyP`BJ=QXAY{oxv(O z^vx3VNEBmm_k!CW*=Wx(nP3Y@x-y(1}HhhKdaFMzdKBEk=3^!^Si?70~BYtN) z&^*v%K!NKys5ik&s*{rw$pxDXvtV;=&Zp1h6l{~h*bx-0=qz2hqo#D@=-Q&%4@+us zvMnVoXn`D_d~ZrR?rU&Vw0dqA3wEsymB>jtT5asY2EMwaZ8va|wiH+;ZHIzehg(Wv z3nufIxJRhb*)3bQpgc7acdjIEtB-I}&e~m})c!1X!|^Y(V2L~lP3VOBKHGw%ENlau ziVb{IJuTx!QKx#)Qhz_$nH?=0Wv4s5Gnd=D?=nxLC9)t1+i6SAtM`Z!Y=Ld-)oN~n zU1PAN0}oa0PhOA)S~78S$+UHSiXGJ+)^`o@T+|(geyX;&yRBoieAe*)hB`ais=_FUmw;dDnEb@zWm1#R0R!^F@0!P4CFg*ro_%vnAasB76#UH==ZqwL*iYkVK$V3`BK zaB8$ItjloouQ8c<-HlmG(tAKzRPF`!fui+`wE?{!lwXZ5pZ(S3IlKtR6Tw$ncK~VwwwO;e6cVTk{;K0qVJ}&eSL)D8tQaim$}03zCGTBc0sI8elR%NRV*PFN08mt1x=jKfyAI~Tptph!0cE2b0bLGSOogBa z!O+1&pmgv!Xdh61G0n`2Y#XF@Bj>fs4K~&0yw(*4TV$~R8f=@v?22Dk^&U2tn_!h_ z_wR-2RMqCg%(f7E(ZpwY6Q>9&$DvRMUkYB}TrP~TK34a($DTbme2qbjE0#g0GZkPN zZsZ=Wx6s!xd;^)&>~DDsynG8eIgDPgDKHDR#^!wb7%0I?YwOoA(xs;zdTU#xI(8Ex zsc2O)u^yY52CD1IVC>p}Z3#`?g5L8H>&fs(_0>f^mK<)c`fLw%3nuAIGYVlRX}eCQ z^6};tG+XPW!5ym%4rtE#I@FWZPNwqi4FykO1ZB9HeNM*B3-2Fe(C>h9=<9t@W~;5B ztg}A?%>(_frP_~ipAuarc8KB*oAYunHo6B!!Hk?2-G(@pZgXC%*;GnHDwRyz+~=o( zNGT_Wlba+I)hmW3${oK>nsHL(ee5WPjL=R0{UcKA797w@Y}1#`FLBy!lj3Qxgi+Z^^2 zHdxGHD1uSjAtgoeyw*5uFjyuMMgnYSbN^EU7apF#6x@a+5*Xnl_b>%`byG9G3*~#q zl+T+ry^{Mm{QsEFk|~=_?C}INwxWwnp?f%#G zNK)9YbXR;GN^3TMe~Ns|%H%UpRwiG9vNHJ^G#7L`DDyYkL7loEf|t}K=hbI13U)Ef zg3Y%%Q85<`?Rr#O`>flI!)J`cXAQ>MS^{KkEuR1P{N3~q#Pj!hgdVZfkhgrTT9|CD zX6+BPKO)8dZ+W^G^Yn=-`5P=cYA#RbAWt%Nb3vJ>^Ff)X^FW!WX=a|LJ@a%|(9WRH z3e|aP~`cJ+P?qW{>2p4st!2) zx71MtzZa&Z5p;Dnl}xMGW#thI>11ImcLU#eiUzhF~@ZrkW*%LrWt{RJOl1sn3*R)0Y~$Op)P4biLN5=9r}qXl_5Ahcw5dVCceS z4KW;7@XBjZMW1VdU3&Lhd41M%ogkT56H(b5-Zi8e1g{rBM@Ta>e8R#r%cswpIJM#u zEk#8SZ^)l>hV6#jVa=?Z5m{J1Z}_bFQ)gbJ+jR5|?aQsrT%$n&6}wkBn-u^&+VTWF zJXoRm2@9vrv)DPMd@ulc2UmONNf>MW09(8AFsF#du+_hI_A*Ql>kmo8mnWezOtRi< zG=9TzclV;K)a1_YmPr}DJRazjGmXb@O5>&l5%|Ae1mXUxRA-7iH7VdOOv?8a_zK)%1j3V{+~=h_Jvb_=G|7$gt0Fiy3bwFs z1#XJ_#H3_~k&Xa-9(TtiKTJ-Ky9@nz+?m)7qQF<;j{TnGbi0$f!b1+A5??QO?AxTI zs5_14Po<Z1~XG z_ds~`Sc@IW*5kN`IGs8{d9uRIIzf-I+HyR1di)^=fy zO8iM1f5ygF+4!?I&hkn3Z12CoEm9Ua8ORW9s%(Ig{dkTiKk?N;B9(gIx~pyf5%Iebc zv1dO>*PA%KIn*KvdHraz`Ts)~|4+J}sPkg@EDnEb(Hy@rM$!Cr(DtDCp;|P56O=DY zxlS?-IWLbDvoUr;1zTV+_J9OiM$ue6-(kS#j6=4Z;*bKlxXZLTo!SYh@qg=j>eP>m z=B(HU{#n*lh-*XfD_`gO%345?RAn@UI@$&cC`I(s!Lm z-0#568ZB`5-{fEXFicgy#^vq1VH)gTbvwrfPCE2zWQ_wrEl$&@^PlzBTiYN>MXDPr zf+Z8zl{D@>Q3|t#& z92bw3=x4pFSr*p4xD$i1PvO%}HN9(C;i85nFBj-2lZU+O;awPoZPz9oa5`~j=AKad zXtJXi{iMX091p`3;e_KM|Iwjzf`F5Rf7%8!bny|6$(7Tmv+X zBE3?3JO>SHyt!=NLukx|1=>>sJY1qZrC|*J&9iV=<6*&0L9mqXIaQo^-=@<%pKJS+ zW?I|sc54d<%{I0@wa@nYUD(*Zug6C4*=%h)G-|x56pTYB0IE*P)zP ztOLCoON&e@%#$-s*TLp2ZQEVPkl82(s$e9=ZtLxvr~~KWY!U5r-s~CTo5Ii&8tSZl z$H&^sTB{o)?)GYVFnmnZ2mmX!OL;J3l!4{jJs^;E35s$H@Rxyh0KE#dALwGx8K75# z^4DAgS_QfU6eAi|4Jga(wV-^PrJ(PDVmW5)eGY8j42C`ITS56}mVxfJ9Z)j466Rk) zW1t-V#KPYgbL<@swfoEDaIZkk<4&o5UnRGX?lJ(23Y~j7 z+F%F9Dp;^H4F+My#^xLBQiH8D7^l<4Z=J!`8;pH_*isho2#bh2+MLhoY_JOr#seEA zKuQ4QaLACs{)drz);>*|bEhNw@R8(31IdVBps5g!Z)S+?|I&<`5jIY1G&{n!27i~$ z3tCj&jAPV5H{)wT*^JkNa)fOiD4UV@aZ58oPR1_`J~Y$NAMrSy$2P8%_X9e-FIohQ*QvON>wn-I zK6Pf3zG&IuebE6(jd+zi_3%OE{6-bajH$9Q3ldUCzzLDYeGc{!Q?oj=-_TbN*_~Mg zmu0wF^vB}nH54U~zpDf^8T52eN&}~XX4qy*1GMi1Iu?}Dz(`Pj)>wyf40ng4<={WZ z?X#xZoQUj#u`(g&)e1L)K@PJqR#$>OZLn1QE5X>Xir;3-23m5=V)fbFXP|#owKD|A z4LuXHeXL}f&aub=rHBXn8DhU(DH${NgpMp3UGyAQc2}!C-n+R9pM5Hyx-u9(T|Idj z4vgyxa~W>-sY-G4Lia+aFJ4R+2joUw9NK`gI3Q2z^lT5xVnH)sjAj;(PjE}|AScB` zFcuHNC|ZFlhC`MQ0Z~3|&-WPYC4;Rq7>fse_!zbIiTc;x&}hfGk}ScWL;sFm@3dmD zx1dcHO96#orRODY1?3z5h+E!}oV=l6d_%#mHSRd#CfH_!y<;$2TxzYwC9NCc&sS<4T^!ht6O)cmIBjLMZ1#syz_Im0*kG#Nt6HZ= zI{+-h&2KBl&FgWbJ}H_3-9vr~)CV&f1Fa463@A&*6QKNyG&3!>?;~(?tF1t8<#5hEGwgY4nDW01x0s(s>7ow#eb8Ubq=|V7Uc@LY#1tJ-rv;cPr z)32MlXMYI0Z2uH$bwvBnS!QyNK`Cg_Y@+H?z~HbSDy9eY3`{#UZ8Nken32nHdvKTH z=7n)aT_8N56u5n$?B=xuWm2MNt}7Z8dtESS=8MwIB>ot;Br!Re-W6=NZ7b8ez}PCs z40xx_`SgIPcwTF;2IGzeLIPxg5O@Ani`bu7;iyQdT~q|PVTF_ILe09!BC7!Pkln7? zKt*TpIq=r7B8MDeZWxBDN6!qkb&NdhM*u)-4v5ylN&FfKrVKYn$yrtM zx(Mk)It7$j6r+UI(?^swzKi}pje0%%LSbQ%8G~Xna(pona;C7dom4a z(F?$A(33#tfDQm9ehTPZ(1D=yKuc_V7-$8|BWyDR{1UhPICApi1mlkrj2|T!#0br` z@LEWGWEM;1R%KyT;&m-jRToeA237cSiVN~1SCPvFs^5GIj1mz!Q6&*<3e18%Vsk!h z3>3#oYS)+4ob4&8nH?ypS(rI4GBz?Ua%SYLlBHAbEm`y(9r^<|Qd8Pzug7P+hB>As2E#Be{kG`{^0P{{@~z^ z$bkjXJ^sMpt^RZCpzRWjEr(0wz|`8o@50{-n0BTNuDOAXfA#rq4_mr~qGPdt^%Ywp z&obrU`-;~F*M11(6>OsooAQl&iV5dZ28h_(J`vA$u)`h4S9 z6Ol&!k^qgq)sHw{j>o!TXN4Fg$_+cqk&_tVx}!z-o*m&6%}01xd{HEBGD6###7WV$ zgP%frGKTu?sWp*&CVpmQOMOXIeV<6xL6`r!UB4eXG^}R(oFI zu-bWjhSgqHgm;^_3y;gT4fYod+Kk8Pb$Fbya#-ybkqaXiM9zW zoe3*hbA7;lfZN64Dl0={4W{a1>%d7Xa5~NohuA=Ty(2S(P&@v>T3$6lymMli022 zS{jcvb>`$*7?B@j)Z#DJPBJvcV$llb4xp&foOFju+F~OKX%g48r5dDoj^KFSF|O$v zL#=M*z(k1w%B&irn~u1DBV=8rG1TsuL&5Z&#-7v|>rqzJlm>A+tCM>AIja7$*6HKW zcxZiJZZ5-5pXR`U+B%9fh+6UPik{^XYpe#*)&ae*dSmDDk&6pjtCbJ8#E#t9hB~?p z5jIjWrnYwuAg0zxhfXcgq}NKKXz9$wzg7klzh%6Qw@DHKwJj+BPY&plpq)TBg64y= z73c!`Hs~=)nAZ3Z%<%x2Acdg(x}Knz%CJrV<>#CTx&(9_=>4E)gT4=X4k%|P&jsCK zoA=n}gP^6rnH5KZmV%A~Jri^^=sBQcFd-S^tO6~j*cQ`h0rPCoL7<#g7y?=kIu!IZ z&=Sx$K!<^{2_pWXjel(8pMv%S{u}53(7s5qQ$UMAPX)zGVgENUWndV{nKC%8SEK@ zv4s`C?U3{m9;HFSMA(3!F(tF4COSLkEF*0!`mPS2dlYE`BE|UpvXa_lII&84L0~D) zKO2(8lL#0};FD_>QI>$j^aRKfEwuVq*HCoWSMR6@`&U(QKCY&ULIn(*q1u_^kFM3+ z;9mP`s#r1at9MrP-&gOc$gQnn{;H{>41t!v>Mc-;8&D9Zw6QZ(A}+A7i00y&r?j-O zN-LE=ORWls1<$_+D-4tesy+czT7$iF(aP+CQaLrrVUFe(Jp210vBFBGiu^u&6!B4l zc4*(^#q|~K;GJ`b{vcLYA<(J~43wtAo#Xgwv;?hviH^>#^Pl^Qw#}j4I>7*x<`>YB z@mxgf=&Mcr^wACr`N~?fDOoxlal)hpoRX!_vyTvYk;)>G+I^81Aa}0QdBK~MW17aMSqlGe^ERk+6 zW2z&2K``RKfBifkP9v$my>?TtSG&DhGqRw#W-FsjUyB8Gpb!fn5=<+CXi#!gu>i8cdCMEg1{XG*3a^=XQJ2N?DZ&8y z@3t^79X1Ey!es?ezp)a5SWp0fbxcPC0t8Go3FdZ{2XkNi{svwu3HGAqL-MR+B^UXN* z-v+i3vR3)biL)kg>Lsobtle^ud%w0n)d{y#ZO1dHs-S0fFk8b}8|`=#9Cnp}ok0^Y zof(Y=*2x^7L8Eh|atq&`;yD@$hpImi%7}8N9D`L5PN>`B(2GqWML3++7;Jbt9kIrR zgG+|v?e$xv!AFJmm!cD46wC>C3$;5Y&RF1Sjb=%>m^Ib42w{&vTw1vT6*n%eU6FLv zu)mn_VbhCJh%iOFVw?Vwd5#n;mt~lp8pXx6tVvhR#-3rx8PHKlPER~qn@^fCbwoUs z@t-0={x>$$%rJf{8LQEft&?yw!k|eUMtDF+7#D>mBC>W?TA)tNxiHvX-Ee+ds-Dnj zBoPH{iHO-}q=VTf4G2@N0{2LVq%JY0nqZQWA~>!5xWX>C1?#O8l6}zC|Z%&O`xkmmxDsZBlb^FsCdMl1%(bu zY(3~&&<&udjbk5yZUp@XbQ37^+$*5W%dg`Mh8SCVv^_C){Q<;|2i*>eX=&?QPE=yK2>K>3$>`71x*4y4-}Pv7RbDyJbE}4^iEKkAGOU?fT5X!8tofw^SieBeNbq3#PIT# z9~3Wb;q=(p4p3~s7Q;(fKSc2+tZ^~`Pyl1?Bmx*Yy@9KZ@y7_Z$hcck9OZw5v=JMS;ds1ay4( z0aGD$-9=8HI%7{LBQR`U1YNp>ww6{Qbc7PifjWjTASZ_~9c8LHs8+TNC)OfnDy^R$ zLd0db-MBG#s;?~=G$ZpiD5MJOJ$`Q8bCk)71D1g0OK2%;l@ug3&4Iv&PKgw{bc8G4g$%Skn`#ZV8N!t zEZ9n$^Xbt~!LTUz>`3iqo(fS!p{Ef0GnYat&M945nm?r&Y99rIcWb!up`b6@)V@6Tq$TerHO1#^_0J!iN_&d`kQL}@lgA? zWWbp~HkgHQ*9e6!X)9}Ba0*+|V{{R_&yQ}-;hWmTxz#Ni{x;Tq%c1%quf*p~*|Ww5;lqi`nS71$h>*cj|fgDrk*R7EyNK-2kXfnrt} zSUS+(pk?p|Ep<0&IlIAyuvuY!j{ERROa8G;>-??5M9*0 zE}4q(fl`#Dxg99Y>{-#=(Ka)3nhR_*|Lb|b(4fcD$VqJ{7;8Jh{%&(VJ=HAOJqEka zxTByUet#pUdOX#lb%uZK&FnvyENux)Pq|sOH{aq^{&~(e(S5oX=cq_+Y%QR(IndP{ zh~fDoy$6~DbDDxBs>3`qNt;`QkB|NbFXz%@f43qJxgdXI@?UdZ!QH00;|+EqxH~o0 z-(al1?$OvNgO!21S7YS{s|3ep+og}skT|YoVR=x4*Bgib0QYZ=Jz%h>!9A)m3LoOR z4jcthm-Vv2-Ui1WnoFx&io36Yt$B&on*l=<o|EHnLMPLS#BZ}-B^TGHqC?J-9B!=3(mdkm;S3{Y?Q!ydge z#))n5dS$qEs(|uZhh!ja0Oc>)0Lo;-K!DbT*aY3ZKaxcQh*?lHfXI1uVhFYbX2F;k zg572?6bl<;b1UxNG1vzNE5av>-(s8dT6Rhu(CEYnH$!uObO zA1m4EK&xm_spK=g0TS;v9qnpKZ;9oAWuh52O^BYq3oYP9;&OzCt zmEm^dJ{dPJH0m)@4E*V|eZfxK7wxot36w43T8El&&`#rTVU{!|CuuAg(^xR3v0zMN z!I;K^F^vUd8VklW7HqG<_8Say`Ly*Q;)P^Y?j$!hVkt}ZVd(C_(z%D8XTsFJQQJ`a zEp)8Us;j<+Bc2t|Oxd8f7{Cq#{c_+2a zpJxu+nfyFFTea`Tx6AQA-mrA|A!t_rQFS1BK~~AcH%hQlabtDA09K^v%R!!lpVRaE zFwR;E^;iIbfyvh}sQF4s?fCpcQTT?=wm*2Cyp2EL>WlHodLY&PD5I6s;)FSFR1&$M zfwk^exD!<=Mn+Ka4W(2bzgp?7&f37Z7Wa8NQ?(m6d(wo8@* zU!4sbM>gUe59oSKpQQJ=((2t&9Uf;Phiixbgy56ft(;mS&5b=ur!{ zkcCD-wpIf2PYQOw!5%c&QwCdYu(u3GIYj(^Zm_Qn)&f~c;@~I~Ic!bV!+}^op#;d$BZ;`D!A>;TK!Xi2SRJ`z@D4ax$AJE|i`eqkZ%@v5n6~0R zI4V`oego~o&s&8vvc?_lcx_#xRm<(+9Li2vYFWRQfzB=Ay9{>{?tI+5UPEn8if_{W z+BY1_eohYO%>lF%w`}`H4kK0uqwus-txpTT*;QsWm?s)=yLd90p44|y&ei9ZxhRoh z?m)fR8qL~;x;9#s9-iZfE=M1XGS7XWT|hCrOZ6@b$`M+t>uJ#WFuw%42(%8g7PKC8 z8E6COZJ_Hx?*ZKe%G~w}D03TgwB$B&BG3wUF>RUSK z+)lovc7F+)q!}f(BRrfkE9q4SXNAAg3L{A+5$#yDYaPx!Y3q{l`KN6t&L82@Uj3^^ zI{d4$>%Z{$wt{!YnIz4`+ppRNHLGK7>k71_#nn5%MkIM}5{ekgcNW;mj0PeLQFCp7ij= znC@siG=#cPJr)VHQyoL$OrJ!bY~?_w@7{}08}iG_aJRrc7B{c|AYbtt-UMZF-3-bY zwtx-*{V(WMpkIS72i*?30`yx@z8lU^*7FxdA7>w+J(m&a53^a0x98KYiyfS=i%*V=Ee-^i(Z zOR!gNX@6f@bnA=x2u^F2mTcUB%>@pX{PIRK(Zb;V3`l@`=6EuIOt(EO{bt)jF&8I$Vu+XBTt%o1bPy{Hy< zO*EyrW~dAPx75c2Z5$Hoan99`G9htZpxKaydJt0TQA4`H4rz%A>1l=(ZSJLp1`t&G z5pPt$Z`)*tQwQo_y}7pPI|d`6_%*o5#1A z)x#PeuqZSg2!0L_`V`yae6PBivvOFM6qVGT4wX`*)so`;g2_yzqK}G}OwS)eFvtEy zZ`OXGIqb`dnedYMOgN?1DU@kvQ=nZ4xpdTp4HR3XA{mGs4(yr)L!x#~!tarqaziXtY|u{cIv}mUC3Xkg8Pm?pF>G)S= zaL3=x|H*4IDmDI#sh~{Fi$O6UV@(4c2RakAt7JWhKVI(MVCKvI`KyLv38uSj(U7&Y@@;}`LO6lNd z+)@jZlU*|fn`qm5^@d@B%>xz{hm;!xtTGPy=7O;n7I&{1>`j9mGFTE`Mcnyq&Zku= z1?yw5la0GlYL-hRth>Y^Gmv1@4aO=#FxG71j)Sd&{lj1<;CBkfLMZM=8H{txf?*H? z+T~UI^OF~Zu&3b~1ONr1h-_@=zqPLM%;nE&iwhf@zV>}66?-OAB2X`c!(G&xk1_*} z_RGS74j9=uB_rGxvz57%orwuZk2=^U>{HjD7s?pSvR{Tf1$QxSUf1JKlimP|TF$x^ zGz6+ZvqA3!%>lg;v?I-`?7dLNg=Jvh#qF~u*_;?u0VczrG? zYSTK}j}uh@@)`$vs{HAFS>Y3%XJGYRIMCj4rYda{&hww?$ZFKDE>c&v2^XK%xN9!^ zkSrSf7xUGp>*(azb}o(PyaC!0HTt5KIb3Sof%&HZTTlV5qm_N}HOs(c|=F?rM$ zF^tD^KQOJ%yAbpXm?6r=((ytNvST@*SAnvxc@5|ppff?4o_qn;QSacEI*OdsQG!jh zZKaM946#PF7q33=UBH`+!zXRdr?=>o0Dm;t9^;O6l=x*GCGJkKIiEGqU?U7R#$eI) z2BcIYp3&#=^s19i5XZc*>A|oXM``WWlA4qA{ROXLGbwCC52ctGG3&9s}F)N@HhwFSJAuoLY)UOcam|@W-_c8NVr;v$~+l!!}L|HKe zaiP>}aaVs1%q#}h4!Ve2?)V0V{u?VEvG+3jpt0SiMh19j8Vdgf62Sh9yaC*sct?&JXcr(DG?oFg1WkSKZ?)}Jcz&a~Yw`S2a|%zf}}4%i0TuK6&kJ&IC+_=;6Bo}^S~X@+!f&D^=}5}#GM$4?u9C~ebHf}{5#qS z`plr`=vJatTq&h=>Wrxs^JMq4EG_Y}3d}+h;50pFLLoqpT2h&_wYK7;#5u)UW_cVZ zI^qMy&1tGoHj-iD_)re9v0w5S%-3l1aLDvVHAcTcFWo1g6iw|w zD(@s)5$$VzBVT5dv8estP}YeA8{j^No~%jEi!lRoH5!RWLAT&mj$|E5gL#)xN5n0g zioFc~d3GpQ+kK9g8^zsZld=SyR`13{-SX|Q-_q; zWb6-ZmTga{g^JIbd$BV_^{o`G7@p|GAhuvI6YlwA>>+L{0p%E|sS;2o>tXN~E6LM`v2AuV#TN;7~-2-4EKHYN-DL(*eLspgEwg zf_4Ob7c>v_L(op3KY&7SF?Im7J1A52I8YAppup?(L>QTl&4+me=#`)&LD^=Z9}v44 z^gK}Z9nJ^6544bKsE>m=6ToWFb3k7JMHeE*#=jrv>!AHX-vK=tbSvltP}V=#Y9yA1 zl&b)313DiRFRyXDq%{lX-k`HV;n$iAdJ5=Xq*yuVFR0>TGr;@`3dgJ^V+%kJfnEaY zg@;9;eoz)ZR9UeHKx3e*Kx;rbmsbl~2YNN=%eI+oE~;RD(>7BEV=9UaM$T*LZ6ySp zYFo>;5`tY~uv=_SMv=uc%e%N^%PQEn2BTRpb}qyn#YMsLh2skH0t23H9F8{_>pk(z zKB0JKiy;`R8^P`|82_wbYYo<5urCb8)+Z_slaWLcU<;e`TF9+7hJ0!V7%|vjgN-)W zID_GL*nY1!*qsK$M3Nm|Np4cyVX4jGP#HPx1QP<-3&NqExFdaq1N%2!m(``$?||g@ zk(2TMoA4R3Gcq}^9_1Hzcfrl?>bmyf@gbHL7Y9NuTDh?AJo4D;-`j^rJDj^LH7YxN zTofZRcHrbNTLd?Poo#cn22HT54Ax-WZ8X?d2HRnpb9ZoNIMgFKm!*g!^x2K6+_?5%4I7 zx-pcLqi2ZYZ_v_wp>X^{cD0kNtkbislh?Sd6IY~IJ#O<@-Iuhmx-9fsc~g8=_M}`Z zdu)!C-8cGFW=ps%HGOa+SrHO1E()cIFTK%!OEcjK$M7nMjkL1TT?oj-;%O%{as;fUcrv%ev}Z8Z zxqR4HYZIh!4DwQ`OD5*nZdf>pS82B_rW78MOc7=7qns(p%xWZF-unWLS1bFq3x*>c z-)X;k_}=tfoT^063`RIQs&S~?8?y?spM{-6M}Gh)_s*4VGaS0U*sN5?@DpLmCQ?zc zX-m>c_^b{fUCqr4hn#OY)V2XQ!s+h3u*bPh+qCn&Y@*$y-pC6d>yu#kfoqjJKYVQJ zYuW{YH|)?dZ_3i49e-0+JLeVw-J7&CwQWKuJbM4Bz4UePRQTdmq@R|@rmbumuxVyd z=CQ?N59Vgs<_%UGPLT zY)(eLv-)(k>!uW(vc5Otc=gGB=_RR8ThctdsM?mO`e$63$^XSirT%}FT?u>?#nRv2 zO*Wg&CcB&L&h(O-9DtBJ5Wt%YARJURZHM2pTdC6CG0! zbt+6zeUjTaMk~Fq7hhRAtulJTM56J$6*Q%6VlM-XRFqM=Tp zgHe}wcr5itYW%+c^zAKwtKm*A_hOukKfJ`78-kIJ!mQ6Sa6NL0$i1mw2f%An#Be{F z8A#B7zgrC3%XyF=brubqjTAcQa+!x49NNdFdJ4r4i*x7p_F*Oh2TnDd1v(@$oif;5=VTGob46PIcCji zh@6U?naHV!ik5Dih2FwFI82;!o;Q%Qm4h>_*$})JKqi%Y?Sx4x()6ase~d?2Th#LH zb>B_-_`vH5%8I}$X$~{4pe%>}b+&crliV+>xg_fegblEeWI3`vP*?p5e9=na-kqe3 z0nvmussG6I*2>H7PK= zY7#P1%)MBT;I)R0N7w-CNfrSVzS_TI95j%uaw+i+Cc^KngfO3YOw}+QbZ#v~wW4aF z5MNvKo~QIgOwq7DC_oQ$Tca|qBx1Y)YSlBrlRQ}>m-9P}A`c`I5KClyuCZGF{~Hcz5$iA@v+ z+Y^Lvqd$>in0Bl2Q6z_(34%5J3IbXQ#@lNTv)C6lV?|ZH-**B-+qK4EEZl;))WHc;-%7!rt z13d%{LR7<|q*PqE#MDwIg4VG*cXQz(GQKx;5uud%g(=W2B!Yy?ccVUA+ekUPOUitUEq?gT|i zw~#V@gbMY>KsRO-v?>SEK|y?-2rXJtFgg)h#G%mEi7ZhNvlH2(Al;qFMO;mh!Vo9q zxStv1L}FyU@MQ>xLcJBFg%g>oAdyZ4-wBmMkQ1S=jwu8?k*m0xBn8n4;pfIuusV@U z1xa!u;}xW}69EMYbs}FVh|P)MYgtl=av}yVWi_vr6H3G`Od;Hf3{;R5Co)q(Vw}iI z1@U(xyA-6S6S<@y2~NZh`=C_*UQQ^PMkIzuoybrH$#)|1Mk~0&=0r9sG#O5$K|zK& z5g(-o$2pNCLMZ>ooX{wRDBX$FC`g(U*{L9XoyZjh>F-1We5ep)G8{doje>M`A_F8O z4xbh|q3H^dpA!KEQ6W;seuvD(Hfz;JH&E_Ur#+Bhx>3L|+$pNPf{-YgE5`#VR1hzT zrhIbQ#L7{!oB}7;8)*uW4k5WqeeNNs&I4)G!TDLH$f_h-QKCSVrd37f=A}rN_(LAD zgd{nBVl_|fXG{7R1&=@ zZ)#3UC90Z;GfP}40~GcWOwp($U6o8r3Bkn*In&U78yCB{ru)M11*S0WD9Dn_3&;6N zQv{N$mij{Yey`Z7yF=f}cA=YKnv;G9UcHd+;yS5tNi%_Im|$BB1c@>XWx=ClEOV>%qKO8*AEnHl@LZ=ICL?LM)a$Pqve% zQeX3-PpoNOv&iW#aD^WHUq=?l?AI#3VpxWgrA`m^2fSiU@BDXns0+QPhwwMGu58So z6jb44tf&_62Www9#hPC7owVD97WqNN*Ivz~X`+8|m$R;vYCqWiCKlz@{Df>5y2lUt zR-=@07uvhY{*NE5`v}YM)VxwR2?+++QjKMxwx%%9et5@YPJN0EP*sOz_#o(Fg$u1U zK>ZRdLnhKwt9AV>bb7jzr@;UPZkyA7dq1)*dj$W$|g+rb8p#0B(6{V;ZT+an}U{C>sMHa+x-^D&XejT`u&v;^Dr40&%3d_5ay~Nd2Ei?vaGyqg-5f z6)uUUN&o+c#O3J!G^$-(5oAFj0`Nsg#{hn*Q~rDxK`s$sco7d8_D}>Si!>?cZP}ky z;4I2yh3+Mp&dnyxA;f8uVs3wM+Ql_X;gV>Y6m_n|6@kBsTYxcT8dc42hHO+GB2ug3 zN8;Z#DB)$O7{P?OKV1;iXN<5EFOq^3nh)3Hpb4fLq+le|gkc^YD9f$3 zUj_dTLa;i^1iB4AVcgOG&U?L%RpnqGVS+B03Nt!jPSZ`Ww_`vUd_otCO>hq{oP7G- zL-0<)!S^o{oFl%7j!1gM1YsF!`O3|^E74xrfJ28CdW2f!6@s@s2$mwE zVbKzf`zbzovV$vMzd(dB5Dn(*R-9!p0mTNlPzaXE1XApM1%(Lv|5bgvI;>M4ma{EF2$ZwxN0%QQ6WTy-@(J3an|6f`3k`b zgqjk(gNRG)oF1C7F0rc=E~075^==ccece0am~nKmFBLALQMhzP7{{;@62_x8#}$f| z2sM@a9wIJn79a55>XLg?;Ub!*T<~tDA7`JGtk!iz`jxBATXLAGmR$!RxIZdd9_;udoqOQ?@m3Y`C5=V;MGuqn<}A zTtw59>%%5o$)$rYxVUg9noLZVpENdQ`v_52<+rO%Mf2;(43x}>2H(-i3J8YP0(b16rJ>Pc^ESmUfAIhaW)9%bVU=UquenZ%^Ci=EA_V^*ouOB6b(3? z?Lv*p>ce`ZZ>sDDL|n?Ym=Rp+;%Z@m^iytn9QKLs?MltCKw-9<9y6_%CF%HsVUPt5 zuX6K}rbN}Ab@5KJz-g2W7bjQ+%S(!KpCX%FPs`EQz_A)*M@06wN#uKPmGyE^;06&2 zmmMM&n?%hCiwgF35fY6B1yd?Z3Mxw{U?3)T=s~e314kjp~-(~M`Q`M z`!Xe{$zP+$fa~os_k3(HCrdaU^nmX%pTyCAiEt*MEXM<0XrJ0oeII|MCfp)jnwFe)e`(LFQjl$r2dcVU*UxV)Th zLyMqI55X2Nr4sw~z===cZV8VSl}nhetUOtQr7L*s9h(lu^$^SA*`SMBJw&0HD(MsbfYuL{TYsp^A;q(U zggZ%!#}t)Kj81xx92b$S&EF8ol9K-yNe?87ck1LQ+TZqEF~(8Ezb6fkc8Gn*CaBn{ zZD9_1e?b%CA;^7~b&?W%zm)q4U|5bYvP$lse=~d>qW(8xM-i3p;2u2Y6p_I+KdSB{2lxM*`jUvAIu0#ozqdE$2+vB&K*P6)3AJTSxOpyqpF6B}gDzaoS&^#Bg#HU2n3T@@Cy zGZ6WMFikx~#_XT`AVf2yJozJjn$P+Go$bljg{vA{N^(-tgVi;e%pyGclVJ3snzN7i zNl5myZwTk%dWdM$c-afz61g6h8O2uuyU!Hnn*7L*prr6BTV+lj^>}$P_V~X z2q7!^)9jo*f3NYbH@gTcKC_Nh+no^;{I3>q-VYl^s2`(M$0RHs-a3uuX+k&wBF&T zny$q3sv3;Zy|tfei4xOGYc8*;tY)jnfZZNqebzp^hd5QuuEX6NF$+(fdWq>_^?0vW zhsW1=?8oDPa_q+Q!9e(Fx-Jyf^%5(QKBl*rz_!^3_7>yRA-AyW;LRWH=x{>XxgiI) zbqGy4lC|lU+WvN~xQ($ZP>?5%VF|D|Pu%Bw1_cV(EMLKrd~p$?iG9Qz6!&x=u}G~( zriOmv8;lvCVt}{|apOSoAj5Ox zIAvtF(kIJ`OYxDcyw6MmY%UdT>P8W2H~QPW>Be}Q{dTDsLe*&=BgSBkX=B6|`hAjA zsp_&C{X(JcyyyoDFa=fHG)BBapIfgOD;E1Z2AxN) zUe|iX?hp7FOa?QDW0S=;-s83byauE6!7FS5GxORu&`@Z$!R1kqlU!_@qWMS?b;@SVx1sS|uB) z29<22Vwq&5)Ki#j95yq_DB0-aA`|;@_91rT{MvUM<_I zdurKM57p}m3kuj-eiit^p;JLzg99uZHGO? zg#C(L!m)OD7drthOwcof|4F0I zG3W6282xsfu3Jhyb)0C0wGE;vfJURE3!V!E8U=8@0nNf{d%_v9L~SJkZyYLGJm^ez z;#|!k9t(#CiQe$VbhE*`FXq46e(|ihm3b5W_pt7=*a{Av7voUQz4KzB8fWH$i(;;l z%&ubc#G9fHzVxtg zqLs0FSP^P%0f|>pmwZ2~2@~G@LF~+Mv>*RbOu|v=drgd&2d5PtyCz!v9Yxk)X%51~ zYp7%bEWIYSMBcBiiQ&!)x_d(m!6fmf7!%lpiK;-UD=b7P(#%5|WZ!U8ObD{2q^8P4 zO&*SJo)9@@Jhg3m+4G>07$S|tTLbY$UwI^EYWTHeX0gY$<5mq5AUmDM;AL4l=V}&Z zf4(FC17At8S7q>@4W90iWZv)P@=C@mFeQ&?Y4GU?Ed0eP*z5B6APaBXw*6liiL}&I zIh9n?OR9Cl&f82<4W2LN(^xe8r^r)-e4wY_mqZ-0ut9W_X8babT#l zdMKQ0Y_6}xxIvHmI_hDbh_tlIdlavK*Tc+cy#9mIaW!9Ou-`7>?=lt$i%0PY=4=0K z6z`)!pGZHJ7qJHy6n-1Wxv|VTGp5Ws3ymvfuuWy8n7-nRt7ymFrGRa|)S4zNp{!>MxK#_Koj+YO+v;6Lzo zX@6x|Q_>X2mnyBDRB4MURH|CJJ^M}OEx6riv+YsC(=YQ7Z?5t;{{jWCa4SqcU}5Vb)o1h-o~j5!p?HRK7S)Fbhw$?zWL^{ z8rZT4o!3tqje%ruB`5F=m26+nt0cSHEEWAGbOsy0zbbSvJpZ2$yq{@vt}YipC_?9v zoU=yMmYl%Wiea6qmYqN>pyUJwYh+7Vrjeb&%^KMmGq)_O_KpzW&HgPp;) zP|-;g*$nt;qD55iR_w4S+h295R+=TMK9=n8o*G#5xH0#1!ZRxyPK^2LNqoqTt&K`c z;V=$Z6V4X<@al*X--Yp`N*otORmYV028`2cInkpS)hQ+34C6oJ!F=$ixWg0p_amcm zs}<<8nWw;^gVtV9-c4KwnKr*j*_MC0g%2cK{wjkAFP#;9GR0N*yO8xK#Q}gbK{wft zEN40&`16JipF(DfouKv)lRvCFXY2}#xATDhnD0+`;7G$KX}RW$sMsoW{BSg(@&8bX z=~X*1Mz1LNsrD$bhffI7{Tqlo`CIHH-gTg-)bcD3FlLpM449N#Hle%j*%WkGWj{%Apy;#F5oW47dKd4S2nWkNr17vIg`_2b;?iy7jdrFSu3fo;3_a6K8@lXxgb*4OElL7zSR5mvZr`X0_0 z?ha2HFWAeYOw@XQKDYs)kB&a9y$n0|@&T+6_&$CW=Lm^V*w52~sVf+K0u37U9`eiq zxHUVrvx+jLNi7C$9OfqPM1%_Ao&9_lJ0$Z?KfoVlh4wuMcp76nWGtwT2eDJoqK*&H zToUoaRGpTyO4xo)46?7O;|mxx{}u;%em%Fa<4{u1TeHispq__ey{)b1k$5^>&&yD3 z$3vLL_LA6OtyJgNVa{P5%Z|X>!`v!OnojjBSS-yEex2GHz82ViZVkwtR6Gv*kGwto8R&bAr(^mn z$8c?6w^YXJRwo3l(lY2s_e0kdTfhRJe~V=*g!*s!PPlahGkWG0x403u#P@7*UXFxi z-*TPI)C}TJ;1(%`ka2=gBC|Gcs8?iboZdy6*y&_;H^{06^tW!$^PsUTv2AuBh>zQ=xbOkmv@F+F* zzvB(OlNuG3ItTXqNR-$6EHYn#=g#sRl(CzxuEU+Py!d`m?Ils==VVcf&hfXbJT3hp z(}c7DGU8V*FMzaNihKXdGb&E@UiR02<-b1jkQolA^0oGzlWlP+JUvO7o241r=~SeH zBt7hXo5|}etyd(!Zxfj5*Ebp?myET%-s{BK}E+0vr3Qxj9$r*`cc*ETCD zJ1(_rkHon2)b`!t(zCmEOY5H8Hnn|XvVG$!+Ysg#p`y;D#@`6L_<_yFd{b1(s(AeO Snq){{W4q~gne?IU`u_p4MKeJF delta 140248 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

xP5Sn2JcSssVMzw90UI|2V3Snz(O*QO(Td zrZ96Z7lPwlUJQ9|27&MsvX#s-+Sb~)={KxLC~r$Hj`iUBzN$AQhF16@7?QJX9Q4ak1K6 zjAG^GVii#W5pa^|Q)0A3tKe9NsjDp|M(wyUw{J*v(fVp+x@t-86wB^6np7$r-6_h- zg`*2Hxf*cHw|hu-?E#t#nh)9uv_I%XP|QqtNN^trN=Y7!$-6qCF>aC)hvLCyZYIZt zfiWrz#Mz~iU&g2`Fh;!_V_Ox5Su7JftuQJJ?2gI;`z2WjTn-|}{E`V$VPJ<;7}#Mi zh4oWdhSleeCd`J4p;L|SaV2hQWQr<}TE~daO zY#JrIV(wF))v{8f@-tke<2j1wDa!pMF)}VDMVxft(8I!eqV1L=ysq(gYD}c)Kh4OA z+Ahb4WAGCEYt0wLV9g=Sf+F(qgSb4g`@!gtq>j>l6XNWAOOLaHHW)M#bEg|$mMb0E zuOvi|q9jr22*#5mgMd(lfi5)}=oCB@ENZl;3+MvSL7>P>Pa!Bugoj?`NlPKyRNW571kUDi2dGSa(2xV;KOK|lx(qy z03u3ipT7ODbpRjBM)w1Xscs|LeI3@g1Ob=u7P-9VT;3~=jH|LAqgCXMu)JNVbj*It zX9E!jo#L$YD*(J7f?Y=&LN$a}V0Mkrqzavik$G|6S?L#Hg|2churIKNoRjXxf@I{Z zbc#`$^QG}5g@AQ#gpy5NuBmVT61<*+v*GD2q~lDy6kQ?A3*VH`ss1UNRq2Nj3MsGm zfac9OfcMsLQSkl*P;N)j&~!Je(hz`Lm3{-qRmqC4Tm+L}p` zvC`Y%%ZkTlLKL9N+KSp(jK{G?I!cSZMGP1l9-JB;2<>>ZcvDdlSRochLw1}#9kSyk zuaOZLhl!ywu_QSrKAJv!F`n`(DJR9H>4q*{oh4U#aAUL%QBfB~)I-qf-EJ0M>EbLS z$Z|#6ip-RlAj@^Jm`7o=6t-1iuPN*^g?*(knt0?mp!ia| zlOw?joe8!yIlDGkVM7&03IW(Nt*s0eQFDym2~QxMw`(U#9u^Dd7>|ggxvKY~W}JJk zLU3H=J_pBD?g}`{ZGV-^wcT3fsI{dkM=kMks&d^dU->JVNSkNmMU_?RuChz#&of@E z*i%tgLJM3qpi=3U@?DCaYB0H{AyGGqlFC~M9+WHjLs^pL4Puv6Ek6O2g2W4WP{v5X zjTCHAgrs0Q1(cHfkSMH^z~zpb9WHmoak*oR${l0$aT%j>$JkDV?N->w3cIMV?-fR0 zmimMn=2(@rXkdML&;6diU)!VY6J z%62n|NnjJi^snh~p28L??6AU)D{LUj1_xMRa(EHBSFUfK_j~9Qudo3w_nSQL&v}P( z%aBxDR^q-29lvGXZuyQxAky0>|w6NICQamsJwP9J42y7El~~(Wf~0Y&pfj zhtVgMoJ61}iIJwW#9fp@Q;^uNGtZ7tXa}Cl?YU$DFoQ}K>lU6e&M}LGch)UjJf8Fl zx10I9)iL{Zprk~&mS2hRNvT9w=@YhtE2Igr!yHm#hsVoyX~w@k7abYU zw@g=!19L~_p8VP6hziPsITY5p^vPhlS!@G?TgpF@jE*n&jq28C9w>4RmYchx@M_$R zw*bdY3Nz9|V*_mS#rLh)q?|#+)k3RUaib6|S|-B7yP(gCAC?#`oONUq(icD- zSYXB6BC6DisVe5}4v%cG-x8*@Cy&UIE-{8NFL!y-LOpIi%=~LE7C&yp#5R$pEz+{5 zvTPzI(B&@f637c>;+w~fn&Y064l8S2_VwZ`rEQjlw#j6IuR;lt*szjG1LXda{V@I^ zq1kA%lC*jl{|$k}PxAt_^JTV-y#Q4

f${#5LqiOSlzWdU{+7gf}Oi+FAo`AnvZ9 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 diff --git a/DeviceAdapters/MCL_MicroDrive/MicroDriveXYStage.cpp b/DeviceAdapters/MCL_MicroDrive/MicroDriveXYStage.cpp index bc660f040..89305648e 100644 --- a/DeviceAdapters/MCL_MicroDrive/MicroDriveXYStage.cpp +++ b/DeviceAdapters/MCL_MicroDrive/MicroDriveXYStage.cpp @@ -1,45 +1,54 @@ /* File: MicroDriveXYStage.cpp -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ +#include "AcquireDevice.h" #include "MicroDriveXYStage.h" #include "mdutils.h" #include #include +#include #include using namespace std; -MicroDriveXYStage::MicroDriveXYStage(): +MicroDriveXYStage::MicroDriveXYStage() : handle_(0), serialNumber_(0), pid_(0), axis1_(0), axis2_(0), + axisBitmap_(0), stepSize_mm_(0.0), encoderResolution_(0.0), maxVelocity_(0.0), + maxVelocityThreeAxis_(0), + maxVelocityTwoAxis_(0), minVelocity_(0.0), velocity_(0.0), - busy_(false), initialized_(false), encoded_(false), - lastX_(0), - lastY_(0), + lastX_(0.0), + lastY_(0.0), iterativeMoves_(false), imRetry_(0), imToleranceUm_(.250), + movementDistanceX_(0.0), + movementDistanceY_(0.0), + movementType_(0), deviceHasTirfModuleAxis_(false), axis1IsTirfModule_(false), axis2IsTirfModule_(false), hasUnknownTirfModuleAxis_(false), - tirfModCalibrationMm_(0.0) + tirfModCalibrationMm_(0.0), + stopCommanded_(false), + movementThread_(NULL) { - InitializeDefaultErrorMessages(); + InitializeDefaultErrorMessages(); - // MCL error messages + // MCL error messages SetErrorText(MCL_GENERAL_ERROR, "MCL Error: General Error"); SetErrorText(MCL_DEV_ERROR, "MCL Error: Error transferring data to device"); SetErrorText(MCL_DEV_NOT_ATTACHED, "MCL Error: Device not attached"); @@ -50,135 +59,96 @@ MicroDriveXYStage::MicroDriveXYStage(): SetErrorText(MCL_INVALID_HANDLE, "MCL Error: Handle not valid"); SetErrorText(MCL_INVALID_DRIVER, "MCL Error: Invalid Driver"); - // Encoders present? + // Encoders present? CPropertyAction* pAct = new CPropertyAction(this, &MicroDriveXYStage::OnEncoded); CreateProperty(g_Keyword_Encoded, "Yes", MM::String, false, pAct, true); + + threadStartMutex_ = CreateMutex(NULL, FALSE, NULL); } + MicroDriveXYStage::~MicroDriveXYStage() { Shutdown(); } + bool MicroDriveXYStage::Busy() { - return busy_; + // Check if the thread is running + long ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) + { + return true; + } + + return false; } + void MicroDriveXYStage::GetName(char* pszName) const { CDeviceUtils::CopyLimitedString(pszName, g_XYStageDeviceName); } + int MicroDriveXYStage::Initialize() { - if (initialized_) - return DEVICE_OK; - - if (MCL_CorrectDriverVersion() == false) - return MCL_INVALID_DRIVER; - - // BEGIN LOCKING - HandleListLock(); - int err = DEVICE_OK; - // Look for a new device that can function as a XY stage. - vector skippedHandles; - bool validDevice = false; - int deviceHandle = 0; - int deviceAxis = 0; - unsigned short devicePid = 0; - unsigned char deviceAxisBitmap = 0; - do - { - deviceHandle = MCL_InitHandle(); - if (deviceHandle != 0) - { - MCL_GetProductID(&devicePid, deviceHandle); - // Single axis systems should be controlled by a Stage class. - if (devicePid == MICRODRIVE1) - { - validDevice = false; - skippedHandles.push_back(deviceHandle); - } - else - { - // Discover all avaialble axes - MCL_GetAxisInfo(&deviceAxisBitmap, deviceHandle); + HandleListLock(); + err = InitDeviceAdapter(); + HandleListUnlock(); - // Choose an available axis. - deviceAxis = ChooseAvailableXYStageAxes(devicePid, deviceAxisBitmap, deviceHandle); - if (deviceAxis != 0) - validDevice = true; - } - } - } while (deviceHandle != 0 && validDevice == false); + return err; +} - // Release extra handles acquired while looking for a new device. - for (vector::iterator it = skippedHandles.begin(); it != skippedHandles.end(); ++it) - { - MCL_ReleaseHandle(*it); - } - bool foundDevice = deviceHandle != 0 && validDevice == true; +int MicroDriveXYStage::InitDeviceAdapter() +{ + if (initialized_) + return DEVICE_OK; - // If we did not find a new device matching our criteria. Search through the devices that - // we already control for an availble axis. - if (foundDevice == false) - { - int numExistingHandles = MCL_NumberOfCurrentHandles(); - if (numExistingHandles > 0) - { - int* existingHandles = new int[numExistingHandles]; - MCL_GetAllHandles(existingHandles, numExistingHandles); - for (int ii = 0; ii < numExistingHandles; ii++) - { - deviceHandle = existingHandles[ii]; + // Attempt to acquire a device/axis for this adapter. + int ret = MCL_SUCCESS; + ret = AcquireDeviceHandle(XYSTAGE_TYPE, handle_, axis1_); + axis2_ = axis1_ + 1; + if (ret != MCL_SUCCESS) + return ret; - MCL_GetProductID(&devicePid, deviceHandle); - // Skip single axis systems. - if (devicePid == MICRODRIVE1) - continue; + // Query device information + serialNumber_ = MCL_GetSerialNumber(handle_); - // Discover all avaialble axes - MCL_GetAxisInfo(&deviceAxisBitmap, deviceHandle); + ret = MCL_GetProductID(&pid_, handle_); + if (ret != MCL_SUCCESS) + return ret; - // Choose an available axis. - deviceAxis = ChooseAvailableXYStageAxes(devicePid, deviceAxisBitmap, deviceHandle); - if (deviceAxis != 0) - { - foundDevice = true; - break; - } - } - delete[] existingHandles; - } - } + ret = MCL_GetAxisInfo(&axisBitmap_, handle_); + if (ret != MCL_SUCCESS) + return ret; - if (foundDevice == false) - { - HandleListUnlock(); - return MCL_INVALID_HANDLE; - } + ret = MCL_MDInformation(&encoderResolution_, &stepSize_mm_, &maxVelocity_, &maxVelocityTwoAxis_, &maxVelocityThreeAxis_, &minVelocity_, handle_); + if (ret != MCL_SUCCESS) + return ret; + velocity_ = maxVelocity_; // Check TIRF mod settings. - err = MCL_GetTirfModuleCalibration(&tirfModCalibrationMm_, deviceHandle); - if (err == MCL_SUCCESS) + ret = MCL_GetTirfModuleCalibration(&tirfModCalibrationMm_, handle_); + if (ret == MCL_SUCCESS) { // If we have a calibration we may also have the assigned tirf module axis. int tirfAxis = 0; - err = MCL_GetTirfModuleAxis(&tirfAxis, deviceHandle); - if (err == MCL_SUCCESS) + ret = MCL_GetTirfModuleAxis(&tirfAxis, handle_); + if (ret == MCL_SUCCESS) { // Check if one of the device adapter axes match the tirf mod axis. hasUnknownTirfModuleAxis_ = false; - if (tirfAxis == deviceAxis) + if (tirfAxis == axis1_) { deviceHasTirfModuleAxis_ = true; axis1IsTirfModule_ = true; } - else if (tirfAxis == (deviceAxis + 1)) + else if (tirfAxis == (axis2_)) { deviceHasTirfModuleAxis_ = true; axis2IsTirfModule_ = true; @@ -190,55 +160,40 @@ int MicroDriveXYStage::Initialize() // our device adapter axes are a tirf mod axis. hasUnknownTirfModuleAxis_ = true; deviceHasTirfModuleAxis_ = true; - if (IsAxisADefaultTirfModuleAxis(devicePid, deviceAxisBitmap, deviceAxis)) + if (IsAxisADefaultTirfModuleAxis(pid_, axisBitmap_, axis1_)) { axis1IsTirfModule_ = true; } - if (IsAxisADefaultTirfModuleAxis(devicePid, deviceAxisBitmap, deviceAxis + 1)) + if (IsAxisADefaultTirfModuleAxis(pid_, axisBitmap_, axis2_)) { axis2IsTirfModule_ = true; } } } - // Discover device information. - err = MCL_MDInformation(&encoderResolution_, &stepSize_mm_, &maxVelocity_, &maxVelocityTwoAxis_, &maxVelocityThreeAxis_, &minVelocity_, deviceHandle); - if (err != MCL_SUCCESS) - { - HandleListUnlock(); - return err; - } - velocity_ = maxVelocity_; - - // Create properties + // Create velocity error text. char velErrText[50]; sprintf(velErrText, "Velocity must be between %f and %f", minVelocity_, maxVelocity_); SetErrorText(INVALID_VELOCITY, velErrText); - CreateMicroDriveXYProperties(); + // Create Stage properties. + int err = DEVICE_OK; + err = CreateMicroDriveXYProperties(); + if (err != DEVICE_OK) + return err; err = UpdateStatus(); if (err != DEVICE_OK) { - HandleListUnlock(); return err; } - // Add device to global list - HandleListType device(deviceHandle, XYSTAGE_TYPE, deviceAxis, deviceAxis+1); - HandleListAddToLockedList(device); - - axis1_ = deviceAxis; - axis2_ = deviceAxis + 1; - handle_ = deviceHandle; - pid_ = devicePid; - initialized_ = true; - HandleListUnlock(); return err; } + int MicroDriveXYStage::CreateMicroDriveXYProperties() { int err; @@ -262,26 +217,43 @@ int MicroDriveXYStage::CreateMicroDriveXYProperties() // Device handle sprintf(iToChar, "%d", handle_); - err = CreateProperty("Handle", iToChar, MM::Integer, true); + err = CreateProperty(g_Keyword_Handle, iToChar, MM::String, true); + if (err != DEVICE_OK) + return err; + + // Serial Number + sprintf(iToChar, "%hu", pid_); + err = CreateProperty(g_Keyword_Serial_Num, iToChar, MM::String, true); + if (err != DEVICE_OK) + return err; + + // Product ID + sprintf(iToChar, "%hu", pid_); + err = CreateProperty(g_Keyword_ProductID, iToChar, MM::String, true); if (err != DEVICE_OK) return err; // Maximum velocity sprintf(iToChar, "%f", maxVelocity_); - err = CreateProperty("Maximum velocity (mm/s)", iToChar, MM::Float, true); + err = CreateProperty(g_Keyword_MaxVelocity, iToChar, MM::Float, true); if (err != DEVICE_OK) return err; // Minumum velocity sprintf(iToChar, "%f", minVelocity_); - err = CreateProperty("Minimum velocity (mm/s)", iToChar, MM::Float, true); + err = CreateProperty(g_Keyword_MinVelocity, iToChar, MM::Float, true); + if (err != DEVICE_OK) + return err; + + // Wait Time + err = CreateProperty(g_Keyword_WaitTime, " ", MM::Float, false); if (err != DEVICE_OK) return err; if (deviceHasTirfModuleAxis_) { sprintf(iToChar, "%f", tirfModCalibrationMm_); - err = CreateProperty("Distance to epi", iToChar, MM::Float, true); + err = CreateProperty(g_Keyword_DistanceToEpi, iToChar, MM::Float, true); if (err != DEVICE_OK) return err; } @@ -337,6 +309,18 @@ int MicroDriveXYStage::CreateMicroDriveXYProperties() if (err != DEVICE_OK) return err; + //Stop + vector stopList; + stopList.push_back(" "); + stopList.push_back(g_Keyword_Stop); + pAct = new CPropertyAction(this, &MicroDriveXYStage::OnStop); + err = CreateProperty(g_Keyword_Stop, " ", MM::String, false, pAct); + if (err != DEVICE_OK) + return err; + err = SetAllowedValues(g_Keyword_Stop, stopList); + if (err != DEVICE_OK) + return err; + // Return to origin pAct = new CPropertyAction(this, &MicroDriveXYStage::OnReturnToOrigin); err = CreateProperty(g_Keyword_ReturnToOrigin, "No", MM::String, false, pAct); @@ -410,8 +394,13 @@ int MicroDriveXYStage::CreateMicroDriveXYProperties() return DEVICE_OK; } + int MicroDriveXYStage::Shutdown() { + unsigned short status; + MCL_MDStop(&status, handle_); + WaitForSingleObject(movementThread_, INFINITE); + HandleListLock(); HandleListType device(handle_, XYSTAGE_TYPE, axis1_, axis2_); @@ -425,28 +414,52 @@ int MicroDriveXYStage::Shutdown() HandleListUnlock(); + CloseHandle(threadStartMutex_); + return DEVICE_OK; } + int MicroDriveXYStage::SetPositionUm(double x, double y) { - return SetPositionMm(x / 1000.0, y / 1000.0); + return BeginMovementThread(STANDARD_MOVE_TYPE, x / 1000.0, y / 1000.0); } + int MicroDriveXYStage::GetPositionUm(double& x, double& y) { - int err = GetPositionMm(x, y); + int err = DEVICE_OK; + + // Check if the thread is running + long ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) + { + // If we are moving simply return the last know position. + x = lastX_; + y = lastY_; + + } + else { + err = GetPositionMm(x, y); + } + x *= 1000.0; y *= 1000.0; return err; } -int MicroDriveXYStage::SetPositionMm(double goalX, double goalY) + +int MicroDriveXYStage::SetPositionMmSync(double goalX, double goalY) { int err; int currentRetries = 0; + int waitTime; bool moveFinished = false; + char iToChar[10]; + + if (stopCommanded_) + return DEVICE_OK; //Calculate the absolute position. double xCurrent, yCurrent; @@ -471,7 +484,6 @@ int MicroDriveXYStage::SetPositionMm(double goalX, double goalY) bool noXMovement = (fabs(xMove) < stepSize_mm_); bool noYMovement = (fabs(yMove) < stepSize_mm_); - if (noXMovement && noYMovement) { ///No movement @@ -500,9 +512,13 @@ int MicroDriveXYStage::SetPositionMm(double goalX, double goalY) return err; } - busy_ = true; + err = MCL_MicroDriveGetWaitTime(&waitTime, handle_); + if (err != MCL_SUCCESS) + return err; + sprintf(iToChar, "%d", waitTime); + SetProperty(g_Keyword_WaitTime, iToChar); + PauseDevice(); - busy_ = false; err = MCL_MDCurrentPositionM(axis1_, &endingMicroStepsX, handle_); if (err != MCL_SUCCESS) @@ -522,7 +538,7 @@ int MicroDriveXYStage::SetPositionMm(double goalX, double goalY) if (err != MCL_SUCCESS) return err; - if(iterativeMoves_ && encoded_) + if(iterativeMoves_ && encoded_ && !stopCommanded_) { double absDiffUmX = abs(goalX - xCurrent) * 1000.0; double absDiffUmY = abs(goalY - yCurrent) * 1000.0; @@ -555,24 +571,30 @@ int MicroDriveXYStage::SetPositionMm(double goalX, double goalY) return DEVICE_OK; } -int MicroDriveXYStage::SetRelativePositionUm(double dx, double dy){ - int err; - err = SetRelativePositionMm(dx/1000, dy/1000); +int MicroDriveXYStage::SetRelativePositionUm(double dx, double dy) +{ + double absX, absY; + int err = ConvertRelativeToAbsoluteMm(dx / 1000, dy / 1000, absX, absY); + if (err != MCL_SUCCESS) + return err; + + err = BeginMovementThread(STANDARD_MOVE_TYPE, absX, absY); return err; } -int MicroDriveXYStage::SetRelativePositionMm(double x, double y){ +int MicroDriveXYStage::ConvertRelativeToAbsoluteMm(double relX, double relY, double &absX, double &absY) +{ int err; bool mirrorX, mirrorY; - GetOrientation(mirrorX, mirrorY); + GetOrientation(mirrorX, mirrorY); - if (mirrorX) - x = -x; - if (mirrorY) - y = -y; + if (mirrorX) + relX = -relX; + if (mirrorY) + relY = -relY; //Calculate the absolute position. double xCurrent, yCurrent; @@ -580,12 +602,26 @@ int MicroDriveXYStage::SetRelativePositionMm(double x, double y){ if (err != MCL_SUCCESS) return err; - double xGoal = xCurrent + x; - double yGoal = yCurrent + y; + absX = xCurrent + relX; + absY = yCurrent + relY; - return SetPositionMm(xGoal, yGoal); + return MCL_SUCCESS; } + +int MicroDriveXYStage::SetRelativePositionMmSync(double x, double y) +{ + double absX, absY; + int err = ConvertRelativeToAbsoluteMm(x, y, absX, absY); + if (err != MCL_SUCCESS) + return err; + + err = SetPositionMmSync(absX, absY); + + return err; +} + + int MicroDriveXYStage::GetPositionMm(double& x, double& y) { if (encoded_) { @@ -593,6 +629,7 @@ int MicroDriveXYStage::GetPositionMm(double& x, double& y) int err = MCL_MDReadEncoders(&m1, &m2, &m3, &m4, handle_); if (err != MCL_SUCCESS) return err; + switch (axis1_) { case M1AXIS: @@ -637,22 +674,34 @@ int MicroDriveXYStage::GetPositionMm(double& x, double& y) return DEVICE_OK; } + double MicroDriveXYStage::GetStepSize() { return stepSize_mm_; } + int MicroDriveXYStage::SetPositionSteps(long x, long y) { - return SetPositionMm(x * stepSize_mm_, y * stepSize_mm_); + return BeginMovementThread(STANDARD_MOVE_TYPE, x * stepSize_mm_, y * stepSize_mm_); } + int MicroDriveXYStage::GetPositionSteps(long& x, long& y) { - int err; + int err = DEVICE_OK; double getX, getY; - err = GetPositionMm(getX, getY); + // Check if the thread is running + long ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) + { + // If we are moving simply return the last know position. + getX = lastX_; + getY = lastY_; + } + else + err = GetPositionMm(getX, getY); x = (long) (getX / stepSize_mm_); y = (long) (getY / stepSize_mm_); @@ -660,39 +709,59 @@ int MicroDriveXYStage::GetPositionSteps(long& x, long& y) return err; } + int MicroDriveXYStage::Home() { - return MoveToForwardLimits(); + return BeginMovementThread(HOME_TYPE, 0.0, 0.0); } + void MicroDriveXYStage::PauseDevice() { MCL_MicroDriveWait(handle_); } + int MicroDriveXYStage::Stop() { int err = MCL_MDStop(NULL, handle_); + stopCommanded_ = true; if (err != MCL_SUCCESS) return err; return DEVICE_OK; } + int MicroDriveXYStage::SetOrigin() { - int err = MCL_SUCCESS; - if (encoded_ && axis1_ < M5AXIS) + long ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) { - err = MCL_MDResetEncoder(axis1_, NULL, handle_); - if (err != MCL_SUCCESS) - return err; + return MCL_DEV_NOT_READY; } - if (encoded_ && axis2_ < M5AXIS) + + return SetOriginSync(); +} + + +int MicroDriveXYStage::SetOriginSync() +{ + if (encoded_) { - err = MCL_MDResetEncoder(axis2_, NULL, handle_); - if (err != MCL_SUCCESS) - return err; + int err = MCL_SUCCESS; + if (axis1_ < M5AXIS) + { + err = MCL_MDResetEncoder(axis1_, NULL, handle_); + if (err != MCL_SUCCESS) + return err; + } + if (axis2_ < M5AXIS) + { + err = MCL_MDResetEncoder(axis2_, NULL, handle_); + if (err != MCL_SUCCESS) + return err; + } } lastX_ = 0; lastY_ = 0; @@ -700,27 +769,32 @@ int MicroDriveXYStage::SetOrigin() return DEVICE_OK; } + int MicroDriveXYStage::GetLimitsUm(double& /*xMin*/, double& /*xMax*/, double& /*yMin*/, double& /*yMax*/) { return DEVICE_UNSUPPORTED_COMMAND; } + int MicroDriveXYStage::GetStepLimits(long& /*xMin*/, long& /*xMax*/, long& /*yMin*/, long& /*yMax*/) { return DEVICE_UNSUPPORTED_COMMAND; } + double MicroDriveXYStage::GetStepSizeXUm() { return stepSize_mm_; } + double MicroDriveXYStage::GetStepSizeYUm() { return stepSize_mm_; } -int MicroDriveXYStage::Calibrate() + +int MicroDriveXYStage::CalibrateSync() { int err; double xPosOrig; @@ -732,7 +806,7 @@ int MicroDriveXYStage::Calibrate() if (err != MCL_SUCCESS) return err; - err = MoveToForwardLimits(); + err = MoveToForwardLimitsSync(); if (err != DEVICE_OK) return err; @@ -740,17 +814,18 @@ int MicroDriveXYStage::Calibrate() if (err != MCL_SUCCESS) return err; - err = SetOrigin(); + err = SetOriginSync(); if (err != DEVICE_OK) return err; - err = SetPositionMm((xPosOrig - xPosLimit), (yPosOrig - yPosLimit)); + err = SetPositionMmSync((xPosOrig - xPosLimit), (yPosOrig - yPosLimit)); if (err != DEVICE_OK) return err; return DEVICE_OK; } + bool MicroDriveXYStage::XMoveBlocked(double possNewPos) { unsigned short status = 0; @@ -770,6 +845,7 @@ bool MicroDriveXYStage::XMoveBlocked(double possNewPos) return false; } + bool MicroDriveXYStage::YMoveBlocked(double possNewPos) { unsigned short status = 0; @@ -789,10 +865,12 @@ bool MicroDriveXYStage::YMoveBlocked(double possNewPos) return false; } + void MicroDriveXYStage::GetOrientation(bool& mirrorX, bool& mirrorY) { char val[MM::MaxStrLength]; int ret = this->GetProperty(MM::g_Keyword_Transpose_MirrorX, val); + assert(ret == DEVICE_OK); mirrorX = strcmp(val, "1") == 0 ? true : false; @@ -801,7 +879,8 @@ void MicroDriveXYStage::GetOrientation(bool& mirrorX, bool& mirrorY) mirrorY = strcmp(val, "1") == 0 ? true : false; } -int MicroDriveXYStage::MoveToForwardLimits() + +int MicroDriveXYStage::MoveToForwardLimitsSync() { int err; unsigned short status = 0; @@ -814,10 +893,9 @@ int MicroDriveXYStage::MoveToForwardLimits() unsigned short axis2ForwardLimitBitMask = LimitBitMask(pid_, axis2_, FORWARD); unsigned short bothLimits = axis1ForwardLimitBitMask | axis2ForwardLimitBitMask; - while ((status & bothLimits) != 0) + while (((status & bothLimits) != 0) && !stopCommanded_) { err = SetRelativePositionUm(4000, 4000); - if (err != DEVICE_OK) return err; @@ -829,13 +907,14 @@ int MicroDriveXYStage::MoveToForwardLimits() return DEVICE_OK; } -int MicroDriveXYStage::ReturnToOrigin() + +int MicroDriveXYStage::ReturnToOriginSync() { int err; double xPos; double yPos; - err = SetPositionMm(0.0, 0.0); + err = SetPositionMmSync(0.0, 0.0); if (err != DEVICE_OK) return err; @@ -850,9 +929,9 @@ int MicroDriveXYStage::ReturnToOrigin() bool xNotFinished = xPos != 0 && yBlocked; bool yNotFinished = yPos != 0 && xBlocked; - if(xNotFinished || yNotFinished) + if ((xNotFinished || yNotFinished) && !stopCommanded_) { - err = SetPositionMm(0, 0); + err = SetPositionMmSync(0, 0); if (err != DEVICE_OK) return err; } @@ -860,11 +939,13 @@ int MicroDriveXYStage::ReturnToOrigin() return DEVICE_OK; } -int MicroDriveXYStage::FindEpi() + +int MicroDriveXYStage::FindEpiSync() { if ((axis1IsTirfModule_ == false) && (axis2IsTirfModule_ == false)) return DEVICE_OK; + int err = MCL_SUCCESS; int epiAxis = 0; double a1Find = 0.0; double a2Find = 0.0; @@ -889,14 +970,15 @@ int MicroDriveXYStage::FindEpi() MCL_MDStatus(&status, handle_); // Move the stage to its reverse limit. - while ((status & mask) == mask) + while (((status & mask) == mask) && !stopCommanded_) { - SetRelativePositionMm(a1Find, a2Find); + err = SetRelativePositionMmSync(a1Find, a2Find); + if (err != DEVICE_OK) + return err; MCL_MDStatus(&status, handle_); } // Set the orgin of the epi axis at the reverse limit. - int err = MCL_SUCCESS; if (encoded_ && epiAxis < M5AXIS) { err = MCL_MDResetEncoder(epiAxis, NULL, handle_); @@ -909,7 +991,9 @@ int MicroDriveXYStage::FindEpi() lastY_ = 0; // Move the calibration distance to find epi. - SetPositionMm(a1Epi, a2Epi); + err = SetPositionMmSync(a1Epi, a2Epi); + if (err != DEVICE_OK) + return err; // Set the orgin of the epi axis at epi. if (encoded_ && epiAxis < M5AXIS) @@ -945,8 +1029,7 @@ int MicroDriveXYStage::OnPositionXmm(MM::PropertyBase* pProp, MM::ActionType eAc err = GetPositionMm(x, y); if(err != MCL_SUCCESS) return err; - err = SetPositionMm(pos, y); - + err = BeginMovementThread(STANDARD_MOVE_TYPE, pos, y); if (err != DEVICE_OK) return err; } @@ -954,6 +1037,7 @@ int MicroDriveXYStage::OnPositionXmm(MM::PropertyBase* pProp, MM::ActionType eAc return DEVICE_OK; } + int MicroDriveXYStage::OnPositionYmm(MM::PropertyBase* pProp, MM::ActionType eAct) { int err; @@ -973,8 +1057,7 @@ int MicroDriveXYStage::OnPositionYmm(MM::PropertyBase* pProp, MM::ActionType eAc err = GetPositionMm(x, y); if(err != MCL_SUCCESS) return err; - err = SetPositionMm(x, pos); - + err = BeginMovementThread(STANDARD_MOVE_TYPE, x, pos); if (err != DEVICE_OK) return err; } @@ -982,6 +1065,7 @@ int MicroDriveXYStage::OnPositionYmm(MM::PropertyBase* pProp, MM::ActionType eAc return DEVICE_OK; } + int MicroDriveXYStage::OnMoveXmm(MM::PropertyBase* pProp, MM::ActionType eAct) { int err; @@ -989,7 +1073,8 @@ int MicroDriveXYStage::OnMoveXmm(MM::PropertyBase* pProp, MM::ActionType eAct) if (eAct == MM::AfterSet) { pProp->Get(pos); - err = SetRelativePositionMm(pos, 0.0); + + err = SetRelativePositionUm(pos * 1000.0, 0.0); if (err != MCL_SUCCESS) return err; } @@ -997,6 +1082,7 @@ int MicroDriveXYStage::OnMoveXmm(MM::PropertyBase* pProp, MM::ActionType eAct) return DEVICE_OK; } + int MicroDriveXYStage::OnMoveYmm(MM::PropertyBase* pProp, MM::ActionType eAct) { int err; @@ -1004,13 +1090,15 @@ int MicroDriveXYStage::OnMoveYmm(MM::PropertyBase* pProp, MM::ActionType eAct) if (eAct == MM::AfterSet) { pProp->Get(pos); - err = SetRelativePositionMm(0.0, pos); + + err = SetRelativePositionUm(0.0, pos * 1000.0); if (err != MCL_SUCCESS) return err; } return DEVICE_OK; } + int MicroDriveXYStage::OnSetOriginHere(MM::PropertyBase* pProp, MM::ActionType eAct) { int err; @@ -1037,6 +1125,7 @@ int MicroDriveXYStage::OnSetOriginHere(MM::PropertyBase* pProp, MM::ActionType e return DEVICE_OK; } + int MicroDriveXYStage::OnCalibrate(MM::PropertyBase* pProp, MM::ActionType eAct) { int err; @@ -1052,7 +1141,7 @@ int MicroDriveXYStage::OnCalibrate(MM::PropertyBase* pProp, MM::ActionType eAct) if (message.compare(g_Listword_Yes) == 0) { - err = Calibrate(); + err = BeginMovementThread(CALIBRATE_TYPE, 0.0, 0.0); if (err != DEVICE_OK) return err; } @@ -1061,6 +1150,7 @@ int MicroDriveXYStage::OnCalibrate(MM::PropertyBase* pProp, MM::ActionType eAct) return DEVICE_OK; } + int MicroDriveXYStage::OnReturnToOrigin(MM::PropertyBase* pProp, MM::ActionType eAct) { int err; @@ -1076,7 +1166,7 @@ int MicroDriveXYStage::OnReturnToOrigin(MM::PropertyBase* pProp, MM::ActionType if (message.compare(g_Listword_Yes) == 0) { - err = ReturnToOrigin(); + err = BeginMovementThread(RETURN_TO_ORIGIN_TYPE, 0, 0); if (err != DEVICE_OK) return err; } @@ -1085,6 +1175,7 @@ int MicroDriveXYStage::OnReturnToOrigin(MM::PropertyBase* pProp, MM::ActionType return DEVICE_OK; } + int MicroDriveXYStage::OnPositionXYmm(MM::PropertyBase* pProp, MM::ActionType eAct) { int err; @@ -1109,7 +1200,7 @@ int MicroDriveXYStage::OnPositionXYmm(MM::PropertyBase* pProp, MM::ActionType eA x = strtod(tokenInput[0].c_str(), &pEnd); y = strtod(tokenInput[1].c_str(), &pEnd); - err = SetPositionMm(x, y); + err = BeginMovementThread(STANDARD_MOVE_TYPE, x, y); if (err != DEVICE_OK) return err; } @@ -1117,6 +1208,7 @@ int MicroDriveXYStage::OnPositionXYmm(MM::PropertyBase* pProp, MM::ActionType eA return DEVICE_OK; } + int MicroDriveXYStage::OnVelocity(MM::PropertyBase* pProp, MM::ActionType eAct){ double vel; @@ -1139,6 +1231,7 @@ int MicroDriveXYStage::OnVelocity(MM::PropertyBase* pProp, MM::ActionType eAct){ return DEVICE_OK; } + int MicroDriveXYStage::OnEncoded(MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) @@ -1155,6 +1248,7 @@ int MicroDriveXYStage::OnEncoded(MM::PropertyBase* pProp, MM::ActionType eAct) return DEVICE_OK; } + int MicroDriveXYStage::OnIterativeMove(MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) @@ -1171,6 +1265,7 @@ int MicroDriveXYStage::OnIterativeMove(MM::PropertyBase* pProp, MM::ActionType e return DEVICE_OK; } + int MicroDriveXYStage::OnImRetry(MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) @@ -1187,6 +1282,7 @@ int MicroDriveXYStage::OnImRetry(MM::PropertyBase* pProp, MM::ActionType eAct) return DEVICE_OK; } + int MicroDriveXYStage::OnImToleranceUm(MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) @@ -1203,12 +1299,14 @@ int MicroDriveXYStage::OnImToleranceUm(MM::PropertyBase* pProp, MM::ActionType e return DEVICE_OK; } + int MicroDriveXYStage::IsXYStageSequenceable(bool& isSequenceable) const { isSequenceable = false; return DEVICE_OK; } + int MicroDriveXYStage::OnIsTirfModuleAxis1(MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) @@ -1224,6 +1322,7 @@ int MicroDriveXYStage::OnIsTirfModuleAxis1(MM::PropertyBase* pProp, MM::ActionTy return DEVICE_OK; } + int MicroDriveXYStage::OnIsTirfModuleAxis2(MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) @@ -1239,6 +1338,7 @@ int MicroDriveXYStage::OnIsTirfModuleAxis2(MM::PropertyBase* pProp, MM::ActionTy return DEVICE_OK; } + int MicroDriveXYStage::OnFindEpi(MM::PropertyBase* pProp, MM::ActionType eAct) { int err; @@ -1254,7 +1354,7 @@ int MicroDriveXYStage::OnFindEpi(MM::PropertyBase* pProp, MM::ActionType eAct) if (message.compare(g_Listword_Yes) == 0) { - err = FindEpi(); + err = BeginMovementThread(FIND_EPI_TYPE, 0.0, 0.0); if (err != DEVICE_OK) return err; } @@ -1262,44 +1362,74 @@ int MicroDriveXYStage::OnFindEpi(MM::PropertyBase* pProp, MM::ActionType eAct) return DEVICE_OK; } -// The handle list must be locked when calling this function. -int MicroDriveXYStage::ChooseAvailableXYStageAxes(unsigned short pid, unsigned char axisBitmap, int handle) + +int MicroDriveXYStage::OnStop(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + int err = DEVICE_OK; + string input; + if (eAct == MM::AfterSet) + { + pProp->Get(input); + if (input.compare(g_Keyword_Stop) == 0) + { + err = Stop(); + } + } + return err; +} + + +int MicroDriveXYStage::BeginMovementThread(int type, double distanceX, double distanceY) { - int ordersize = 2; - int order[] = { M1AXIS, M4AXIS}; + long ret = WaitForSingleObject(threadStartMutex_, 0); + if (ret == WAIT_TIMEOUT) + { + return MCL_DEV_NOT_READY; + } - switch (pid) + // Check if the thread is running + ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) { - case MICRODRIVE: - case NC_MICRODRIVE: - case MICRODRIVE3: - case MICRODRIVE4: - order[1] = 0; - break; - // Use the standard order. - default: - break; + ReleaseMutex(threadStartMutex_); + return MCL_DEV_NOT_READY; } - int axis = 0; - for (int ii = 0; ii < ordersize; ii++) + // Create a new thread for our action + movementType_ = type; + movementDistanceX_ = distanceX; + movementDistanceY_ = distanceY; + movementThread_ = CreateThread(0, 0, ExecuteMovement, this, 0, 0); + + ReleaseMutex(threadStartMutex_); + + return DEVICE_OK; + +} + + +DWORD WINAPI MicroDriveXYStage::ExecuteMovement(LPVOID lpParam) { + + MicroDriveXYStage* instance = reinterpret_cast(lpParam); + instance->stopCommanded_ = false; + switch (instance->movementType_) { - if (order[ii] == 0) - break; - - // Check that both axes are valid. - int xBitmap = 0x1 << (order[ii] - 1); - int yBitmap = 0x1 << order[ii]; - if (((axisBitmap & xBitmap) != xBitmap) || - ((axisBitmap & yBitmap) != yBitmap)) - continue; - - HandleListType device(handle, XYSTAGE_TYPE, order[ii], order[ii] + 1); - if (HandleExistsOnLockedList(device) == false) - { - axis = order[ii]; - break; - } + case STANDARD_MOVE_TYPE: + instance->SetPositionMmSync(instance->movementDistanceX_, instance->movementDistanceY_); + break; + case CALIBRATE_TYPE: + instance->CalibrateSync(); + break; + case HOME_TYPE: + instance->MoveToForwardLimitsSync(); + break; + case RETURN_TO_ORIGIN_TYPE: + instance->ReturnToOriginSync(); + break; + case FIND_EPI_TYPE: + instance->FindEpiSync(); + break; } - return axis; + + return 0; } \ No newline at end of file diff --git a/DeviceAdapters/MCL_MicroDrive/MicroDriveXYStage.h b/DeviceAdapters/MCL_MicroDrive/MicroDriveXYStage.h index d631e543a..43d364817 100644 --- a/DeviceAdapters/MCL_MicroDrive/MicroDriveXYStage.h +++ b/DeviceAdapters/MCL_MicroDrive/MicroDriveXYStage.h @@ -1,6 +1,6 @@ /* File: MicroDriveXYStage.h -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #ifndef _MICRODRIVEXYSTAGE_H_ @@ -24,6 +24,7 @@ License: Distributed under the BSD license. #define ERR_UNKNOWN_POSITION 103 #define ERR_NOT_VALID_INPUT 104 + class MicroDriveXYStage : public CXYStageBase { public: @@ -41,7 +42,6 @@ class MicroDriveXYStage : public CXYStageBase virtual int SetPositionSteps(long x, long y); virtual int GetPositionSteps(long& x, long& y); virtual int Home(); - virtual int Stop(); virtual int SetOrigin(); virtual int GetLimitsUm(double& xMin, double& xMax, double& yMin, double& yMax); virtual int GetStepLimits(long &xMin, long &xMax, long &yMin, long &yMax); @@ -58,7 +58,7 @@ class MicroDriveXYStage : public CXYStageBase int OnMoveXmm(MM::PropertyBase* pProp, MM::ActionType eAct); int OnMoveYmm(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSetOriginHere(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnCalibrate(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnCalibrate(MM::PropertyBase* pProp, MM::ActionType eAct); int OnReturnToOrigin(MM::PropertyBase* pProp, MM::ActionType eAct); int OnPositionXYmm(MM::PropertyBase* pProp, MM::ActionType eAct); int OnVelocity(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -69,27 +69,30 @@ class MicroDriveXYStage : public CXYStageBase int OnIsTirfModuleAxis1(MM::PropertyBase* pProp, MM::ActionType eAct); int OnIsTirfModuleAxis2(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFindEpi(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnStop(MM::PropertyBase* pProp, MM::ActionType eAct); private: // Initialization int CreateMicroDriveXYProperties(); + int InitDeviceAdapter(); // Set/Get positions - int SetPositionMm(double x, double y); + int SetPositionMmSync(double x, double y); int GetPositionMm(double& x, double& y); - int SetRelativePositionMm(double x, double y); - int SetPositionXSteps(long x); - int SetPositionYSteps(long y); + int SetRelativePositionMmSync(double x, double y); // Calibration & origin methods - int Calibrate(); - int MoveToForwardLimits(); - int ReturnToOrigin(); - int FindEpi(); + int CalibrateSync(); + int MoveToForwardLimitsSync(); + int ReturnToOriginSync(); + int FindEpiSync(); + int SetOriginSync(); + + int ConvertRelativeToAbsoluteMm(double relX, double relY, double &absX, double &absY); // Pause devices - void PauseDevice(); - int ChooseAvailableXYStageAxes(unsigned short pid, unsigned char axisBitmap, int handle); + void PauseDevice(); + int Stop(); // Check if blocked bool XMoveBlocked(double possNewPos); @@ -97,12 +100,17 @@ class MicroDriveXYStage : public CXYStageBase void GetOrientation(bool& mirrorX, bool& mirrorY); + // Threading + int BeginMovementThread(int type, double distanceX, double distanceY); + static DWORD WINAPI ExecuteMovement(LPVOID lpParam); + // Device Information int handle_; int serialNumber_; unsigned short pid_; int axis1_; int axis2_; + unsigned char axisBitmap_; double stepSize_mm_; double encoderResolution_; double maxVelocity_; @@ -111,7 +119,6 @@ class MicroDriveXYStage : public CXYStageBase double minVelocity_; double velocity_; // Device State - bool busy_; bool initialized_; bool encoded_; double lastX_; @@ -126,6 +133,13 @@ class MicroDriveXYStage : public CXYStageBase bool axis2IsTirfModule_; bool hasUnknownTirfModuleAxis_; double tirfModCalibrationMm_; + // Threading + bool stopCommanded_; + int movementType_; + double movementDistanceX_; + double movementDistanceY_; + HANDLE movementThread_; + HANDLE threadStartMutex_; }; diff --git a/DeviceAdapters/MCL_MicroDrive/MicroDriveZStage.cpp b/DeviceAdapters/MCL_MicroDrive/MicroDriveZStage.cpp index da1d5f262..893109d66 100644 --- a/DeviceAdapters/MCL_MicroDrive/MicroDriveZStage.cpp +++ b/DeviceAdapters/MCL_MicroDrive/MicroDriveZStage.cpp @@ -1,8 +1,9 @@ /* File: MicroDriveZStage.cpp -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ +#include "AcquireDevice.h" #include "MicroDriveZStage.h" #include "mdutils.h" @@ -17,22 +18,26 @@ MCL_MicroDrive_ZStage::MCL_MicroDrive_ZStage() : serialNumber_(0), pid_(0), axis_(0), + axisBitmap_(0), stepSize_mm_(0.0), encoderResolution_(0.0), maxVelocity_(0.0), minVelocity_(0.0), velocity_(0.0), - busy_(false), initialized_(false), encoded_(false), lastZ_(0.0), iterativeMoves_(false), imRetry_(0), imToleranceUm_(.250), + movementDistance_(0.0), deviceHasTirfModuleAxis_(false), axisIsTirfModule_(false), hasUnknownTirfModuleAxis_(false), - tirfModCalibrationMm_(0.0) + tirfModCalibrationMm_(0.0), + stopCommanded_(false), + movementType_(0), + movementThread_(NULL) { InitializeDefaultErrorMessages(); @@ -49,6 +54,8 @@ MCL_MicroDrive_ZStage::MCL_MicroDrive_ZStage() : // Encoders present? CPropertyAction* pAct = new CPropertyAction(this, &MCL_MicroDrive_ZStage::OnEncoded); CreateProperty(g_Keyword_Encoded, "Yes", MM::String, false, pAct, true); + + threadStartMutex_ = CreateMutex(NULL, FALSE, NULL); } @@ -66,108 +73,58 @@ void MCL_MicroDrive_ZStage::GetName(char* name) const int MCL_MicroDrive_ZStage::Initialize() { - if (initialized_) - return DEVICE_OK; - - if (MCL_CorrectDriverVersion() == false) - return MCL_INVALID_DRIVER; - - // BEGIN LOCKING - HandleListLock(); - int err = DEVICE_OK; - // Look for a new device that can function as a stage. - vector skippedHandles; - bool validDevice = false; - int deviceHandle = 0; - int deviceAxis = 0; - unsigned short devicePid = 0; - unsigned char deviceAxisBitmap = 0; - do - { - deviceHandle = MCL_InitHandle(); - if (deviceHandle != 0) - { - MCL_GetProductID(&devicePid, deviceHandle); - // Two axis systems should be controlled by a XY Stage class. - if (devicePid == MICRODRIVE || devicePid == NC_MICRODRIVE) - { - validDevice = false; - skippedHandles.push_back(deviceHandle); - } - else - { - validDevice = true; + HandleListLock(); + err = InitDeviceAdapter(); + HandleListUnlock(); - // Discover all avaialble axes - MCL_GetAxisInfo(&deviceAxisBitmap, deviceHandle); + return err; +} - // Choose an available axis. - deviceAxis = ChooseAvailableStageAxis(devicePid, deviceAxisBitmap, deviceHandle); - } - } - } while (deviceHandle != 0 && validDevice == false); - // Release extra handles acquired while looking for a new device. - for (vector::iterator it = skippedHandles.begin(); it != skippedHandles.end(); ++it) - { - MCL_ReleaseHandle(*it); - } +int MCL_MicroDrive_ZStage::InitDeviceAdapter() +{ + if (initialized_) + return DEVICE_OK; - bool foundDevice = deviceHandle != 0 && validDevice == true; - - // If we did not find a new device matching our criteria. Search through the devices that - // we already control for an availble axis. - if (foundDevice == false) + // Attempt to acquire a device/axis for this adapter. + int ret = MCL_SUCCESS; + ret = AcquireDeviceHandle(STAGE_TYPE, handle_, axis_); + if (ret != MCL_SUCCESS) { - int numExistingHandles = MCL_NumberOfCurrentHandles(); - if (numExistingHandles > 0) - { - int* existingHandles = new int[numExistingHandles]; - MCL_GetAllHandles(existingHandles, numExistingHandles); - for (int ii = 0; ii < numExistingHandles; ii++) - { - deviceHandle = existingHandles[ii]; + return ret; + } - MCL_GetProductID(&devicePid, deviceHandle); - // Skip two axis systems. - if (devicePid == MICRODRIVE || devicePid == NC_MICRODRIVE) - continue; + // Query device information + serialNumber_ = MCL_GetSerialNumber(handle_); - // Discover all avaialble axes - MCL_GetAxisInfo(&deviceAxisBitmap, deviceHandle); + ret = MCL_GetProductID(&pid_, handle_); + if (ret != MCL_SUCCESS) + return ret; - // Choose an available axis. - deviceAxis = ChooseAvailableStageAxis(devicePid, deviceAxisBitmap, deviceHandle); - if (deviceAxis != 0) - { - foundDevice = true; - break; - } - } - delete[] existingHandles; - } - } + ret = MCL_GetAxisInfo(&axisBitmap_, handle_); + if (ret != MCL_SUCCESS) + return ret; - if (foundDevice == false) - { - HandleListUnlock(); - return MCL_INVALID_HANDLE; - } + double ignore1, ignore2; + ret = MCL_MDInformation(&encoderResolution_, &stepSize_mm_, &maxVelocity_, &ignore1, &ignore2, &minVelocity_, handle_); + if (ret != MCL_SUCCESS) + return ret; + velocity_ = maxVelocity_; // Check TIRF mod settings. - err = MCL_GetTirfModuleCalibration(&tirfModCalibrationMm_, deviceHandle); - if (err == MCL_SUCCESS) + ret = MCL_GetTirfModuleCalibration(&tirfModCalibrationMm_, handle_); + if (ret == MCL_SUCCESS) { // If we have a calibration we may also have the assigned tirf module axis. int tirfAxis = 0; - err = MCL_GetTirfModuleAxis(&tirfAxis, deviceHandle); - if (err == MCL_SUCCESS) + ret = MCL_GetTirfModuleAxis(&tirfAxis, handle_); + if (ret == MCL_SUCCESS) { // Check if one of the device adapter axes match the tirf mod axis. hasUnknownTirfModuleAxis_ = false; - if (tirfAxis == deviceAxis) + if (tirfAxis == axis_) { deviceHasTirfModuleAxis_ = true; axisIsTirfModule_ = true; @@ -179,55 +136,41 @@ int MCL_MicroDrive_ZStage::Initialize() // our device adapter axes are a tirf mod axis. hasUnknownTirfModuleAxis_ = true; deviceHasTirfModuleAxis_ = true; - if (IsAxisADefaultTirfModuleAxis(devicePid, deviceAxisBitmap, deviceAxis)) + + if (IsAxisADefaultTirfModuleAxis(pid_, axisBitmap_, axis_)) { axisIsTirfModule_ = true; } } } - // Discover device information. - double ignore1, ignore2; - err = MCL_MDInformation(&encoderResolution_, &stepSize_mm_, &maxVelocity_, &ignore1, &ignore2, &minVelocity_, deviceHandle); - if (err != MCL_SUCCESS) - { - HandleListUnlock(); - return err; - } - velocity_ = maxVelocity_; - - // Create properties + // Create velocity error text. char velErrText[50]; sprintf(velErrText, "Velocity must be between %f and %f", minVelocity_, maxVelocity_); SetErrorText(INVALID_VELOCITY, velErrText); - CreateZStageProperties(); + // Create Stage properties. + int err = DEVICE_OK; + err = CreateZStageProperties(); + if (err != DEVICE_OK) + return err; err = UpdateStatus(); if (err != DEVICE_OK) - { - HandleListUnlock(); return err; - } - - // Add device to global list - HandleListType device(deviceHandle, STAGE_TYPE, deviceAxis, 0); - HandleListAddToLockedList(device); - - axis_ = deviceAxis; - handle_ = deviceHandle; - pid_ = devicePid; initialized_ = true; - HandleListUnlock(); return err; } int MCL_MicroDrive_ZStage::Shutdown() { - //BEGIN LOCKING + unsigned short status; + MCL_MDStop(&status, handle_); + WaitForSingleObject(movementThread_, INFINITE); + HandleListLock(); HandleListType device(handle_, STAGE_TYPE, axis_, 0); @@ -239,7 +182,8 @@ int MCL_MicroDrive_ZStage::Shutdown() initialized_ = false; HandleListUnlock(); - //END LOCKING + + CloseHandle(threadStartMutex_); return DEVICE_OK; } @@ -247,7 +191,14 @@ int MCL_MicroDrive_ZStage::Shutdown() bool MCL_MicroDrive_ZStage::Busy() { - return busy_; + // Check if the thread is running + long ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) + { + return true; + } + + return false; } @@ -259,13 +210,24 @@ double MCL_MicroDrive_ZStage::GetStepSize() int MCL_MicroDrive_ZStage::SetPositionUm(double z) { - return SetPositionMm(z / 1000.0); + return BeginMovementThread(STANDARD_MOVE_TYPE, z / 1000.0); } int MCL_MicroDrive_ZStage::GetPositionUm(double& z) { - int err = GetPositionMm(z); + int err = DEVICE_OK; + + // Check if the thread is running + long ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) + { + z = lastZ_; + } + else + { + err = GetPositionMm(z); + } z *= 1000.0; return err; @@ -274,22 +236,34 @@ int MCL_MicroDrive_ZStage::GetPositionUm(double& z) int MCL_MicroDrive_ZStage::SetRelativePositionUm(double z) { - return SetRelativePositionMm(z/1000.0); + double absZ; + int err = ConvertRelativeToAbsoluteMm(z / 1000.0, absZ); + if (err != MCL_SUCCESS) + return err; + + err = BeginMovementThread(STANDARD_MOVE_TYPE, absZ); + + return err; } int MCL_MicroDrive_ZStage::SetPositionSteps(long z) { - return SetPositionMm(z * stepSize_mm_); + return BeginMovementThread(STANDARD_MOVE_TYPE, z* stepSize_mm_); } int MCL_MicroDrive_ZStage::GetPositionSteps(long& z) { - int err; + int err = DEVICE_OK; double getZ; - err = GetPositionMm(getZ); + // Check if the thread is running + long ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) + getZ = lastZ_; + else + err = GetPositionMm(getZ); z = (long) (getZ / stepSize_mm_); @@ -298,6 +272,18 @@ int MCL_MicroDrive_ZStage::GetPositionSteps(long& z) int MCL_MicroDrive_ZStage::SetOrigin() +{ + long ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) + { + return MCL_DEV_NOT_READY; + } + + return SetOriginSync(); +} + + +int MCL_MicroDrive_ZStage::SetOriginSync() { int err = MCL_SUCCESS; if (encoded_ && axis_ < M5AXIS) @@ -307,35 +293,41 @@ int MCL_MicroDrive_ZStage::SetOrigin() return err; } lastZ_ = 0; + return DEVICE_OK; } -int MCL_MicroDrive_ZStage::FindEpi() +int MCL_MicroDrive_ZStage::FindEpiSync() { if (axisIsTirfModule_ == false) return DEVICE_OK; + int err = MCL_SUCCESS; unsigned short status; unsigned short mask = LimitBitMask(pid_, axis_, REVERSE); MCL_MDStatus(&status, handle_); // Move the stage to its reverse limit. - while ((status & mask) == mask) + while ((status & mask) == mask && !stopCommanded_) { - SetRelativePositionMm(-.5); + err = SetRelativePositionMmSync(-.5); + if (err != DEVICE_OK) + return err; MCL_MDStatus(&status, handle_); } // Set the orgin at the reverse limit. - SetOrigin(); + SetOriginSync(); // Move the calibration distance to find epi. - SetPositionMm(tirfModCalibrationMm_); + err = SetPositionMmSync(tirfModCalibrationMm_); + if (err != DEVICE_OK) + return err; // Set the orgin at epi. - SetOrigin(); + SetOriginSync(); return DEVICE_OK; } @@ -378,8 +370,7 @@ int MCL_MicroDrive_ZStage::OnPositionMm(MM::PropertyBase* pProp, MM::ActionType err = GetPositionMm(z); if(err != MCL_SUCCESS) return err; - err = SetPositionMm(pos); - + err = BeginMovementThread(STANDARD_MOVE_TYPE, z); if (err != DEVICE_OK) return err; } @@ -394,7 +385,8 @@ int MCL_MicroDrive_ZStage::OnMovemm(MM::PropertyBase* pProp, MM::ActionType eAct if (eAct == MM::AfterSet) { pProp->Get(pos); - err = SetRelativePositionMm(pos); + + err = SetRelativePositionUm(pos * 1000.0); if (err != MCL_SUCCESS) return err; } @@ -444,7 +436,7 @@ int MCL_MicroDrive_ZStage::OnCalibrate(MM::PropertyBase* pProp, MM::ActionType e if (message.compare(g_Listword_Yes) == 0) { - err = Calibrate(); + err = BeginMovementThread(CALIBRATE_TYPE, 0.0); if (err != DEVICE_OK) return err; } @@ -469,7 +461,7 @@ int MCL_MicroDrive_ZStage::OnReturnToOrigin(MM::PropertyBase* pProp, MM::ActionT if (message.compare(g_Listword_Yes) == 0) { - err = ReturnToOrigin(); + err = BeginMovementThread(RETURN_TO_ORIGIN_TYPE, 0); if (err != DEVICE_OK) return err; } @@ -600,8 +592,8 @@ int MCL_MicroDrive_ZStage::OnFindEpi(MM::PropertyBase* pProp, MM::ActionType eAc pProp->Get(message); if (message.compare(g_Listword_Yes) == 0) - { - err = FindEpi(); + { + err = BeginMovementThread(FIND_EPI_TYPE, 0.0); if (err != DEVICE_OK) return err; } @@ -610,6 +602,24 @@ int MCL_MicroDrive_ZStage::OnFindEpi(MM::PropertyBase* pProp, MM::ActionType eAc } +int MCL_MicroDrive_ZStage::OnStop(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + int err = DEVICE_OK; + string input; + if (eAct == MM::AfterSet) + { + pProp->Get(input); + + if (input.compare(g_Keyword_Stop) == 0) + { + err = Stop(); + } + + } + return DEVICE_OK; +} + + int MCL_MicroDrive_ZStage::CreateZStageProperties() { int err; @@ -633,7 +643,19 @@ int MCL_MicroDrive_ZStage::CreateZStageProperties() // Device handle sprintf(iToChar, "%d", handle_); - err = CreateProperty("Handle", iToChar, MM::Integer, true); + err = CreateProperty("Handle", iToChar, MM::String, true); + if (err != DEVICE_OK) + return err; + + // Product ID + sprintf(iToChar, "%hu", pid_); + err = CreateProperty("Product ID", iToChar, MM::String, true); + if (err != DEVICE_OK) + return err; + + // Serial Number + sprintf(iToChar, "%d", serialNumber_); + err = CreateProperty("Serial number", iToChar, MM::String, true); if (err != DEVICE_OK) return err; @@ -657,6 +679,11 @@ int MCL_MicroDrive_ZStage::CreateZStageProperties() return err; } + // Wait Time + err = CreateProperty(g_Keyword_WaitTime, " ", MM::Float, false); + if (err != DEVICE_OK) + return err; + ///// Action properties // Change velocity @@ -696,6 +723,18 @@ int MCL_MicroDrive_ZStage::CreateZStageProperties() if (err != DEVICE_OK) return err; + // Stop + vector stopList; + stopList.push_back(" "); + stopList.push_back(g_Keyword_Stop); + pAct = new CPropertyAction(this, &MCL_MicroDrive_ZStage::OnStop); + err = CreateProperty(g_Keyword_Stop, " ", MM::String, false, pAct); + if (err != DEVICE_OK) + return err; + err = SetAllowedValues(g_Keyword_Stop, stopList); + if (err != DEVICE_OK) + return err; + // Return to origin pAct = new CPropertyAction(this, &MCL_MicroDrive_ZStage::OnReturnToOrigin); err = CreateProperty(g_Keyword_ReturnToOrigin, "No", MM::String, false, pAct); @@ -727,6 +766,7 @@ int MCL_MicroDrive_ZStage::CreateZStageProperties() if (err != DEVICE_OK) return err; + if (deviceHasTirfModuleAxis_) { // Axis is tirfModule @@ -756,12 +796,17 @@ int MCL_MicroDrive_ZStage::CreateZStageProperties() return DEVICE_OK; } -int MCL_MicroDrive_ZStage::SetPositionMm(double goalZ) + +int MCL_MicroDrive_ZStage::SetPositionMmSync(double goalZ) { - int err; + int err, waitTime; + char iToChar[10]; int currentRetries = 0; bool moveFinished = false; + if (stopCommanded_) + return DEVICE_OK; + //Calculate the absolute position. double zCurrent; err = GetPositionMm(zCurrent); @@ -785,9 +830,14 @@ int MCL_MicroDrive_ZStage::SetPositionMm(double goalZ) err = MCL_MDMove(axis_, velocity_, zMove, handle_); if (err != MCL_SUCCESS) return err; - busy_ = true; + + err = MCL_MicroDriveGetWaitTime(&waitTime, handle_); + if (err != MCL_SUCCESS) + return err; + sprintf(iToChar, "%d", waitTime); + SetProperty(g_Keyword_WaitTime, iToChar); + PauseDevice(); - busy_ = false; err = MCL_MDCurrentPositionM(axis_, &endingMicroSteps, handle_); if (err != MCL_SUCCESS) @@ -804,7 +854,7 @@ int MCL_MicroDrive_ZStage::SetPositionMm(double goalZ) if (err != MCL_SUCCESS) return err; - if(iterativeMoves_ && encoded_) + if(iterativeMoves_ && encoded_ && !stopCommanded_) { double absDiffUmZ = abs(goalZ - zCurrent) * 1000.0; bool zInTolerance = absDiffUmZ < imToleranceUm_; @@ -837,7 +887,7 @@ int MCL_MicroDrive_ZStage::SetPositionMm(double goalZ) int MCL_MicroDrive_ZStage::GetPositionMm(double& z) { - if (encoded_ && axis_ < M5AXIS) + if (encoded_ && (axis_ < M5AXIS)) { double tempM1, tempM2, tempM3, tempM4; int err = MCL_MDReadEncoders(&tempM1, &tempM2, &tempM3, &tempM4, handle_); @@ -867,7 +917,7 @@ int MCL_MicroDrive_ZStage::GetPositionMm(double& z) } -int MCL_MicroDrive_ZStage::SetRelativePositionMm(double z) +int MCL_MicroDrive_ZStage::ConvertRelativeToAbsoluteMm(double relZ, double& absZ) { int err; @@ -877,13 +927,26 @@ int MCL_MicroDrive_ZStage::SetRelativePositionMm(double z) if (err != MCL_SUCCESS) return err; - double zGoal = zCurrent + z; + absZ = zCurrent + relZ; + + return MCL_SUCCESS; +} + + +int MCL_MicroDrive_ZStage::SetRelativePositionMmSync(double z) +{ + double absZ; + int err = ConvertRelativeToAbsoluteMm(z, absZ); + if (err != MCL_SUCCESS) + return err; + + err = SetPositionMmSync(absZ); - return SetPositionMm(zGoal); + return err; } -int MCL_MicroDrive_ZStage::Calibrate() +int MCL_MicroDrive_ZStage::CalibrateSync() { int err; double zPosOrig; @@ -893,7 +956,7 @@ int MCL_MicroDrive_ZStage::Calibrate() if (err != MCL_SUCCESS) return err; - err = MoveToForwardLimit(); + err = MoveToForwardLimitSync(); if (err != DEVICE_OK) return err; @@ -901,11 +964,11 @@ int MCL_MicroDrive_ZStage::Calibrate() if (err != MCL_SUCCESS) return err; - err = SetOrigin(); + err = SetOriginSync(); if (err != DEVICE_OK) return err; - err = SetPositionMm((zPosOrig - zPosLimit)); + err = SetPositionMmSync((zPosOrig - zPosLimit)); if (err != DEVICE_OK) return err; @@ -913,7 +976,7 @@ int MCL_MicroDrive_ZStage::Calibrate() } -int MCL_MicroDrive_ZStage::MoveToForwardLimit() +int MCL_MicroDrive_ZStage::MoveToForwardLimitSync() { int err; unsigned short status = 0; @@ -923,7 +986,7 @@ int MCL_MicroDrive_ZStage::MoveToForwardLimit() return err; unsigned short bitMask = LimitBitMask(pid_, axis_, FORWARD); - while ((status & bitMask) != 0) + while ((status & bitMask) != 0 && !stopCommanded_) { err = SetRelativePositionUm(4000); if (err != DEVICE_OK) @@ -937,9 +1000,9 @@ int MCL_MicroDrive_ZStage::MoveToForwardLimit() } -int MCL_MicroDrive_ZStage::ReturnToOrigin() +int MCL_MicroDrive_ZStage::ReturnToOriginSync() { - return SetPositionMm(0.0); + return SetPositionMmSync(0.0); } @@ -949,65 +1012,66 @@ void MCL_MicroDrive_ZStage::PauseDevice() } -// The handle list must be locked when calling this function. -int MCL_MicroDrive_ZStage::ChooseAvailableStageAxis(unsigned short pid, unsigned char axisBitmap, int handle) +int MCL_MicroDrive_ZStage::Stop() { - int ordersize = 6; - int order[] = { M1AXIS, M2AXIS, M3AXIS, M4AXIS, M5AXIS, M6AXIS }; + int err = MCL_MDStop(NULL, handle_); + stopCommanded_ = true; + if (err != MCL_SUCCESS) + return err; + + return DEVICE_OK; +} + - switch (pid) +int MCL_MicroDrive_ZStage::BeginMovementThread(int type, double distance) +{ + long ret = WaitForSingleObject(threadStartMutex_, 0); + if (ret == WAIT_TIMEOUT) { - // These devices should be used as XY Stage devices. - case MICRODRIVE: - case NC_MICRODRIVE: - return 0; - case MICRODRIVE3: - { - int neworder[] = { M3AXIS, M2AXIS, M1AXIS, 0, 0, 0 }; - copy(neworder, neworder + ordersize, order); - break; - } - // For 4 and 6 axis systems leave M1/M2 for an XY Stage. - case MICRODRIVE4: - { - int neworder[] = { M3AXIS, M4AXIS, 0, 0, 0, 0 }; - copy(neworder, neworder + ordersize, order); - break; - } - case MICRODRIVE6: - { - int neworder[] = { M3AXIS, M6AXIS, M4AXIS, M5AXIS, 0, 0 }; - copy(neworder, neworder + ordersize, order); - break; - } - case MICRODRIVE1: - { - int neworder[] = { M1AXIS, M2AXIS, M3AXIS, 0, 0, 0 }; - copy(neworder, neworder + ordersize, order); - break; - } - // Use the standard order. - default: - break; + return MCL_DEV_NOT_READY; } - int axis = 0; - for (int ii = 0; ii < ordersize; ii++) + // Check if the thread is running + ret = WaitForSingleObject(movementThread_, 0); + if (ret == WAIT_TIMEOUT) { - if (order[ii] == 0) - break; + ReleaseMutex(threadStartMutex_); + return MCL_DEV_NOT_READY; + } - // Check that the axis is valid. - int bitmap = 0x1 << (order[ii] - 1); - if ((axisBitmap & bitmap) != bitmap) - continue; + // Create a new thread for our action + movementType_ = type; + movementDistance_ = distance; + movementThread_ = CreateThread(0, 0, ExecuteMovement, this, 0, 0); - HandleListType device(handle, STAGE_TYPE, order[ii], 0); - if (HandleExistsOnLockedList(device) == false) - { - axis = order[ii]; - break; - } + ReleaseMutex(threadStartMutex_); + + return DEVICE_OK; +} + + +DWORD WINAPI MCL_MicroDrive_ZStage::ExecuteMovement(LPVOID lpParam) { + + MCL_MicroDrive_ZStage* instance = reinterpret_cast(lpParam); + instance->stopCommanded_ = false; + switch (instance->movementType_) + { + case STANDARD_MOVE_TYPE: + instance->SetPositionMmSync(instance->movementDistance_); + break; + case CALIBRATE_TYPE: + instance->CalibrateSync(); + break; + case HOME_TYPE: + instance->MoveToForwardLimitSync(); + break; + case RETURN_TO_ORIGIN_TYPE: + instance->ReturnToOriginSync(); + break; + case FIND_EPI_TYPE: + instance->FindEpiSync(); + break; } - return axis; + + return 0; } \ No newline at end of file diff --git a/DeviceAdapters/MCL_MicroDrive/MicroDriveZStage.h b/DeviceAdapters/MCL_MicroDrive/MicroDriveZStage.h index 27624af91..93628438d 100644 --- a/DeviceAdapters/MCL_MicroDrive/MicroDriveZStage.h +++ b/DeviceAdapters/MCL_MicroDrive/MicroDriveZStage.h @@ -1,6 +1,6 @@ /* File: MicroDriveZStage.h -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #pragma once @@ -23,6 +23,9 @@ License: Distributed under the BSD license. #define ERR_UNKNOWN_POSITION 103 #define ERR_NOT_VALID_INPUT 104 +#define STANDARD_MOVE_TYPE 1 +#define CALIBRATE_TYPE 2 + class MCL_MicroDrive_ZStage : public CStageBase { public: @@ -64,38 +67,46 @@ class MCL_MicroDrive_ZStage : public CStageBase int OnImToleranceUm(MM::PropertyBase* pProp, MM::ActionType eAct); int OnIsTirfModule(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFindEpi(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnStop(MM::PropertyBase* pProp, MM::ActionType eAct); private: // Initialization int CreateZStageProperties(); + int InitDeviceAdapter(); // Set/Get positions - int SetPositionMm(double z); + int SetPositionMmSync(double z); int GetPositionMm(double& z); - int SetRelativePositionMm(double z); + int SetRelativePositionMmSync(double z); + int ConvertRelativeToAbsoluteMm(double relZ, double &absZ); // Calibration & origin methods - int Calibrate(); - int MoveToForwardLimit(); - int ReturnToOrigin(); - int FindEpi(); + int CalibrateSync(); + int MoveToForwardLimitSync(); + int ReturnToOriginSync(); + int FindEpiSync(); + int SetOriginSync(); void PauseDevice(); - int ChooseAvailableStageAxis(unsigned short pid, unsigned char axisBitmap, int handle); + int Stop(); + + // Threading + int BeginMovementThread(int type, double distance); + static DWORD WINAPI ExecuteMovement(LPVOID lpParam); // Device Information int handle_; int serialNumber_; unsigned short pid_; int axis_; + unsigned char axisBitmap_; double stepSize_mm_; double encoderResolution_; double maxVelocity_; double minVelocity_; double velocity_; // Device State - bool busy_; bool initialized_; bool encoded_; double lastZ_; @@ -108,4 +119,10 @@ class MCL_MicroDrive_ZStage : public CStageBase bool axisIsTirfModule_; bool hasUnknownTirfModuleAxis_; double tirfModCalibrationMm_; + // Threading + bool stopCommanded_; + int movementType_; + double movementDistance_; + HANDLE movementThread_; + HANDLE threadStartMutex_; }; diff --git a/DeviceAdapters/MCL_NanoDrive/MCL_Common.cpp b/DeviceAdapters/MCL_NanoDrive/MCL_Common.cpp index 9c13072a5..472c3793c 100644 --- a/DeviceAdapters/MCL_NanoDrive/MCL_Common.cpp +++ b/DeviceAdapters/MCL_NanoDrive/MCL_Common.cpp @@ -1,3 +1,9 @@ +/* +File: MCL_Common.cpp +Copyright: Mad City Labs Inc., 2023 +License: Distributed under the BSD license. +*/ + // MCL headers #include "Madlib.h" #include "MCL_Common.h" @@ -94,6 +100,7 @@ bool FindMatchingDeviceInList(int deviceAdapterType, int *handles, int handlesCo if (deviceAdapterAxis != 0) { deviceAdapterHandle = handles[ii]; + break; } } return deviceAdapterAxis != 0; diff --git a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.h b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.h index 93f8a0c04..cc1327308 100644 --- a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.h +++ b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.h @@ -1,6 +1,6 @@ /* File: MCL_NanoDrive.h -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #pragma once @@ -47,6 +47,7 @@ static const char* g_Keyword_Calibration = "Calibration"; static const char* g_Keyword_CalibrationX = "Calibration X"; static const char* g_Keyword_CalibrationY = "Calibration Y"; static const char* g_Keyword_SerialNumber = "Serial Number"; +static const char* g_Keyword_ProductID = "Product ID"; static const char* g_Keyword_DacSteps = "DacBits"; static const char* g_Keyword_DeviceAxisInUse = "Device axis in use"; static const char* g_Keyword_LowerLimit = "Lower Limit (um)"; diff --git a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_XYStage.cpp b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_XYStage.cpp index 5273e851b..310832435 100644 --- a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_XYStage.cpp +++ b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_XYStage.cpp @@ -1,6 +1,6 @@ /* File: MCL_NanoDrive_XYStage.cpp -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #include "MCL_Common.h" @@ -520,6 +520,8 @@ int MCL_NanoDrive_XYStage::InitDeviceAdapter() } supportsLastCommanded_ = (pi.FirmwareProfile & PROFILE_BIT_SUPPORTS_LASTCOMMANDED) != 0; + productID_ = pi.Product_id; + int err = CreateDeviceProperties(); if (err != DEVICE_OK) return err; @@ -567,11 +569,18 @@ int MCL_NanoDrive_XYStage::CreateDeviceProperties() // Device serial number (read-only) memset(valueBuffer, 0, valueBufferSize); - sprintf_s(valueBuffer, valueBufferSize, "%d", MCL_GetSerialNumber(handle_)); + sprintf_s(valueBuffer, valueBufferSize, "%d", serialNumber_); err = CreateProperty(g_Keyword_SerialNumber, valueBuffer, MM::String, true); if (err != DEVICE_OK) return err; + // Product ID (read-only) + memset(valueBuffer, 0, valueBufferSize); + sprintf_s(valueBuffer, valueBufferSize, "%d", productID_); + err = CreateProperty(g_Keyword_ProductID, valueBuffer, MM::String, true); + if (err != DEVICE_OK) + return err; + // Calibration value for X axis (read-only) memset(valueBuffer, 0, valueBufferSize); sprintf_s(valueBuffer, valueBufferSize, "%f", calibrationX_); diff --git a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_XYStage.h b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_XYStage.h index 01a03b7af..540404b0e 100644 --- a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_XYStage.h +++ b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_XYStage.h @@ -1,6 +1,6 @@ /* File: MCL_NanoDrive_XYStage.h -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #pragma once @@ -71,6 +71,7 @@ class MCL_NanoDrive_XYStage : public CXYStageBase int handle_; int serialNumber_; + int productID_; int axisX_; int axisY_; double calibrationX_; diff --git a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_ZStage.cpp b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_ZStage.cpp index f18cb2b7b..b63bdb6fa 100644 --- a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_ZStage.cpp +++ b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_ZStage.cpp @@ -1,6 +1,6 @@ /* File: MCL_NanoDrive_ZStage.cpp -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #include "MCL_Common.h" @@ -10,6 +10,13 @@ License: Distributed under the BSD license. #include MCL_NanoDrive_ZStage::MCL_NanoDrive_ZStage() : + axis_(0), + calibration_(0.0), + dacBits_(0), + lowerLimit_(0.0), + upperLimit_(0.0), + serialNumber_(0), + stepSize_um_(0.0), axisUsedForTirfControl_(false), canSupportSeq_(false), commandedZ_(0), @@ -645,6 +652,8 @@ int MCL_NanoDrive_ZStage::InitDeviceAdapter() } supportsLastCommanded_ = (pi.FirmwareProfile & PROFILE_BIT_SUPPORTS_LASTCOMMANDED) != 0; + productID_ = pi.Product_id; + int err = CreateZStageProperties(); if (err != DEVICE_OK) return err; @@ -693,11 +702,18 @@ int MCL_NanoDrive_ZStage::CreateZStageProperties() // Device serial number (read-only) memset(valueBuffer, 0, valueBufferSize); - sprintf_s(valueBuffer, valueBufferSize, "%d", MCL_GetSerialNumber(handle_)); + sprintf_s(valueBuffer, valueBufferSize, "%d",serialNumber_); err = CreateProperty(g_Keyword_SerialNumber, valueBuffer, MM::String, true); if (err != DEVICE_OK) return err; + // Product ID (read-only) + memset(valueBuffer, 0, valueBufferSize); + sprintf_s(valueBuffer, valueBufferSize, "%d", productID_); + err = CreateProperty(g_Keyword_ProductID, valueBuffer, MM::String, true); + if (err != DEVICE_OK) + return err; + // Look at the calibration value (read-only) memset(valueBuffer, 0, valueBufferSize); sprintf_s(valueBuffer, valueBufferSize, "%f",calibration_); diff --git a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_ZStage.h b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_ZStage.h index 69c03c85e..a54ab54e7 100644 --- a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_ZStage.h +++ b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive_ZStage.h @@ -1,6 +1,6 @@ /* File: MCL_NanoDrive_ZStage.h -Copyright: Mad City Labs Inc., 2019 +Copyright: Mad City Labs Inc., 2023 License: Distributed under the BSD license. */ #pragma once @@ -71,6 +71,7 @@ class MCL_NanoDrive_ZStage : public CStageBase int handle_; int serialNumber_; + int productID_; int axis_; double calibration_; int dacBits_; From c3a57c4a4dd763d980c8da94c60290c59c625579 Mon Sep 17 00:00:00 2001 From: fandayu <57091941+fandayu@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:57:08 +0800 Subject: [PATCH 116/141] Update MMTUCam.cpp support aries 16 lt/fl 26bw/fl 9bw --- DeviceAdapters/TUCam/MMTUCam.cpp | 1556 ++++++++++++++++++++++++++---- 1 file changed, 1390 insertions(+), 166 deletions(-) diff --git a/DeviceAdapters/TUCam/MMTUCam.cpp b/DeviceAdapters/TUCam/MMTUCam.cpp index b54ca7cd1..f55059d08 100755 --- a/DeviceAdapters/TUCam/MMTUCam.cpp +++ b/DeviceAdapters/TUCam/MMTUCam.cpp @@ -8,9 +8,9 @@ // microscope devices and enables testing of the rest of the // system without the need to connect to the actual hardware. // -// AUTHOR: fandayu, fandayu@tucsen.com 2022 +// AUTHOR: fandayu, fandayu@tucsen.com 2024 // -// COPYRIGHT: Tucsen Photonics Co., Ltd., 2022 +// COPYRIGHT: Tucsen Photonics Co., Ltd., 2024 // LICENSE: This file is distributed under the BSD license. // License text is included with the source distribution. // @@ -70,11 +70,14 @@ const char* g_PropNameLED = "LEDMode"; const char* g_PropNameTEC = "TECMode"; const char* g_PropNamePI = "PIMode"; const char* g_PropNameDepth = "DepthMode"; +const char* g_PropNameShutter = "Shutter Mode"; const char* g_PropNameMdTgr = "Trigger Mode"; const char* g_PropNameMdExp = "Trigger Exposure Mode"; const char* g_PropNameMdEdg = "Trigger Edge Mode"; const char* g_PropNameMdDly = "Trigger Delay"; +const char* g_PropNameFilter= "Signal Filter"; const char* g_PropNameMdFrames = "Trigger Frames"; +const char* g_PropNameMdTFrames = "Trigger Total Frames"; const char* g_PropNameDoSFW = "Trigger Software Do"; const char* g_PropNameSharp = "Image Adjustment Sharpness"; const char* g_PropNameDPC = "Image Adjustment DPC"; @@ -97,6 +100,13 @@ const char* g_PropNameFrameRate = "Frame Rate"; const char* g_PropNameTestImg = "Test Image"; +const char* g_PropNameBrightness = "Targeting Level"; //"Brightness"; +const char* g_PropNamePixelRatio = "Metering Level"; //"Pixel Ratio"; +const char* g_PropNameImgMetadata= "Image Metadata"; +const char* g_PropNameATExpMode = "ATExposure Mode"; + +const char* g_PropNameBinningSum = "Binning Sum"; + const char* g_DeviceName = "Dhyana"; //"400Li" const char* g_SdkName = "TUCam"; //const char* g_SdkName = "CamCore"; @@ -148,6 +158,8 @@ const char* g_GLOBALRESET_ON = "Global Reset"; /// GlobalReset Hg const char* g_TRIGGER_OFF = "Off"; const char* g_TRIGGER_STD = "Standard"; +const char* g_TRIGGER_STDOVERLAP = "Standard(Overlap)"; +const char* g_TRIGGER_STDNONOVERLAP = "Standard(Non-Overlap)"; const char* g_TRIGGER_CC1 = "CC1"; const char* g_TRIGGER_SYN = "Synchronization"; const char* g_TRIGGER_GLB = "Global"; @@ -160,6 +172,7 @@ const char* g_TRIGGER_PORT3 = "3"; const char* g_TRIGGER_EXPSTART = "Exposure Start"; const char* g_TRIGGER_READEND = "Readout End"; const char* g_TRIGGER_GLBEXP = "Global Exposure"; +const char* g_TRIGGER_TRIREADY = "Trigger Ready"; const char* g_TRIGGER_LOW = "Low"; const char* g_TRIGGER_HIGH = "High"; @@ -292,6 +305,7 @@ CMMTUCam::CMMTUCam() : m_frame.ucFormatGet = TUFRM_FMT_USUAl; m_frame.pBuffer = NULL; + m_nZeroTemp = 50; m_nPID = 0; m_nBCD = 0; m_nIdxGain = 0; @@ -454,6 +468,13 @@ int CMMTUCam::Initialize() m_nBCD = valInfo.nValue; } + // Get Zero Temperature Value + valInfo.nID = TUIDI_ZEROTEMPERATURE_VALUE; + if (TUCAMRET_SUCCESS == TUCAM_Dev_GetInfo(m_opCam.hIdxTUCam, &valInfo)) + { + m_nZeroTemp = valInfo.nValue; + } + // Get camera type valInfo.nID = TUIDI_PRODUCT; if (TUCAMRET_SUCCESS == TUCAM_Dev_GetInfo(m_opCam.hIdxTUCam, &valInfo)) @@ -477,7 +498,7 @@ int CMMTUCam::Initialize() m_bTriEn = false; } - if (PID_FL_20BW == m_nPID || DHYANA_4040V2 == m_nPID || DHYANA_4040BSI == m_nPID || DHYANA_XF4040BSI == m_nPID) + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID || PID_FL_20BW == m_nPID || DHYANA_4040V2 == m_nPID || DHYANA_4040BSI == m_nPID || DHYANA_XF4040BSI == m_nPID) { m_bOffsetEn = true; } @@ -519,16 +540,30 @@ int CMMTUCam::Initialize() if (nRet != DEVICE_OK) return nRet; + if (PID_FL_26BW == m_nPID) + { + // binning sum + pAct = new CPropertyAction(this, &CMMTUCam::OnBinningSum); + nRet = CreateProperty(g_PropNameBinningSum, "", MM::String, false, pAct); + assert(nRet == DEVICE_OK); + + nRet = SetAllowedBinningSum(); + if (nRet != DEVICE_OK) + return nRet; + } + // Bit depth capaAttr.idCapa = TUIDC_BITOFDEPTH; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) { if(capaAttr.nValMax != capaAttr.nValMin && m_nPID != DHYANA_D95_X100) { - if (DHYANA_400DC_X100 == m_nPID || DHYANA_400DC_X45 == m_nPID) + if (capaAttr.nValMax > 8) { - TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_BITOFDEPTH, 8); - } + if (DHYANA_400DC_X100 == m_nPID || DHYANA_400DC_X45 == m_nPID) + { + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_BITOFDEPTH, 8); + } pAct = new CPropertyAction (this, &CMMTUCam::OnBitDepth); nRet = CreateProperty(g_PropNameBODP, "8", MM::String, false, pAct); @@ -538,9 +573,18 @@ int CMMTUCam::Initialize() bitDepths.push_back("8"); bitDepths.push_back("16"); - nRet = SetAllowedValues(g_PropNameBODP, bitDepths); - if (nRet != DEVICE_OK) - return nRet; + nRet = SetAllowedValues(g_PropNameBODP, bitDepths); + if (nRet != DEVICE_OK) + return nRet; + } + else + { + pAct = new CPropertyAction(this, &CMMTUCam::OnBitDepthEum); + nRet = CreateProperty(g_PropNameBODP, "", MM::String, false, pAct); + SetAllowedDepth(); + if (nRet != DEVICE_OK) + return nRet; + } } } @@ -569,13 +613,30 @@ int CMMTUCam::Initialize() assert(nRet == DEVICE_OK); UpdateExpRange(); - /*exposureMaximum_ = propAttr.dbValMax; - exposureMinimum_ = propAttr.dbValMin; - if(m_nPID == PID_FL_20BW) - { - exposureMaximum_ = 3600000; - } - SetPropertyLimits(MM::g_Keyword_Exposure, exposureMinimum_, exposureMaximum_);*/ + } + + // Brightness + propAttr.nIdxChn = 0; + propAttr.idProp = TUIDP_BRIGHTNESS; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnBrightness); + nRet = CreateProperty(g_PropNameBrightness, "0", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + + SetPropertyLimits(g_PropNameBrightness, propAttr.dbValMin, propAttr.dbValMax); + } + + // PixelRatio + propAttr.nIdxChn = 0; + propAttr.idProp = TUIDP_PIXELRATIO; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnPixelRatio); + nRet = CreateProperty(g_PropNamePixelRatio, "0", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + + SetPropertyLimits(g_PropNamePixelRatio, propAttr.dbValMin, propAttr.dbValMax); } // Global Gain @@ -593,9 +654,38 @@ int CMMTUCam::Initialize() } else { - nRet = SetAllowedGainMode(); - if (nRet != DEVICE_OK) - return nRet; + if (m_nPID == PID_FL_9BW || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID || IsSupportAries16()) + { + int nCnt = (int)(propAttr.dbValMax - propAttr.dbValMin + 1); + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + valText.nID = TUIDP_GLOBALGAIN; + + vector gainValues; + + for (int i = 0; i StampValues; + StampValues.push_back("FALSE"); + StampValues.push_back("TRUE"); + + nRet = SetAllowedValues(g_PropNameImgMetadata, StampValues); + if (nRet != DEVICE_OK) + return nRet; + } + } + // Sensor reset capaAttr.idCapa = TUIDC_SENSORRESET; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) @@ -619,6 +729,17 @@ int CMMTUCam::Initialize() AddAllowedValue(g_PropNameReset, g_PropNameReset); } + // Auto Exposure Mode + capaAttr.idCapa = TUIDC_ATEXPOSURE_MODE; + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnATExpMode); + nRet = CreateProperty(g_PropNameATExpMode, "0", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + + SetPropertyLimits(g_PropNameATExpMode, capaAttr.nValMin, capaAttr.nValMax); + } + // Auto Exposure capaAttr.idCapa = TUIDC_ATEXPOSURE; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) @@ -669,7 +790,18 @@ int CMMTUCam::Initialize() if (nRet != DEVICE_OK) return nRet; } - + + // Shutter + if (PID_FL_26BW == m_nPID) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnShutterMode); + nRet = CreateProperty(g_PropNameShutter, "", MM::String, false, pAct); + assert(nRet == DEVICE_OK); + + nRet = SetAllowedShutterMode(); + if (nRet != DEVICE_OK) + return nRet; + } // Gamma propAttr.nIdxChn= 0; @@ -796,12 +928,30 @@ int CMMTUCam::Initialize() if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr) && m_bTempEn) // For 401D 201D disable temperature { pAct = new CPropertyAction (this, &CMMTUCam::OnTemperature); - nRet = CreateProperty(g_PropNameTEMP, "0", MM::Integer, false, pAct); - m_nMidTemp = (int)((propAttr.dbValMax - propAttr.dbValMin) / 2); - SetPropertyLimits(g_PropNameTEMP, -m_nMidTemp, m_nMidTemp); - char sz[64] = { 0 }; + if (propAttr.dbValMax > 100) + { + m_fScaTemp = 10; + nRet = CreateProperty(g_PropNameTEMP, "0.0", MM::Float, false, pAct); + } + else + { + m_fScaTemp = 1; + nRet = CreateProperty(g_PropNameTEMP, "0", MM::Integer, false, pAct); + } + m_nMidTemp = m_nZeroTemp; + if (PID_FL_9BW_LT == m_nPID) + { + ///m_nMidTemp = 500; + SetPropertyLimits(g_PropNameTEMP, (propAttr.dbValMin - m_nMidTemp) / m_fScaTemp, (propAttr.dbValMax - m_nMidTemp) / m_fScaTemp); + } + else + { + SetPropertyLimits(g_PropNameTEMP, -m_nMidTemp / m_fScaTemp, m_nMidTemp / m_fScaTemp); + } + + char sz[64] = { 0 }; switch (m_nPID) { case PID_FL_20BW: @@ -812,8 +962,8 @@ int CMMTUCam::Initialize() SetProperty(g_PropNameTEMP, sz); } break; - case DHYANA_400BSIV2: - case DHYANA_400BSIV3: +// case DHYANA_400BSIV2: +// case DHYANA_400BSIV3: case DHYANA_D95_V2: case DHYANA_4040V2: case DHYANA_4040BSI: @@ -824,11 +974,21 @@ int CMMTUCam::Initialize() break; default: // Set default temperature - if (TUCAMRET_SUCCESS == TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, 40.0f)) - { - sprintf(sz, "%d", -10); - SetProperty(g_PropNameTEMP, sz); - } + double dblTemp; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE_TARGET, &dblTemp)) + { + sprintf(sz, "%.1f", (dblTemp - m_nMidTemp) / m_fScaTemp); + SetProperty(g_PropNameTEMP, sz); + } + else + { + if (TUCAMRET_SUCCESS == TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, 40.0f)) + { + sprintf(sz, "%d", -10); + SetProperty(g_PropNameTEMP, sz); + } + } + break; } @@ -875,11 +1035,17 @@ int CMMTUCam::Initialize() capaAttr.idCapa = TUIDC_IMGMODESELECT; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) { - int nImgMode = capaAttr.nValMax - capaAttr.nValMin +1; - - if((nImgMode < 0x3) && (capaAttr.nValMax < 0x02)) - { - pAct = new CPropertyAction (this, &CMMTUCam::OnCMSMode); + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) + { + nRet = SetAllowedGainMode(); + } + else + { + int nImgMode = capaAttr.nValMax - capaAttr.nValMin + 1; + + if ((nImgMode < 0x3) && (capaAttr.nValMax < 0x02) && PID_FL_9BW != m_nPID && PID_FL_9BW_LT != m_nPID) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnCMSMode); nRet = CreateProperty(g_PropNameCMS, g_CMS_ON, MM::String, false, pAct); assert(nRet == DEVICE_OK); @@ -888,9 +1054,11 @@ int CMMTUCam::Initialize() CMSValues.push_back(g_CMS_OFF); CMSValues.push_back(g_CMS_ON); - nRet = SetAllowedValues(g_PropNameCMS, CMSValues); + nRet = SetAllowedValues(g_PropNameCMS, CMSValues); + + } + } - } if (nRet != DEVICE_OK) return nRet; } @@ -1055,17 +1223,31 @@ int CMMTUCam::Initialize() vectorModTgrValues; ModTgrValues.push_back(g_TRIGGER_OFF); - ModTgrValues.push_back(g_TRIGGER_STD); + + if (IsSupport95V2New() || IsSupport401DNew() || IsSupport400BSIV3New()) + { + ModTgrValues.push_back(g_TRIGGER_STDOVERLAP); + ModTgrValues.push_back(g_TRIGGER_STDNONOVERLAP); + } + else + { + ModTgrValues.push_back(g_TRIGGER_STD); + } int nImgMode = 0; switch (m_nPID) { + case PID_FL_9BW: + case PID_FL_9BW_LT: + case PID_FL_26BW: case PID_FL_20BW: case DHYANA_401D: case DHYANA_201D: case DHYANA_4040V2: case DHYANA_4040BSI: case DHYANA_XF4040BSI: + case PID_ARIES16LT: + case PID_ARIES16: { if (TU_PHXCAMERALINK == m_nDriverType) { @@ -1130,7 +1312,25 @@ int CMMTUCam::Initialize() nRet = CreateProperty(g_PropNameMdDly, "0", MM::Integer, false, pAct); assert(nRet == DEVICE_OK); - SetPropertyLimits(g_PropNameMdDly, 0, 10000000); + // Trigger Filter + capaAttr.idCapa = TUIDC_SIGNALFILTER; + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnTriggerFilter); + nRet = CreateProperty(g_PropNameFilter, "0", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + SetPropertyLimits(g_PropNameFilter, 1, 1000000); + } + + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID) + { + // Trigger total frames + pAct = new CPropertyAction(this, &CMMTUCam::OnTriggerTotalFrames); + nRet = CreateProperty(g_PropNameMdTFrames, "1", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + } + + SetPropertyLimits(g_PropNameMdTFrames, 1, 0xFFFF); if (TUCAMRET_SUCCESS == TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_ROLLINGSCANMODE, &m_rsPara.nMode)) { @@ -1139,7 +1339,7 @@ int CMMTUCam::Initialize() nRet = CreateProperty(g_PropNameMdFrames, "1", MM::Integer, false, pAct); assert(nRet == DEVICE_OK); - SetPropertyLimits(g_PropNameMdFrames, 1, 65535); + SetPropertyLimits(g_PropNameMdFrames, 1, 0xFFFF); } } } @@ -1324,6 +1524,10 @@ int CMMTUCam::Initialize() ModKindValues.push_back(g_TRIGGER_EXPSTART); ModKindValues.push_back(g_TRIGGER_READEND); ModKindValues.push_back(g_TRIGGER_GLBEXP); + if (IsSupport95V2New() || IsSupport401DNew() || IsSupport400BSIV3New()) + { + ModKindValues.push_back(g_TRIGGER_TRIREADY); + } ModKindValues.push_back(g_TRIGGER_LOW); ModKindValues.push_back(g_TRIGGER_HIGH); @@ -1524,10 +1728,12 @@ int CMMTUCam::SnapImage() StartCapture(); } + int nRet = DEVICE_ERR; + if (TUCCM_TRIGGER_SOFTWARE == m_tgrAttr.nTgrMode) { int nCnt = 0; - int nRet = DEVICE_ERR; + ///int nRet = DEVICE_ERR; do { @@ -1546,7 +1752,7 @@ int CMMTUCam::SnapImage() CDeviceUtils::SleepMs((long) exp); } ///TUDBG_PRINTF("SanpImage fastImage_ = %d",fastImage_); - WaitForFrame(img_); + nRet = WaitForFrame(img_); } } @@ -1557,7 +1763,7 @@ int CMMTUCam::SnapImage() StopCapture(); } - return DEVICE_OK; + return nRet/*DEVICE_OK*/; } @@ -1730,8 +1936,7 @@ int CMMTUCam::SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) roiY_ = y; m_bROI = true; - - StartCapture(); +// StartCapture(); ResizeImageBuffer(); Sleep(2); @@ -1782,7 +1987,7 @@ int CMMTUCam::ClearROI() roiY_ = 0; m_bROI = false; - StartCapture(); +// StartCapture(); ResizeImageBuffer(); return DEVICE_OK; @@ -2001,6 +2206,39 @@ int CMMTUCam::LineIntervalCal(int nVal, bool bExpChange) return nStep; } +int CMMTUCam::SetAllowedDepth() +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_BITOFDEPTH; + + if (TUCAMRET_SUCCESS != TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + return DEVICE_NOT_SUPPORTED; + } + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_BITOFDEPTH; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + vector depthValues; + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (int i = 0; i binValues; + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (int i = 0; i modeValues; + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (int i = 0; i shutters; + + for (int i = 0; iIsStopped() && !returnToSoftwareTriggers_) - return DEVICE_OK; - + TUDBG_PRINTF("[StopSequenceAcquisition]:Enter \n"); + if (thd_->IsStopped() /*&& !returnToSoftwareTriggers_*/) + { + if (m_bLiving) + { + m_bAcquisition = false; + StopCapture(); + } + return DEVICE_OK; + } + m_bLiving = false; if (NULL == m_opCam.hIdxTUCam) @@ -2423,13 +2784,13 @@ int CMMTUCam::StopSequenceAcquisition() ReleaseBuffer(); // Switch back to software trigger mode if that is what the user used - if (returnToSoftwareTriggers_) + /*if (returnToSoftwareTriggers_) { TUCAM_Cap_GetTrigger(m_opCam.hIdxTUCam, &m_tgrAttr); m_tgrAttr.nTgrMode = TUCCM_TRIGGER_SOFTWARE; TUCAM_Cap_SetTrigger(m_opCam.hIdxTUCam, m_tgrAttr); returnToSoftwareTriggers_ = false; - } + }*/ m_bAcquisition = false; ///StartCapture(); // avoid wasting time in the SnapImage function @@ -2546,18 +2907,22 @@ int CMMTUCam::RunSequenceOnThread(MM::MMTime startTime) } ret = WaitForFrame(img_); + /* if (!fastImage_) { GenerateSyntheticImage(img_, GetSequenceExposure()); } */ - ret = InsertImage(); - - while (((double) (this->GetCurrentMMTime() - startTime).getMsec() / imageCounter_) < this->GetSequenceExposure()) + if (DEVICE_OK == ret) + { + ret = InsertImage(); + } + + /* while (((double) (this->GetCurrentMMTime() - startTime).getMsec() / imageCounter_) < this->GetSequenceExposure()) { CDeviceUtils::SleepMs(1); - } + }*/ if (ret != DEVICE_OK) { @@ -2730,9 +3095,10 @@ int CMMTUCam::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) valText.nTextSize = 64; valText.pText = &szBuf[0]; + int i = 0; int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; - for (int i=0; iSet(valText.pText); + if (nIdx > 0) + { + valText.dbValue = nIdx; + valText.nID = TUIDC_BINNING_SUM; + + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + pProp->Set(valText.pText); + } + else + { + valText.dbValue = 0; + valText.nID = TUIDC_RESOLUTION; + + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + pProp->Set(valText.pText); + } + } + else + { + int nIdx = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_RESOLUTION, &nIdx); + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_RESOLUTION; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + valText.dbValue = nIdx; + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + pProp->Set(valText.pText); + } + ret = DEVICE_OK; } break; @@ -2785,6 +3230,88 @@ int CMMTUCam::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "BinningSum" property. +*/ +int CMMTUCam::OnBinningSum(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + string val; + pProp->Get(val); + + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_BINNING_SUM; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_BINNING_SUM; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int nCnt = (int)(capaAttr.nValMax - capaAttr.nValMin + 1); + + for (int i = 0; iSet(valText.pText); + + ret = DEVICE_OK; + } + break; + default: + break; + } + return ret; +} + /** * Handles "PixelClock" property. */ @@ -2912,6 +3439,84 @@ int CMMTUCam::OnExposure(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "Brightness" property. +*/ +int CMMTUCam::OnBrightness(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + double dbVal = 0.0f; + pProp->Get(dbVal); + + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_BRIGHTNESS, dbVal); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + double dbVal = 0.0f; + + TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_BRIGHTNESS, &dbVal); + + pProp->Set(dbVal); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + +/** +* Handles "OnPixelRatio" property. +*/ +int CMMTUCam::OnPixelRatio(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + double dbVal = 0.0f; + pProp->Get(dbVal); + + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_PIXELRATIO, dbVal); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + double dbVal = 0.0f; + + TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_PIXELRATIO, &dbVal); + + pProp->Set(dbVal); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "GlobalGain" property. */ @@ -2963,20 +3568,20 @@ int CMMTUCam::OnFrameRate(MM::PropertyBase* pProp, MM::ActionType eAct) { case MM::AfterSet: { - double dblGain; - pProp->Get(dblGain); + double dblRate; + pProp->Get(dblRate); - TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, dblGain); + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, dblRate); ret = DEVICE_OK; } break; case MM::BeforeGet: { - double dblGain = 0.0f; + double dblRate = 0.0f; - TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, &dblGain); - pProp->Set(dblGain); + TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, &dblRate); + pProp->Set(dblRate); ret = DEVICE_OK; } @@ -3812,6 +4417,94 @@ int CMMTUCam::OnTestImageMode(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "OnGlobalGainMode" property. +*/ +int CMMTUCam::OnGlobalGainMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + // the user just set the new value for the property, so we have to + // apply this value to the 'hardware'. + + string val; + pProp->Get(val); + + if (val.length() != 0) + { + TUCAM_PROP_ATTR propAttr; + propAttr.nIdxChn = 0; + propAttr.idProp = TUIDP_GLOBALGAIN; + + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDP_GLOBALGAIN; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int nCnt = (int)(propAttr.dbValMax - propAttr.dbValMin + 1); + + for (int i = 0; iSet(valText.pText); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "OnGAINMode" property. */ @@ -3825,42 +4518,88 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) { case MM::AfterSet: { - int nVal = 0; - int nImgMode = 0; - int nGain = 0; - double dblExp = 0.0; - string val; - pProp->Get(val); - if (val.length() != 0) + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) { - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, &nVal)) - { - if (DHYANA_D95_V2 == m_nPID) - { - bool bLiving = m_bLiving; - if (0 == val.compare(g_HDRBIT_ON)) - { - nImgMode = 0; //HDR - nGain = 0; - } - else if(0 == val.compare(g_HIGHBIT_ON)) - { - nImgMode = 0; //HIGH - nGain = 1; - } - else if(0 == val.compare(g_LOWBIT_ON)) - { - nImgMode = 0; //LOW - nGain = 2; - } - else if(0 == val.compare(g_STDHIGH_ON)) - { - nImgMode = 1; //STDH - } - else if(0 == val.compare(g_STDLOW_ON)) - { - nImgMode = 2; //STDL - } + string val; + pProp->Get(val); + + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_IMGMODESELECT; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_IMGMODESELECT; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (int i = 0; i < nCnt; i++) + { + valText.dbValue = i; + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + + if (0 == val.compare(valText.pText)) + { + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, i); + break; + } + } + } + + OnPropertyChanged(g_PropNameMode, val.c_str()); + + double val = 0; + TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_GLOBALGAIN, &val); + SetProperty(g_PropNameGain, CDeviceUtils::ConvertToString((int)val)); + + UpdateExpRange(); + + ret = DEVICE_OK; + } + } + else + { + int nVal = 0; + int nImgMode = 0; + int nGain = 0; + double dblExp = 0.0; + string val; + pProp->Get(val); + if (val.length() != 0) + { + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, &nVal)) + { + if (DHYANA_D95_V2 == m_nPID) + { + bool bLiving = m_bLiving; + if (0 == val.compare(g_HDRBIT_ON)) + { + nImgMode = 0; //HDR + nGain = 0; + } + else if (0 == val.compare(g_HIGHBIT_ON)) + { + nImgMode = 0; //HIGH + nGain = 1; + } + else if (0 == val.compare(g_LOWBIT_ON)) + { + nImgMode = 0; //LOW + nGain = 2; + } + else if (0 == val.compare(g_STDHIGH_ON)) + { + nImgMode = 1; //STDH + } + else if (0 == val.compare(g_STDLOW_ON)) + { + nImgMode = 2; //STDL + } if (nImgMode != nVal) { @@ -3918,27 +4657,28 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) UpdateExpRange(); - if ((DHYANA_400BSIV2 == m_nPID && m_nBCD > 0x04)) - { - vectorModTgrValues; - ModTgrValues.push_back(g_TRIGGER_OFF); - if (0 == val.compare(g_GRLOW_ON) || 0 == val.compare(g_GRHIGH_ON)) - { - ModTgrValues.push_back(g_TRIGGER_STD); - } - else - { - ModTgrValues.push_back(g_TRIGGER_STD); - ModTgrValues.push_back(g_TRIGGER_SYN); - } - ModTgrValues.push_back(g_TRIGGER_SWF); - ClearAllowedValues(g_PropNameMdTgr); - SetAllowedValues(g_PropNameMdTgr, ModTgrValues); - } - } - } - - ret = DEVICE_OK; + if ((DHYANA_400BSIV2 == m_nPID && m_nBCD > 0x04)) + { + vectorModTgrValues; + ModTgrValues.push_back(g_TRIGGER_OFF); + if (0 == val.compare(g_GRLOW_ON) || 0 == val.compare(g_GRHIGH_ON)) + { + ModTgrValues.push_back(g_TRIGGER_STD); + } + else + { + ModTgrValues.push_back(g_TRIGGER_STD); + ModTgrValues.push_back(g_TRIGGER_SYN); + } + ModTgrValues.push_back(g_TRIGGER_SWF); + ClearAllowedValues(g_PropNameMdTgr); + SetAllowedValues(g_PropNameMdTgr, ModTgrValues); + } + } + } + + ret = DEVICE_OK; + } } } break; @@ -3967,6 +4707,22 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) pProp->Set(g_HDRBIT_ON); } } + else if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) + { + int nIdx = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, &nIdx); + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_IMGMODESELECT; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + valText.dbValue = nIdx; + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + + pProp->Set(valText.pText); + } else { if (1 == nVal) @@ -3997,6 +4753,82 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "OnShutterMode" property. +*/ +int CMMTUCam::OnShutterMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + string val; + pProp->Get(val); + + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_SHUTTER; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_SHUTTER; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int nCnt = (int)(capaAttr.nValMax - capaAttr.nValMin + 1); + + for (int i = 0; iSet(valText.pText); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "OnModeSelect" property. */ @@ -4320,6 +5152,9 @@ int CMMTUCam::OnBitDepth(MM::PropertyBase* pProp, MM::ActionType eAct) if (NULL == m_opCam.hIdxTUCam) return DEVICE_NOT_CONNECTED; + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID) + return DEVICE_OK; + int ret = DEVICE_ERR; switch(eAct) { @@ -4359,37 +5194,128 @@ int CMMTUCam::OnBitDepth(MM::PropertyBase* pProp, MM::ActionType eAct) SetProperty(MM::g_Keyword_PixelType, g_PixelType_8bit); } - if(m_nPID == PID_FL_20BW) + if (m_nPID == PID_FL_9BW || PID_FL_9BW_LT == m_nPID || m_nPID == PID_FL_20BW || m_nPID == PID_FL_26BW) { UpdateExpRange(); } - StartCapture(); +// StartCapture(); ResizeImageBuffer(); roiX_ = 0; roiY_ = 0; } - OnPropertyChanged(g_PropNameBODP, val.c_str()); + OnPropertyChanged(g_PropNameBODP, val.c_str()); + + ret = DEVICE_OK; + } + } + break; + case MM::BeforeGet: + { + int nVal = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BITOFDEPTH, &nVal); + + if (16 == nVal) + { + pProp->Set("16"); + } + else + { + pProp->Set("8"); + } + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + +/** +* Handles "BitDepth" property. +*/ +int CMMTUCam::OnBitDepthEum(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + // the user just set the new value for the property, so we have to + // apply this value to the 'hardware'. + string val; + pProp->Get(val); + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_BITOFDEPTH; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + m_bLiving = false; + TUCAM_Cap_Stop(m_opCam.hIdxTUCam); // Stop capture + ReleaseBuffer(); + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_BITOFDEPTH; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int i = 0; + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (; iSet("16"); - } - else - { - pProp->Set("8"); - } + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_BITOFDEPTH; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + valText.dbValue = nIdx; + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + pProp->Set(valText.pText); ret = DEVICE_OK; } @@ -4907,6 +5833,45 @@ int CMMTUCam::OnBlueGain(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "OnATExpMode" property. +*/ +int CMMTUCam::OnATExpMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + long lVal = 0; + pProp->Get(lVal); + + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_ATEXPOSURE_MODE, (int)lVal); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + int nVal = 0; + + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_ATEXPOSURE_MODE, &nVal); + + pProp->Set((long)nVal); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "ATExposure" property. */ @@ -4969,6 +5934,68 @@ int CMMTUCam::OnATExposure(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "OnTimeStamp" property. +*/ +int CMMTUCam::OnTimeStamp(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + string val; + pProp->Get(val); + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_ENABLETIMESTAMP; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + if (0 == val.compare("TRUE")) + { + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_ENABLETIMESTAMP, 1); + } + else + { + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_ENABLETIMESTAMP, 0); + } + } + + OnPropertyChanged(g_PropNameATEXP, val.c_str()); + + ret = DEVICE_OK; + } + } + break; + case MM::BeforeGet: + { + int nVal = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_ENABLETIMESTAMP, &nVal); + + if (1 == nVal) + { + pProp->Set("TRUE"); + } + else + { + pProp->Set("FALSE"); + } + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "Temperature" property. */ @@ -4986,14 +6013,24 @@ int CMMTUCam::OnTemperature(MM::PropertyBase* pProp, MM::ActionType eAct) pProp->Get(dblTemp); m_fValTemp = (float)dblTemp; - TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, (dblTemp + 50)); +// TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, (dblTemp + m_nMidTemp)); + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, (dblTemp * m_fScaTemp + m_nMidTemp)); ret = DEVICE_OK; } break; case MM::BeforeGet: { - pProp->Set(m_fValTemp); + double dblTemp; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE_TARGET, &dblTemp)) + { +// pProp->Set((dblTemp - 50)); + pProp->Set((dblTemp - m_nMidTemp) / m_fScaTemp); + } + else + { + pProp->Set(m_fValTemp); + } ret = DEVICE_OK; } @@ -5315,10 +6352,14 @@ int CMMTUCam::OnTriggerMode(MM::PropertyBase* pProp, MM::ActionType eAct) { m_tgrAttr.nTgrMode = TUCCM_SEQUENCE; } - else if (0 == val.compare(g_TRIGGER_STD)) + else if (0 == val.compare(g_TRIGGER_STD) || 0 == val.compare(g_TRIGGER_STDOVERLAP)) { m_tgrAttr.nTgrMode = TUCCM_TRIGGER_STANDARD; } + else if (0 == val.compare(g_TRIGGER_STDNONOVERLAP)) + { + m_tgrAttr.nTgrMode = TUCCM_TRIGGER_STANDARD_NONOVERLAP; + } else if (0 == val.compare(g_TRIGGER_SYN)) { m_tgrAttr.nTgrMode = TUCCM_TRIGGER_SYNCHRONOUS; @@ -5366,7 +6407,19 @@ int CMMTUCam::OnTriggerMode(MM::PropertyBase* pProp, MM::ActionType eAct) } else if (TUCCM_TRIGGER_STANDARD == m_tgrAttr.nTgrMode) { - pProp->Set(g_TRIGGER_STD); + if (IsSupport95V2New() || IsSupport401DNew() || IsSupport400BSIV3New()) + { + pProp->Set(g_TRIGGER_STDOVERLAP); + } + else + { + pProp->Set(g_TRIGGER_STD); + } + + } + else if (TUCCM_TRIGGER_STANDARD_NONOVERLAP == m_tgrAttr.nTgrMode) + { + pProp->Set(g_TRIGGER_STDNONOVERLAP); } else if (TUCCM_TRIGGER_SYNCHRONOUS == m_tgrAttr.nTgrMode) { @@ -5558,6 +6611,41 @@ int CMMTUCam::OnTriggerDelay(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +int CMMTUCam::OnTriggerFilter(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + long lVal = 0; + pProp->Get(lVal); + + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_SIGNALFILTER, lVal); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + int nVal = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_SIGNALFILTER, &nVal); + + pProp->Set((long)(nVal)); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + int CMMTUCam::OnTriggerFrames(MM::PropertyBase* pProp, MM::ActionType eAct) { if (NULL == m_opCam.hIdxTUCam) @@ -5597,6 +6685,45 @@ int CMMTUCam::OnTriggerFrames(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +int CMMTUCam::OnTriggerTotalFrames(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + long lVal = 0; + pProp->Get(lVal); + + if (TUCCM_SEQUENCE < m_tgrAttr.nTgrMode && m_tgrAttr.nTgrMode < TUCCM_TRIGGER_SOFTWARE) + { + m_tgrAttr.nFrames = lVal; + } + + TUCAM_Cap_SetTrigger(m_opCam.hIdxTUCam, m_tgrAttr); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + TUCAM_Cap_GetTrigger(m_opCam.hIdxTUCam, &m_tgrAttr); + + pProp->Set((long)(m_tgrAttr.nFrames)); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + int CMMTUCam::OnTriggerDoSoftware(MM::PropertyBase* pProp, MM::ActionType eAct) { if (NULL == m_opCam.hIdxTUCam) @@ -5966,6 +7093,10 @@ int CMMTUCam::OnTrgOutKindMode(MM::PropertyBase* pProp, MM::ActionType eAct) { m_tgrOutAttr.nTgrOutMode = 4; } + else if (0 == val.compare(g_TRIGGER_TRIREADY)) + { + m_tgrOutAttr.nTgrOutMode = 6; + } else if (0 == val.compare(g_TRIGGER_LOW)) { m_tgrOutAttr.nTgrOutMode = 0; @@ -6033,6 +7164,10 @@ int CMMTUCam::OnTrgOutKindMode(MM::PropertyBase* pProp, MM::ActionType eAct) { pProp->Set(g_TRIGGER_READEND); } + else if (6 == m_tgrOutAttr.nTgrOutMode) + { + pProp->Set(g_TRIGGER_TRIREADY); + } ret = DEVICE_OK; } @@ -6575,6 +7710,15 @@ int CMMTUCam::ResizeImageBuffer() if (TUCAMRET_SUCCESS != TUCAM_Dev_GetInfo(m_opCam.hIdxTUCam, &valHeight)) return DEVICE_NATIVE_MODULE_FAILED; + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) + { + ResizeBinImageBufferFL9BW(valWidth.nValue, valHeight.nValue); + } + else if (PID_FL_26BW == m_nPID) + { + ResizeBinImageBufferFL26BW(valWidth.nValue, valHeight.nValue); + } + char sz[256] = {0}; sprintf(sz, "[ResizeImageBuffer]:Width:%d, Height:%d, BytesPerPixel:%d\n", valWidth.nValue, valHeight.nValue, m_frame.ucElemBytes * nChnnels); OutputDebugString(sz); @@ -6601,6 +7745,50 @@ int CMMTUCam::ResizeImageBuffer() return DEVICE_OK; } +void CMMTUCam::ResizeBinImageBufferFL9BW(int &width, int &height) +{ + int bin = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BINNING_SUM, &bin); + + if (5 == bin) + bin = 8; + else if (4 == bin) + bin = 6; + else + bin += 1; + + if (!m_bROI) + { + int nMaxWid = width / bin; + int nMaxHei = height / bin; + + width = (nMaxWid >> 2) << 2; + height = (nMaxHei >> 2) << 2; + } +} + +void CMMTUCam::ResizeBinImageBufferFL26BW(int &width, int &height) +{ + int bin = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BINNING_SUM, &bin); + + if (6 == bin) + bin = 8; + else if (7 == bin) + bin = 16; + else + bin += 1; + + if (!m_bROI) + { + int nMaxWid = width / bin; + int nMaxHei = height / bin; + + width = (nMaxWid >> 2) << 2; + height = (nMaxHei >> 2) << 2; + } +} + void CMMTUCam::GenerateEmptyImage(ImgBuffer& img) { MMThreadGuard g(imgPixelsLock_); @@ -6978,7 +8166,7 @@ void CMMTUCam::RunTemperature() dw = GetTickCount(); - if (isSupportFanWaterCool()) // 400BSIV2 BCD = 0x05, 0x07, 0x09 ä¸å¯è°ƒé£Žæ‰‡ç›¸æœº + if (isSupportSoftProtect()) // 400BSIV2 BCD = 0x05, 0x07, 0x09 ²»¿Éµ÷·çÉÈÃà»ú { int nFan = 0; TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_FAN_GEAR, &nFan); @@ -7178,7 +8366,7 @@ int CMMTUCam::WaitForFrame(ImgBuffer& img) MMThreadGuard g(imgPixelsLock_); m_frame.ucFormatGet = TUFRM_FMT_USUAl; // Set usual format - if (TUCAMRET_SUCCESS == TUCAM_Buf_WaitForFrame(m_opCam.hIdxTUCam, &m_frame)) + if (TUCAMRET_SUCCESS == TUCAM_Buf_WaitForFrame(m_opCam.hIdxTUCam, &m_frame, 1000)) { if (img.Height() == 0 || img.Width() == 0 || img.Depth() == 0) return DEVICE_OUT_OF_MEMORY; @@ -7370,7 +8558,11 @@ bool CMMTUCam::isSupportFanCool() { bool bSupport = false; bSupport = isSupportFanWaterCool(); - if (PID_FL_20BW == m_nPID) + if (m_nPID == PID_FL_9BW || PID_FL_9BW_LT == m_nPID || PID_FL_20BW == m_nPID || m_nPID == PID_FL_26BW) + { + return true; + } + if (IsSupportAries16()) { return true; } @@ -7397,6 +8589,17 @@ bool CMMTUCam::isSupportFanWaterCool() return false; } +bool CMMTUCam::isSupportSoftProtect() +{ + bool bSupport = false; + bSupport = isSupportFanWaterCool(); + if (IsSupport400BSIV3New() || IsSupport95V2New()) + { + return false; + } + return bSupport; +} + void CMMTUCam::UpdateSlitHeightRange() { int nImgMode = 0; @@ -7434,7 +8637,7 @@ void CMMTUCam::UpdateExpRange() exposureMinimum_ = propAttr.dbValMin; exposureMaximum_ = propAttr.dbValMax; - if (PID_FL_20BW == m_nPID) + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_20BW == m_nPID || PID_FL_26BW == m_nPID) { exposureMaximum_ = 3600000; } @@ -7446,3 +8649,24 @@ void CMMTUCam::UpdateExpRange() SetPropertyLimits(MM::g_Keyword_Exposure, exposureMinimum_, exposureMaximum_); } + +void CMMTUCam::UpdateLevelsRange() +{ + TUCAM_PROP_ATTR propAttr; + propAttr.nIdxChn = 0; + propAttr.idProp = TUIDP_LFTLEVELS; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + SetPropertyLimits(g_PropNameLLev, (int)propAttr.dbValMin, (int)propAttr.dbValMax); + + SetProperty(g_PropNameLLev, CDeviceUtils::ConvertToString((int)propAttr.dbValMin)); + } + + propAttr.idProp = TUIDP_RGTLEVELS; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + SetPropertyLimits(g_PropNameRLev, (int)propAttr.dbValMin, (int)propAttr.dbValMax); + + SetProperty(g_PropNameRLev, CDeviceUtils::ConvertToString((int)propAttr.dbValMax)); + } +} From a97df73d3c54870d01e6c20303d789ccc19c6b51 Mon Sep 17 00:00:00 2001 From: fandayu <57091941+fandayu@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:57:34 +0800 Subject: [PATCH 117/141] Update MMTUCam.h --- DeviceAdapters/TUCam/MMTUCam.h | 44 ++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/DeviceAdapters/TUCam/MMTUCam.h b/DeviceAdapters/TUCam/MMTUCam.h index a6d6fac05..662a53ecc 100755 --- a/DeviceAdapters/TUCam/MMTUCam.h +++ b/DeviceAdapters/TUCam/MMTUCam.h @@ -8,12 +8,12 @@ // microscope devices and enables testing of the rest of the // system without the need to connect to the actual hardware. // -// AUTHOR: fandayu, fandayu@tucsen.com 2022 +// AUTHOR: fandayu, fandayu@tucsen.com 2024 // // Karl Hoover (stuff such as programmable CCD size & the various image processors) // Arther Edelstein ( equipment error simulation) // -// COPYRIGHT: Tucsen Photonics Co., Ltd., 2022 +// COPYRIGHT: Tucsen Photonics Co., Ltd., 2024 // // // LICENSE: This file is distributed under the BSD license. @@ -91,7 +91,13 @@ const int SEVEN_SEGMENT_Y_OFFSET[] = {0, 0, 0, 1, 1, 1, 2}; #define DHYANA_4040BSI 0xE413 #define DHYANA_400BSIV3 0xE419 #define DHYANA_XF4040BSI 0xE41B +#define PID_FL_20 0xEC0D +#define PID_FL_9BW 0xE422 +#define PID_FL_9BW_LT 0xE426 +#define PID_FL_26BW 0xE423 +#define PID_ARIES16LT 0xE424 +#define PID_ARIES16 0xE425 /////////////////////// // ImgMode /////////////////////// @@ -251,8 +257,11 @@ class CMMTUCam : public CCameraBase int OnTestProperty(MM::PropertyBase* pProp, MM::ActionType eAct, long); int OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnBinningSum(MM::PropertyBase* pProp, MM::ActionType eAct); int OnPixelClock(MM::PropertyBase* pProp, MM::ActionType eAct); int OnExposure(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnBrightness(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnPixelRatio(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGlobalGain(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFrameRate(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSensorReset(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -270,11 +279,14 @@ class CMMTUCam : public CCameraBase int OnRollingScanReset(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTestImageMode(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnGlobalGainMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnShutterMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnModeSelect(MM::PropertyBase* pProp, MM::ActionType eAct); int OnImageMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnPixelType(MM::PropertyBase* pProp, MM::ActionType eAct); int OnBitDepth(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnBitDepthEum(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFlipH(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFlipV(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGamma(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -285,7 +297,9 @@ class CMMTUCam : public CCameraBase int OnRedGain(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGreenGain(MM::PropertyBase* pProp, MM::ActionType eAct); int OnBlueGain(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnATExpMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnATExposure(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTimeStamp(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTemperature(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFan(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFanState(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -296,7 +310,9 @@ class CMMTUCam : public CCameraBase int OnTriggerExpMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerEdgeMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerDelay(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTriggerFilter(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerFrames(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTriggerTotalFrames(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerDoSoftware(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSharpness(MM::PropertyBase* pProp, MM::ActionType eAct); int OnDPCLevel(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -327,7 +343,9 @@ class CMMTUCam : public CCameraBase private: + int SetAllowedDepth(); int SetAllowedBinning(); + int SetAllowedBinningSum(); int SetAllowedPixelClock(); int SetAllowedFanGear(); @@ -338,12 +356,21 @@ class CMMTUCam : public CCameraBase int SetAllowedRSReset(); int SetAllowedTestImg(); int SetAllowedClrTemp(); + int SetAllowedShutterMode(); void TestResourceLocking(const bool); void GenerateEmptyImage(ImgBuffer& img); void GenerateSyntheticImage(ImgBuffer& img, double exp); int ResizeImageBuffer(); + void ResizeBinImageBufferFL9BW(int &width, int &height); + void ResizeBinImageBufferFL26BW(int &width, int &height); + + bool IsSupport95V2New() { return DHYANA_D95_V2 == m_nPID && m_nBCD >= 0x2000; } + bool IsSupport401DNew() { return DHYANA_401D == m_nPID && m_nBCD >= 0x2000; } + bool IsSupport400BSIV3New() { return DHYANA_400BSIV3 == m_nPID && m_nBCD >= 0x2000; } + bool IsSupportAries16() { return 0xE424 == m_nPID || 0xE425 == m_nPID; } + static const double nominalPixelSizeUm_; double exposureMaximum_; @@ -410,12 +437,17 @@ class CMMTUCam : public CCameraBase int WaitForFrame(ImgBuffer& img); bool SaveRaw(char *pfileName, unsigned char *pData, unsigned long ulSize); - bool isSupportFanWaterCool(); bool isSupportFanCool(); + bool isSupportFanWaterCool(); + bool isSupportSoftProtect(); void UpdateSlitHeightRange(); void UpdateExpRange(); - + void UpdateLevelsRange(); +/* + void LoadProfile(); + void SaveProfile(); +*/ static int s_nNumCam; // The number of cameras static int s_nCntCam; // The count of camera @@ -425,10 +457,12 @@ class CMMTUCam : public CCameraBase int m_nIdxGain; // The gain mode int m_nMaxHeight; // The max height size int m_nTriType; // The trigger type + int m_nZeroTemp; // The zero temperature reference value char m_szImgPath[MAX_PATH]; // The save image path float m_fCurTemp; // The current temperature - float m_fValTemp; // The temperature value + float m_fValTemp; // The current temperature + float m_fScaTemp; // The scale of temperature int m_nMidTemp; // The middle value of temperature bool m_bROI; // The ROI state bool m_bSaving; // The tag of save image From 2f8a4282504df72f95e43545499b8a5381b024a4 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 11 Jan 2024 17:24:51 -0600 Subject: [PATCH 118/141] QSI: Set location of SDK in 3rdparty --- DeviceAdapters/QSI/QSI.vcxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DeviceAdapters/QSI/QSI.vcxproj b/DeviceAdapters/QSI/QSI.vcxproj index dbcdb796b..4bcadef65 100644 --- a/DeviceAdapters/QSI/QSI.vcxproj +++ b/DeviceAdapters/QSI/QSI.vcxproj @@ -94,7 +94,7 @@ true NotUsing pch.h - $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_2022_07_19\include;%(AdditionalIncludeDirectories) + $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_20200917\QSICameraCLib\include;%(AdditionalIncludeDirectories) Windows @@ -103,7 +103,7 @@ true false QSICameraCLib.lib;%(AdditionalDependencies) - $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_2022_07_19\lib;%(AdditionalLibraryDirectories) + $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_20200917\QSICameraCLib\lib\x64;%(AdditionalLibraryDirectories) From 273d2329d30714c2dc9d297e6e930f8735dd275b Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Fri, 5 Jan 2024 16:24:34 -0600 Subject: [PATCH 119/141] Add NotificationTester device adapter Initial implementation with property emulator only. --- DeviceAdapters/Makefile.am | 1 + .../NotificationTester/DelayedNotifier.h | 137 ++++++++++ DeviceAdapters/NotificationTester/Makefile.am | 8 + .../NotificationTester/NotificationTester.cpp | 196 ++++++++++++++ .../NotificationTester.vcxproj | 99 +++++++ .../NotificationTester.vcxproj.filters | 30 +++ .../NotificationTester/ProcessModel.h | 242 ++++++++++++++++++ DeviceAdapters/configure.ac | 1 + micromanager.sln | 6 + 9 files changed, 720 insertions(+) create mode 100644 DeviceAdapters/NotificationTester/DelayedNotifier.h create mode 100644 DeviceAdapters/NotificationTester/Makefile.am create mode 100644 DeviceAdapters/NotificationTester/NotificationTester.cpp create mode 100644 DeviceAdapters/NotificationTester/NotificationTester.vcxproj create mode 100644 DeviceAdapters/NotificationTester/NotificationTester.vcxproj.filters create mode 100644 DeviceAdapters/NotificationTester/ProcessModel.h diff --git a/DeviceAdapters/Makefile.am b/DeviceAdapters/Makefile.am index ec6b8891a..5d590152f 100644 --- a/DeviceAdapters/Makefile.am +++ b/DeviceAdapters/Makefile.am @@ -155,6 +155,7 @@ SUBDIRS = \ NewportSMC \ Nikon \ NikonTE2000 \ + NotificationTester \ OVP_ECS2 \ Omicron \ Oxxius \ diff --git a/DeviceAdapters/NotificationTester/DelayedNotifier.h b/DeviceAdapters/NotificationTester/DelayedNotifier.h new file mode 100644 index 000000000..51033b082 --- /dev/null +++ b/DeviceAdapters/NotificationTester/DelayedNotifier.h @@ -0,0 +1,137 @@ +// Mock device adapter for testing of device change notifications +// +// Copyright (C) 2024 Board of Regents of the University of Wisconsin System +// +// This file is distributed under the BSD license. License text is included +// with the source distribution. +// +// This file is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// Author: Mark A. Tsuchida + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// DelayedNotifier calls scheduled callables after a delay relative to when +// they are scheduled. The delay is fixed at the time of scheduling, and +// actions are always called on a background thread, even if the delay is zero. +// Member functions are thread-safe. + +class DelayedNotifier { + using Clock = std::chrono::steady_clock; + using TimePoint = Clock::time_point; + + struct Item { + TimePoint when; + std::function what; + + Item(TimePoint timeout, std::function action) : + when(timeout), what(action) {} + }; + + // Priority queue with soonest item at top + struct LaterItem { + bool operator()(const Item& lhs, const Item& rhs) const { + return lhs.when > rhs.when; + } + }; + using PrioQ = std::priority_queue, LaterItem>; + + mutable std::mutex mut_; + std::condition_variable cv_; + std::chrono::microseconds delay_{}; + PrioQ scheduledItems_; + bool stopRequested_ = false; + + std::thread notifyThread_; + std::once_flag threadStarted_; + + TimePoint NextTimeout() const { + if (scheduledItems_.empty()) { + return TimePoint::max(); + } + return scheduledItems_.top().when; + } + + void IssueNotifications() { + std::unique_lock lock(mut_); + for (;;) { + const auto timeout = NextTimeout(); + cv_.wait_until(lock, timeout, + [&] { return NextTimeout() < timeout || stopRequested_; }); + if (stopRequested_) { + return; + } + const auto now = Clock::now(); + while (NextTimeout() <= now) { + std::function func = scheduledItems_.top().what; + scheduledItems_.pop(); + lock.unlock(); + func(); + lock.lock(); + } + } + } + +public: + ~DelayedNotifier() { + if (notifyThread_.joinable()) { + { + std::lock_guard lock(mut_); + stopRequested_ = true; + } + cv_.notify_one(); + notifyThread_.join(); + } + } + + std::chrono::microseconds Delay() const { + std::lock_guard lock(mut_); + return delay_; + } + + // Set the delay; applies to actions scheduled in the future only. + void Delay(std::chrono::microseconds delay) { + assert(delay.count() >= 0); + std::lock_guard lock(mut_); + delay_ = delay; + } + + void Schedule(std::function action) { + assert(action); + std::call_once(threadStarted_, [this] { // Lazy-start thread + notifyThread_ = std::thread([this] { IssueNotifications(); }); + }); + bool needToInterruptWait{}; + { + std::lock_guard lock(mut_); + const auto prevTimeout = NextTimeout(); + const auto timeout = Clock::now() + delay_; + scheduledItems_.emplace(timeout, action); + needToInterruptWait = timeout < prevTimeout; + } + if (needToInterruptWait) { + cv_.notify_one(); + } + } + + void CancelAll() { + std::lock_guard lock(mut_); + while (!scheduledItems_.empty()) { + scheduledItems_.pop(); + } + } +}; diff --git a/DeviceAdapters/NotificationTester/Makefile.am b/DeviceAdapters/NotificationTester/Makefile.am new file mode 100644 index 000000000..b3eb0085c --- /dev/null +++ b/DeviceAdapters/NotificationTester/Makefile.am @@ -0,0 +1,8 @@ +AM_CXXFLAGS = $(MMDEVAPI_CXXFLAGS) + +deviceadapter_LTLIBRARIES = libmmgr_dal_NotificationTester.la + +libmmgr_dal_NotificationTester_la_SOURCES = NotificationTester.cpp + +libmmgr_dal_NotificationTester_la_LIBADD = $(MMDEVAPI_LIBADD) +libmmgr_dal_NotificationTester_la_LDFLAGS = $(MMDEVAPI_LDFLAGS) diff --git a/DeviceAdapters/NotificationTester/NotificationTester.cpp b/DeviceAdapters/NotificationTester/NotificationTester.cpp new file mode 100644 index 000000000..f7820bc65 --- /dev/null +++ b/DeviceAdapters/NotificationTester/NotificationTester.cpp @@ -0,0 +1,196 @@ +// Mock device adapter for testing of device change notifications +// +// Copyright (C) 2024 Board of Regents of the University of Wisconsin System +// +// This file is distributed under the BSD license. License text is included +// with the source distribution. +// +// This file is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// Author: Mark A. Tsuchida + +#include "DelayedNotifier.h" +#include "ProcessModel.h" + +#include "DeviceBase.h" +#include "ModuleInterface.h" + +#include +#include +#include +#include +#include + +constexpr char DEVNAME_SYNC_PROPERTY[] = "NTSyncProperty"; +constexpr char DEVNAME_ASYNC_PROPERTY[] = "NTAsyncProperty"; +constexpr char PROPNAME_TEST_PROPERTY[] = "TestProperty"; + +template +class NTestProp : public CGenericBase> +{ + static constexpr bool isAsync_ = + std::is_same::value; + + std::string name_; + ProcModel model_; + DelayedNotifier delayer_; + + std::mutex notificationMut_; + bool notificationsEnabled_ = false; + +public: + explicit NTestProp(std::string name) : + name_(std::move(name)), + model_([this](double pv) { + this->LogMessage(("PV = " + std::to_string(pv)).c_str(), true); + { + std::lock_guard lock(notificationMut_); + if (!notificationsEnabled_) { + return; + } + } + // Avoid delay scheduling for async zero-delay, for close (though not + // quite atomic) sync with busy state. (Atomicity between device state + // and notifications is not meaningfully achievable with the MMDevice + // API, nor is it what notifications are meant for.) + if (isAsync_ && delayer_.Delay().count() > 0) { + delayer_.Schedule([this, pv = std::to_string(pv)] { + this->OnPropertyChanged(PROPNAME_TEST_PROPERTY, pv.c_str()); + }); + } else { + this->OnPropertyChanged(PROPNAME_TEST_PROPERTY, + std::to_string(pv).c_str()); + } + }) + {} + + int Initialize() final { + this->CreateStringProperty("NotificationsEnabled", + notificationsEnabled_ ? "Yes" : "No", false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(notificationsEnabled_ ? "Yes" : "No"); + } else if (eAct == MM::AfterSet) { + std::string value; + pProp->Get(value); + notificationsEnabled_ = (value == "Yes"); + } + return DEVICE_OK; + })); + this->AddAllowedValue("NotificationsEnabled", "No"); + this->AddAllowedValue("NotificationsEnabled", "Yes"); + + this->CreateFloatProperty(PROPNAME_TEST_PROPERTY, + model_.ProcessVariable(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(model_.ProcessVariable()); + } else if (eAct == MM::AfterSet) { + double value{}; + pProp->Get(value); + this->LogMessage( + ("sp = " + std::to_string(value)).c_str(), true); + model_.Setpoint(value); + } + return DEVICE_OK; + })); + + if (isAsync_) { + this->CreateFloatProperty("SlewTimePerUnit_s", + model_.ReciprocalSlewRateSeconds(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(model_.ReciprocalSlewRateSeconds()); + } else if (eAct == MM::AfterSet) { + double seconds{}; + pProp->Get(seconds); + model_.ReciprocalSlewRateSeconds(seconds); + } + return DEVICE_OK; + })); + this->SetPropertyLimits("SlewTimePerUnit_s", 0.0001, 10.0); + + this->CreateFloatProperty("UpdateInterval_s", + model_.UpdateIntervalSeconds(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(model_.UpdateIntervalSeconds()); + } else if (eAct == MM::AfterSet) { + double seconds{}; + pProp->Get(seconds); + model_.UpdateIntervalSeconds(seconds); + } + return DEVICE_OK; + })); + this->SetPropertyLimits("UpdateInterval_s", 0.0001, 10.0); + + using std::chrono::duration_cast; + using std::chrono::microseconds; + using FPSeconds = std::chrono::duration; + this->CreateFloatProperty("NotificationDelay_s", + duration_cast(delayer_.Delay()).count(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set( + duration_cast(delayer_.Delay()).count()); + } else if (eAct == MM::AfterSet) { + double seconds{}; + pProp->Get(seconds); + const auto delay = FPSeconds{seconds}; + delayer_.Delay(duration_cast(delay)); + } + return DEVICE_OK; + })); + this->SetPropertyLimits("NotificationDelay_s", 0.0, 1.0); + } + + return DEVICE_OK; + } + + int Shutdown() final { + model_.Halt(); + delayer_.CancelAll(); + return DEVICE_OK; + } + + bool Busy() final { + return model_.IsSlewing(); + } + + void GetName(char *name) const final { + CDeviceUtils::CopyLimitedString(name, name_.c_str()); + } +}; + +MODULE_API void InitializeModuleData() +{ + RegisterDevice(DEVNAME_SYNC_PROPERTY, MM::GenericDevice, + "Test synchronous property notifications"); + RegisterDevice(DEVNAME_ASYNC_PROPERTY, MM::GenericDevice, + "Test asynchronous property busy state and notifications"); +} + +MODULE_API MM::Device *CreateDevice(const char *deviceName) +{ + const std::string name(deviceName); + if (name == DEVNAME_SYNC_PROPERTY) + return new NTestProp(name); + if (name == DEVNAME_ASYNC_PROPERTY) + return new NTestProp(name); + return nullptr; +} + +MODULE_API void DeleteDevice(MM::Device *pDevice) +{ + delete pDevice; +} \ No newline at end of file diff --git a/DeviceAdapters/NotificationTester/NotificationTester.vcxproj b/DeviceAdapters/NotificationTester/NotificationTester.vcxproj new file mode 100644 index 000000000..56a5b3dfc --- /dev/null +++ b/DeviceAdapters/NotificationTester/NotificationTester.vcxproj @@ -0,0 +1,99 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + {b8c95f39-54bf-40a9-807b-598df2821d55} + + + + 16.0 + Win32Proj + {7c8c60fa-92e3-4102-80ab-a4468c4fcd2f} + NotificationTester + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + false + + + + true + _DEBUG;NOTIFICATIONTESTERVS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + false + + + + + true + true + true + NDEBUG;NOTIFICATIONTESTERVS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + false + + + + + + \ No newline at end of file diff --git a/DeviceAdapters/NotificationTester/NotificationTester.vcxproj.filters b/DeviceAdapters/NotificationTester/NotificationTester.vcxproj.filters new file mode 100644 index 000000000..c60f9837b --- /dev/null +++ b/DeviceAdapters/NotificationTester/NotificationTester.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/DeviceAdapters/NotificationTester/ProcessModel.h b/DeviceAdapters/NotificationTester/ProcessModel.h new file mode 100644 index 000000000..a2d5ac59c --- /dev/null +++ b/DeviceAdapters/NotificationTester/ProcessModel.h @@ -0,0 +1,242 @@ +// Mock device adapter for testing of device change notifications +// +// Copyright (C) 2024 Board of Regents of the University of Wisconsin System +// +// This file is distributed under the BSD license. License text is included +// with the source distribution. +// +// This file is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// Author: Mark A. Tsuchida + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// Models of physical device state. SyncProcessModel and AsyncProcessModel have +// the same compile-time interface. Member functions are not thread-safe: they +// must be called with external synchronization. +// +// SyncProcessModel issues change notifications synchronously (i.e., from the +// thread setting the value) and never becomes busy. +// +// AsyncProcessModel separates setpoint from process variable; the latter is +// slewed so that it reaches the setpoint over time (so the model is "busy" +// while still slewing). Change notifications are periodically issued from a +// background thread during the slewing, with the last notification always +// occuring when the PV reaches the SP. + +class SyncProcessModel { + // Setpoint and process variable are always equal. + double value_ = 0.0; + + std::function update_; + double lastUpdatedPV_ = 0.0; + + void Update() { + if (value_ == lastUpdatedPV_) { + return; + } + update_(value_); + lastUpdatedPV_ = value_; + } + +public: + explicit SyncProcessModel(std::function updateFunc) : + update_(std::move(updateFunc)) { + assert(update_); + } + + void ReciprocalSlewRateSeconds(double rRate_s) { + (void)rRate_s; + assert(false); + } + + double ReciprocalSlewRateSeconds() const { + assert(false); + return 1.0; + } + + void UpdateIntervalSeconds(double interval_s) { + (void)interval_s; + assert(false); + } + + double UpdateIntervalSeconds() const { + assert(false); + return 0.1; + } + + bool IsSlewing() const { return false; } + + double ProcessVariable() const { return value_; } + + double Setpoint() const { return value_; } + + void Halt() { /* no-op */ } + + void Setpoint(double setpoint) { + assert(std::isfinite(setpoint)); + value_ = setpoint; + Update(); + } +}; + +class AsyncProcessModel { + std::chrono::duration rRate_{1.0}; // Reciprocal rate, per PV unit + std::chrono::duration updateInterval_{0.1}; + + // PV and SP protected by mut_ unless slewThread_ known not to be running + mutable std::mutex mut_; + double procVar_ = 0.0; + double setpoint_ = 0.0; + + std::thread slewThread_; + std::mutex stopMut_; + std::condition_variable stopCv_; + bool stopRequested_ = false; + + std::mutex updateMut_; + std::function update_; + double lastUpdatedPV_ = 0.0; + + // Only call from slew thread; cancel triggered by Halt() + template + bool CancelableWaitUntil(TimePoint timeout) { + std::unique_lock lock(stopMut_); + stopCv_.wait_until(lock, timeout, [&] { return stopRequested_; }); + return stopRequested_; + } + + // Only call from slew thread + void Update() { + std::lock_guard lock(updateMut_); + if (procVar_ == lastUpdatedPV_) { + return; + } + update_(procVar_); + lastUpdatedPV_ = procVar_; + } + +public: + explicit AsyncProcessModel(std::function updateFunc) : + update_(std::move(updateFunc)) { + assert(update_); + } + + ~AsyncProcessModel() { + // Avoid sending updates upon halting + { + std::lock_guard lock(updateMut_); + update_ = [](auto) {}; + } + Halt(); + } + + // Set slew rate (as reciprocal slew rate); applies to next setpoint + void ReciprocalSlewRateSeconds(double rRate_s) { + assert(rRate_s > 0.0); + rRate_ = std::chrono::duration{rRate_s}; + } + + double ReciprocalSlewRateSeconds() const { + return rRate_.count(); + } + + // Set update interval; applies to next setpoint + void UpdateIntervalSeconds(double interval_s) { + assert(interval_s > 0.0); + updateInterval_ = std::chrono::duration{interval_s}; + } + + double UpdateIntervalSeconds() const { + return updateInterval_.count(); + } + + bool IsSlewing() const { // Aka "busy", "in motion" + std::lock_guard lock(mut_); + return procVar_ != setpoint_; + } + + double ProcessVariable() const { + std::lock_guard lock(mut_); + return procVar_; + } + + double Setpoint() const { + std::lock_guard lock(mut_); + return setpoint_; + } + + void Halt() { + if (!slewThread_.joinable()) { + return; + } + + { + std::lock_guard lock(stopMut_); + stopRequested_ = true; + } + stopCv_.notify_one(); + slewThread_.join(); + + setpoint_ = procVar_; + } + + void Setpoint(double setpoint) { + assert(std::isfinite(setpoint)); + Halt(); // Cancel previous slew, if any, updating last PV + setpoint_ = setpoint; + if (procVar_ == setpoint_) { + return; + } + + const auto orig = procVar_; + stopRequested_ = false; + slewThread_ = std::thread( + [this, orig, setpoint, absRRate = rRate_, + updateInterval = updateInterval_] { + const auto displacement = setpoint - orig; + const auto rRate = displacement < 0.0 ? -absRRate : absRRate; + const auto duration = displacement * rRate; + const auto startTime = std::chrono::steady_clock::now(); + const auto finishTime = startTime + duration; + auto updateTime = startTime + updateInterval; + for (;;) { + if (finishTime <= updateTime) { + if (!CancelableWaitUntil(finishTime)) { + std::lock_guard lock(mut_); + procVar_ = setpoint; + } + Update(); + return; + } + + if (CancelableWaitUntil(updateTime)) { + Update(); + return; + } + const auto elapsed = updateTime - startTime; + const double pv = orig + elapsed / rRate; + { + std::lock_guard lock(mut_); + procVar_ = pv; + } + Update(); + updateTime += updateInterval; + } + }); + } +}; diff --git a/DeviceAdapters/configure.ac b/DeviceAdapters/configure.ac index 7424deba3..13ad336a2 100644 --- a/DeviceAdapters/configure.ac +++ b/DeviceAdapters/configure.ac @@ -586,6 +586,7 @@ m4_define([device_adapter_dirs], [m4_strip([ NewportSMC Nikon NikonTE2000 + NotificationTester OVP_ECS2 Omicron OpenCVgrabber diff --git a/micromanager.sln b/micromanager.sln index 60a6adb1f..13bb0646a 100644 --- a/micromanager.sln +++ b/micromanager.sln @@ -481,6 +481,8 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IDSPeak", "DeviceAdapters\IDSPeak\IDSPeak.vcxproj", "{823CF77E-8120-41B1-9B25-823A79C93198}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QSI", "DeviceAdapters\QSI\QSI.vcxproj", "{320AF64E-C4FC-4A97-A7D1-F8233A8A44C7}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NotificationTester", "DeviceAdapters\NotificationTester\NotificationTester.vcxproj", "{7C8C60FA-92E3-4102-80AB-A4468C4FCD2F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -1447,6 +1449,10 @@ Global {320AF64E-C4FC-4A97-A7D1-F8233A8A44C7}.Debug|x64.Build.0 = Debug|x64 {320AF64E-C4FC-4A97-A7D1-F8233A8A44C7}.Release|x64.ActiveCfg = Release|x64 {320AF64E-C4FC-4A97-A7D1-F8233A8A44C7}.Release|x64.Build.0 = Release|x64 + {7C8C60FA-92E3-4102-80AB-A4468C4FCD2F}.Debug|x64.ActiveCfg = Debug|x64 + {7C8C60FA-92E3-4102-80AB-A4468C4FCD2F}.Debug|x64.Build.0 = Debug|x64 + {7C8C60FA-92E3-4102-80AB-A4468C4FCD2F}.Release|x64.ActiveCfg = Release|x64 + {7C8C60FA-92E3-4102-80AB-A4468C4FCD2F}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From e7ccd143a2bc8a1ea2013cb0136bdf81c9df3ff0 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 9 Jan 2024 13:28:03 -0600 Subject: [PATCH 120/141] NotificationTester: ExternallySet property --- .../NotificationTester/NotificationTester.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/DeviceAdapters/NotificationTester/NotificationTester.cpp b/DeviceAdapters/NotificationTester/NotificationTester.cpp index f7820bc65..4bdc1bf87 100644 --- a/DeviceAdapters/NotificationTester/NotificationTester.cpp +++ b/DeviceAdapters/NotificationTester/NotificationTester.cpp @@ -102,6 +102,21 @@ class NTestProp : public CGenericBase> return DEVICE_OK; })); + this->CreateFloatProperty("ExternallySet", model_.Setpoint(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + // Keep last-set value + } else if (eAct == MM::AfterSet) { + double value{}; + pProp->Get(value); + this->LogMessage(("sp = " + std::to_string(value) + + " (external)").c_str(), true); + model_.Setpoint(value); + } + return DEVICE_OK; + })); + if (isAsync_) { this->CreateFloatProperty("SlewTimePerUnit_s", model_.ReciprocalSlewRateSeconds(), false, From bf3964b95c1d6794de2512112eec916301afd598 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 9 Jan 2024 13:49:55 -0600 Subject: [PATCH 121/141] NotificationTester: Refactor sync notifications Make the decision in DelayedNotifier (which always has delay 0 in the sync case) rather than in the update handler, so that we don't need to repeat the logic for additional devices. This preserves: - Sync -> notify on calling thread (i.e., sync) - Async, zero delay -> notify on update thread (i.e., async) - Async, > 0 delay -> notify on notifier thread (i.e., async) --- .../NotificationTester/DelayedNotifier.h | 26 +++++++++++++------ .../NotificationTester/NotificationTester.cpp | 15 +++-------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/DeviceAdapters/NotificationTester/DelayedNotifier.h b/DeviceAdapters/NotificationTester/DelayedNotifier.h index 51033b082..5d784daaa 100644 --- a/DeviceAdapters/NotificationTester/DelayedNotifier.h +++ b/DeviceAdapters/NotificationTester/DelayedNotifier.h @@ -27,7 +27,8 @@ // DelayedNotifier calls scheduled callables after a delay relative to when // they are scheduled. The delay is fixed at the time of scheduling, and -// actions are always called on a background thread, even if the delay is zero. +// actions are always called on a background thread, except when the delay is +// zero, in which case the action is called on the scheduling thread. // Member functions are thread-safe. class DelayedNotifier { @@ -112,16 +113,25 @@ class DelayedNotifier { void Schedule(std::function action) { assert(action); - std::call_once(threadStarted_, [this] { // Lazy-start thread - notifyThread_ = std::thread([this] { IssueNotifications(); }); - }); + bool callSync{}; bool needToInterruptWait{}; { std::lock_guard lock(mut_); - const auto prevTimeout = NextTimeout(); - const auto timeout = Clock::now() + delay_; - scheduledItems_.emplace(timeout, action); - needToInterruptWait = timeout < prevTimeout; + if (delay_.count() == 0) { + callSync = true; + } else { + const auto prevTimeout = NextTimeout(); + const auto timeout = Clock::now() + delay_; + scheduledItems_.emplace(timeout, action); + needToInterruptWait = timeout < prevTimeout; + } + } + if (callSync) { + action(); + } else { + std::call_once(threadStarted_, [this] { // Lazy-start thread + notifyThread_ = std::thread([this] { IssueNotifications(); }); + }); } if (needToInterruptWait) { cv_.notify_one(); diff --git a/DeviceAdapters/NotificationTester/NotificationTester.cpp b/DeviceAdapters/NotificationTester/NotificationTester.cpp index 4bdc1bf87..ccab11f8c 100644 --- a/DeviceAdapters/NotificationTester/NotificationTester.cpp +++ b/DeviceAdapters/NotificationTester/NotificationTester.cpp @@ -54,18 +54,9 @@ class NTestProp : public CGenericBase> return; } } - // Avoid delay scheduling for async zero-delay, for close (though not - // quite atomic) sync with busy state. (Atomicity between device state - // and notifications is not meaningfully achievable with the MMDevice - // API, nor is it what notifications are meant for.) - if (isAsync_ && delayer_.Delay().count() > 0) { - delayer_.Schedule([this, pv = std::to_string(pv)] { - this->OnPropertyChanged(PROPNAME_TEST_PROPERTY, pv.c_str()); - }); - } else { - this->OnPropertyChanged(PROPNAME_TEST_PROPERTY, - std::to_string(pv).c_str()); - } + delayer_.Schedule([this, pv = std::to_string(pv)]{ + this->OnPropertyChanged(PROPNAME_TEST_PROPERTY, pv.c_str()); + }); }) {} From ba09d90db4fd0ec70a0c9354da19e39687d5667f Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 9 Jan 2024 15:10:57 -0600 Subject: [PATCH 122/141] NotificationTester: Add Z stage simulation --- .../NotificationTester/NotificationTester.cpp | 210 +++++++++++++++++- 1 file changed, 209 insertions(+), 1 deletion(-) diff --git a/DeviceAdapters/NotificationTester/NotificationTester.cpp b/DeviceAdapters/NotificationTester/NotificationTester.cpp index ccab11f8c..57ca6aad7 100644 --- a/DeviceAdapters/NotificationTester/NotificationTester.cpp +++ b/DeviceAdapters/NotificationTester/NotificationTester.cpp @@ -21,6 +21,7 @@ #include "ModuleInterface.h" #include +#include #include #include #include @@ -28,6 +29,8 @@ constexpr char DEVNAME_SYNC_PROPERTY[] = "NTSyncProperty"; constexpr char DEVNAME_ASYNC_PROPERTY[] = "NTAsyncProperty"; +constexpr char DEVNAME_SYNC_STAGE[] = "NTSyncStage"; +constexpr char DEVNAME_ASYNC_STAGE[] = "NTAsyncStage"; constexpr char PROPNAME_TEST_PROPERTY[] = "TestProperty"; template @@ -55,6 +58,7 @@ class NTestProp : public CGenericBase> } } delayer_.Schedule([this, pv = std::to_string(pv)]{ + this->LogMessage(("Notifying: PV = " + pv).c_str(), true); this->OnPropertyChanged(PROPNAME_TEST_PROPERTY, pv.c_str()); }); }) @@ -178,12 +182,212 @@ class NTestProp : public CGenericBase> } }; +template +class NTestStage : public CStageBase> +{ + static constexpr bool isAsync_ = + std::is_same::value; + + // The process model operates in steps (only set to integers; round upon + // readout). + static constexpr double umPerStep_ = 0.1; + + std::string name_; + ProcModel model_; + DelayedNotifier delayer_; + + std::mutex notificationMut_; + bool notificationsEnabled_ = false; + +public: + explicit NTestStage(std::string name) : + name_(std::move(name)), + model_([this](double pv) { + long ipv = std::lround(pv); + this->LogMessage(("PV = " + std::to_string(ipv)).c_str(), true); + { + std::lock_guard lock(notificationMut_); + if (!notificationsEnabled_) { + return; + } + } + delayer_.Schedule([this, ipv] { + this->LogMessage(("Notifying: PV = " + + std::to_string(ipv)).c_str(), true); + this->OnStagePositionChanged(umPerStep_ * ipv); + }); + }) + { + // Adjust default for stage-like velocity (100 um/s). + model_.ReciprocalSlewRateSeconds(0.01 * umPerStep_); + } + + int Initialize() final { + this->CreateFloatProperty("UmPerStep", umPerStep_, true); + + this->CreateStringProperty("NotificationsEnabled", + notificationsEnabled_ ? "Yes" : "No", false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(notificationsEnabled_ ? "Yes" : "No"); + } else if (eAct == MM::AfterSet) { + std::string value; + pProp->Get(value); + notificationsEnabled_ = (value == "Yes"); + } + return DEVICE_OK; + })); + this->AddAllowedValue("NotificationsEnabled", "No"); + this->AddAllowedValue("NotificationsEnabled", "Yes"); + + this->CreateIntegerProperty("ExternallySetSteps", + static_cast(model_.Setpoint()), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + // Keep last-set value + } else if (eAct == MM::AfterSet) { + long value{}; + pProp->Get(value); + this->LogMessage(("sp = " + std::to_string(value) + + " (external)").c_str(), true); + model_.Setpoint(value); + } + return DEVICE_OK; + })); + + if (isAsync_) { + this->CreateFloatProperty("SlewTimePerStep_s", + model_.ReciprocalSlewRateSeconds(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(model_.ReciprocalSlewRateSeconds()); + } else if (eAct == MM::AfterSet) { + double seconds{}; + pProp->Get(seconds); + model_.ReciprocalSlewRateSeconds(seconds); + } + return DEVICE_OK; + })); + this->SetPropertyLimits("SlewTimePerStep_s", 0.0001, 10.0); + + this->CreateFloatProperty("UpdateInterval_s", + model_.UpdateIntervalSeconds(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(model_.UpdateIntervalSeconds()); + } else if (eAct == MM::AfterSet) { + double seconds{}; + pProp->Get(seconds); + model_.UpdateIntervalSeconds(seconds); + } + return DEVICE_OK; + })); + this->SetPropertyLimits("UpdateInterval_s", 0.0001, 10.0); + + using std::chrono::duration_cast; + using std::chrono::microseconds; + using FPSeconds = std::chrono::duration; + this->CreateFloatProperty("NotificationDelay_s", + duration_cast(delayer_.Delay()).count(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(duration_cast(delayer_.Delay()).count()); + } else if (eAct == MM::AfterSet) { + double seconds{}; + pProp->Get(seconds); + const auto delay = FPSeconds{seconds}; + delayer_.Delay(duration_cast(delay)); + } + return DEVICE_OK; + })); + this->SetPropertyLimits("NotificationDelay_s", 0.0, 1.0); + } + + return DEVICE_OK; + } + + int Shutdown() final { + model_.Halt(); + delayer_.CancelAll(); + return DEVICE_OK; + } + + bool Busy() final { + return model_.IsSlewing(); + } + + void GetName(char* name) const final { + CDeviceUtils::CopyLimitedString(name, name_.c_str()); + } + + int SetPositionUm(double um) final { + const long steps = std::lround(um / umPerStep_); + return SetPositionSteps(steps); + } + + int Stop() final { + model_.Halt(); + return DEVICE_OK; + } + + int GetPositionUm(double& um) final { + long steps{}; + int ret = GetPositionSteps(steps); + if (ret != DEVICE_OK) { + return ret; + } + um = umPerStep_ * steps; + return DEVICE_OK; + } + + int SetPositionSteps(long steps) final { + this->LogMessage(("sp = " + std::to_string(steps)).c_str(), true); + model_.Setpoint(steps); + return DEVICE_OK; + } + + int GetPositionSteps(long& steps) final { + steps = std::lround(model_.ProcessVariable()); + return DEVICE_OK; + } + + int SetOrigin() final { + return DEVICE_UNSUPPORTED_COMMAND; + } + + int GetLimits(double& lo, double& hi) final { + // Conservatively keep steps within 32-bit range (we want exact integer + // steps to be preserved by the double-based process model). + lo = -2147483648 * umPerStep_; + hi = +2147483647 * umPerStep_; + return DEVICE_OK; + } + + int IsStageSequenceable(bool& flag) const final { + flag = false; + return DEVICE_OK; + } + + bool IsContinuousFocusDrive() const final { + return false; + } +}; + MODULE_API void InitializeModuleData() { RegisterDevice(DEVNAME_SYNC_PROPERTY, MM::GenericDevice, "Test synchronous property notifications"); RegisterDevice(DEVNAME_ASYNC_PROPERTY, MM::GenericDevice, "Test asynchronous property busy state and notifications"); + RegisterDevice(DEVNAME_SYNC_STAGE, MM::StageDevice, + "Test synchronous stage position notifications"); + RegisterDevice(DEVNAME_ASYNC_STAGE, MM::StageDevice, + "Test asynchronous stage busy state and position notifications"); } MODULE_API MM::Device *CreateDevice(const char *deviceName) @@ -193,10 +397,14 @@ MODULE_API MM::Device *CreateDevice(const char *deviceName) return new NTestProp(name); if (name == DEVNAME_ASYNC_PROPERTY) return new NTestProp(name); + if (name == DEVNAME_SYNC_STAGE) + return new NTestStage(name); + if (name == DEVNAME_ASYNC_STAGE) + return new NTestStage(name); return nullptr; } MODULE_API void DeleteDevice(MM::Device *pDevice) { delete pDevice; -} \ No newline at end of file +} From 495056402c9a934fc9070e36977fa7af50ca6410 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 10 Jan 2024 14:57:20 -0600 Subject: [PATCH 123/141] NotificationTester: Refactor for 2D slew In preparation for adding XY stage. Factor out SlewModel from AsyncProcessModel. --- .../NotificationTester/NotificationTester.cpp | 47 +++--- .../NotificationTester/ProcessModel.h | 151 ++++++++++++++---- 2 files changed, 138 insertions(+), 60 deletions(-) diff --git a/DeviceAdapters/NotificationTester/NotificationTester.cpp b/DeviceAdapters/NotificationTester/NotificationTester.cpp index 57ca6aad7..548f7c978 100644 --- a/DeviceAdapters/NotificationTester/NotificationTester.cpp +++ b/DeviceAdapters/NotificationTester/NotificationTester.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include constexpr char DEVNAME_SYNC_PROPERTY[] = "NTSyncProperty"; @@ -36,9 +35,6 @@ constexpr char PROPNAME_TEST_PROPERTY[] = "TestProperty"; template class NTestProp : public CGenericBase> { - static constexpr bool isAsync_ = - std::is_same::value; - std::string name_; ProcModel model_; DelayedNotifier delayer_; @@ -49,15 +45,15 @@ class NTestProp : public CGenericBase> public: explicit NTestProp(std::string name) : name_(std::move(name)), - model_([this](double pv) { - this->LogMessage(("PV = " + std::to_string(pv)).c_str(), true); + model_([this](std::array pv) { + this->LogMessage(("PV = " + std::to_string(pv[0])).c_str(), true); { std::lock_guard lock(notificationMut_); if (!notificationsEnabled_) { return; } } - delayer_.Schedule([this, pv = std::to_string(pv)]{ + delayer_.Schedule([this, pv = std::to_string(pv[0])]{ this->LogMessage(("Notifying: PV = " + pv).c_str(), true); this->OnPropertyChanged(PROPNAME_TEST_PROPERTY, pv.c_str()); }); @@ -82,22 +78,22 @@ class NTestProp : public CGenericBase> this->AddAllowedValue("NotificationsEnabled", "Yes"); this->CreateFloatProperty(PROPNAME_TEST_PROPERTY, - model_.ProcessVariable(), false, + model_.ProcessVariable()[0], false, new MM::ActionLambda([this](MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) { - pProp->Set(model_.ProcessVariable()); + pProp->Set(model_.ProcessVariable()[0]); } else if (eAct == MM::AfterSet) { double value{}; pProp->Get(value); this->LogMessage( ("sp = " + std::to_string(value)).c_str(), true); - model_.Setpoint(value); + model_.Setpoint({value}); } return DEVICE_OK; })); - this->CreateFloatProperty("ExternallySet", model_.Setpoint(), false, + this->CreateFloatProperty("ExternallySet", model_.Setpoint()[0], false, new MM::ActionLambda([this](MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) { @@ -107,12 +103,12 @@ class NTestProp : public CGenericBase> pProp->Get(value); this->LogMessage(("sp = " + std::to_string(value) + " (external)").c_str(), true); - model_.Setpoint(value); + model_.Setpoint({value}); } return DEVICE_OK; })); - if (isAsync_) { + if (ProcModel::isAsync) { this->CreateFloatProperty("SlewTimePerUnit_s", model_.ReciprocalSlewRateSeconds(), false, new MM::ActionLambda([this](MM::PropertyBase* pProp, @@ -185,9 +181,6 @@ class NTestProp : public CGenericBase> template class NTestStage : public CStageBase> { - static constexpr bool isAsync_ = - std::is_same::value; - // The process model operates in steps (only set to integers; round upon // readout). static constexpr double umPerStep_ = 0.1; @@ -202,8 +195,8 @@ class NTestStage : public CStageBase> public: explicit NTestStage(std::string name) : name_(std::move(name)), - model_([this](double pv) { - long ipv = std::lround(pv); + model_([this](std::array pv) { + const long ipv = std::lround(pv[0]); this->LogMessage(("PV = " + std::to_string(ipv)).c_str(), true); { std::lock_guard lock(notificationMut_); @@ -242,7 +235,7 @@ class NTestStage : public CStageBase> this->AddAllowedValue("NotificationsEnabled", "Yes"); this->CreateIntegerProperty("ExternallySetSteps", - static_cast(model_.Setpoint()), false, + static_cast(model_.Setpoint()[0]), false, new MM::ActionLambda([this](MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) { @@ -252,12 +245,12 @@ class NTestStage : public CStageBase> pProp->Get(value); this->LogMessage(("sp = " + std::to_string(value) + " (external)").c_str(), true); - model_.Setpoint(value); + model_.Setpoint({double(value)}); } return DEVICE_OK; })); - if (isAsync_) { + if (ProcModel::isAsync) { this->CreateFloatProperty("SlewTimePerStep_s", model_.ReciprocalSlewRateSeconds(), false, new MM::ActionLambda([this](MM::PropertyBase* pProp, @@ -347,12 +340,12 @@ class NTestStage : public CStageBase> int SetPositionSteps(long steps) final { this->LogMessage(("sp = " + std::to_string(steps)).c_str(), true); - model_.Setpoint(steps); + model_.Setpoint({double(steps)}); return DEVICE_OK; } int GetPositionSteps(long& steps) final { - steps = std::lround(model_.ProcessVariable()); + steps = std::lround(model_.ProcessVariable()[0]); return DEVICE_OK; } @@ -394,13 +387,13 @@ MODULE_API MM::Device *CreateDevice(const char *deviceName) { const std::string name(deviceName); if (name == DEVNAME_SYNC_PROPERTY) - return new NTestProp(name); + return new NTestProp>(name); if (name == DEVNAME_ASYNC_PROPERTY) - return new NTestProp(name); + return new NTestProp>(name); if (name == DEVNAME_SYNC_STAGE) - return new NTestStage(name); + return new NTestStage>(name); if (name == DEVNAME_ASYNC_STAGE) - return new NTestStage(name); + return new NTestStage>(name); return nullptr; } diff --git a/DeviceAdapters/NotificationTester/ProcessModel.h b/DeviceAdapters/NotificationTester/ProcessModel.h index a2d5ac59c..f04abcd76 100644 --- a/DeviceAdapters/NotificationTester/ProcessModel.h +++ b/DeviceAdapters/NotificationTester/ProcessModel.h @@ -16,12 +16,16 @@ #pragma once +#include +#include #include #include #include #include +#include #include #include +#include #include #include @@ -38,12 +42,15 @@ // background thread during the slewing, with the last notification always // occuring when the PV reaches the SP. +template class SyncProcessModel { + using ValueType = std::array; + // Setpoint and process variable are always equal. - double value_ = 0.0; + ValueType value_{}; - std::function update_; - double lastUpdatedPV_ = 0.0; + std::function update_; + ValueType lastUpdatedPV_{}; void Update() { if (value_ == lastUpdatedPV_) { @@ -54,7 +61,9 @@ class SyncProcessModel { } public: - explicit SyncProcessModel(std::function updateFunc) : + static constexpr bool isAsync = false; + + explicit SyncProcessModel(std::function updateFunc) : update_(std::move(updateFunc)) { assert(update_); } @@ -81,27 +90,108 @@ class SyncProcessModel { bool IsSlewing() const { return false; } - double ProcessVariable() const { return value_; } + ValueType ProcessVariable() const { return value_; } - double Setpoint() const { return value_; } + ValueType Setpoint() const { return value_; } void Halt() { /* no-op */ } - void Setpoint(double setpoint) { - assert(std::isfinite(setpoint)); + void Setpoint(ValueType setpoint) { + assert(std::all_of(setpoint.cbegin(), setpoint.cend(), + [](double v) { return std::isfinite(v); })); value_ = setpoint; Update(); } }; +template +class SlewModel { + static_assert(Dim > 0, "Cannot handle empty value type"); + using ValueType = std::array; + + ValueType origin_; + ValueType target_; + ValueType displacement_; + std::array, Dim> rRate_; + std::chrono::duration duration_; + std::chrono::steady_clock::time_point startTime_; + decltype(startTime_ + duration_) finishTime_; + +public: + explicit SlewModel(ValueType origin, ValueType target, + std::chrono::duration absRRate) : + origin_(origin), target_(target), + displacement_([this] { + ValueType d; + std::transform(target_.cbegin(), target_.cend(), origin_.cbegin(), + d.begin(), [](auto a, auto b) { return a - b; }); + return d; + }()), + rRate_([this, absRRate] { + decltype(rRate_) rr; + std::transform(displacement_.cbegin(), displacement_.cend(), + rr.begin(), + [absRRate](auto d) { return d < 0.0 ? -absRRate : absRRate; }); + return rr; + }()), + duration_([this] { + std::array, Dim> dur; + std::transform(displacement_.cbegin(), displacement_.cend(), + rRate_.cbegin(), dur.begin(), + [](auto disp, auto rr) { return disp * rr; }); + return *std::max_element(dur.cbegin(), dur.cend()); + }()), + startTime_(std::chrono::steady_clock::now()), + finishTime_(startTime_ + duration_) + {} + + ValueType Origin() const { + return origin_; + } + + ValueType Target() const { + return target_; + } + + auto StartTime() const { + return startTime_; + } + + auto FinishTime() const { + return finishTime_; + } + + template + ValueType ValueAtTime(TimePoint tp) const { + const auto elapsed = tp - startTime_; + auto elemAtTime = [this, elapsed](std::size_t i) { + const double v = origin_[i] + elapsed / rRate_[i]; + if ((rRate_[i].count() > 0.0 && v > target_[i]) || + (rRate_[i].count() < 0.0 && v < target_[i])) { + return target_[i]; + } + return v; + }; + std::array indices; + std::iota(indices.begin(), indices.end(), 0); + ValueType value; + std::transform(indices.cbegin(), indices.cend(), value.begin(), + elemAtTime); + return value; + } +}; + +template class AsyncProcessModel { + using ValueType = std::array; + std::chrono::duration rRate_{1.0}; // Reciprocal rate, per PV unit std::chrono::duration updateInterval_{0.1}; // PV and SP protected by mut_ unless slewThread_ known not to be running mutable std::mutex mut_; - double procVar_ = 0.0; - double setpoint_ = 0.0; + ValueType procVar_{}; + ValueType setpoint_{}; std::thread slewThread_; std::mutex stopMut_; @@ -109,8 +199,8 @@ class AsyncProcessModel { bool stopRequested_ = false; std::mutex updateMut_; - std::function update_; - double lastUpdatedPV_ = 0.0; + std::function update_; + ValueType lastUpdatedPV_{}; // Only call from slew thread; cancel triggered by Halt() template @@ -131,7 +221,9 @@ class AsyncProcessModel { } public: - explicit AsyncProcessModel(std::function updateFunc) : + static constexpr bool isAsync = true; + + explicit AsyncProcessModel(std::function updateFunc) : update_(std::move(updateFunc)) { assert(update_); } @@ -170,12 +262,12 @@ class AsyncProcessModel { return procVar_ != setpoint_; } - double ProcessVariable() const { + ValueType ProcessVariable() const { std::lock_guard lock(mut_); return procVar_; } - double Setpoint() const { + ValueType Setpoint() const { std::lock_guard lock(mut_); return setpoint_; } @@ -195,30 +287,24 @@ class AsyncProcessModel { setpoint_ = procVar_; } - void Setpoint(double setpoint) { - assert(std::isfinite(setpoint)); + void Setpoint(ValueType setpoint) { + assert(std::all_of(setpoint.cbegin(), setpoint.cend(), + [](double v) { return std::isfinite(v); })); Halt(); // Cancel previous slew, if any, updating last PV setpoint_ = setpoint; if (procVar_ == setpoint_) { return; } - const auto orig = procVar_; + const auto slew = SlewModel(procVar_, setpoint, rRate_); stopRequested_ = false; - slewThread_ = std::thread( - [this, orig, setpoint, absRRate = rRate_, - updateInterval = updateInterval_] { - const auto displacement = setpoint - orig; - const auto rRate = displacement < 0.0 ? -absRRate : absRRate; - const auto duration = displacement * rRate; - const auto startTime = std::chrono::steady_clock::now(); - const auto finishTime = startTime + duration; - auto updateTime = startTime + updateInterval; + slewThread_ = std::thread([this, slew, dt = updateInterval_] { + auto updateTime = slew.StartTime() + dt; for (;;) { - if (finishTime <= updateTime) { - if (!CancelableWaitUntil(finishTime)) { + if (slew.FinishTime() <= updateTime) { + if (!CancelableWaitUntil(slew.FinishTime())) { std::lock_guard lock(mut_); - procVar_ = setpoint; + procVar_ = slew.Target(); } Update(); return; @@ -228,14 +314,13 @@ class AsyncProcessModel { Update(); return; } - const auto elapsed = updateTime - startTime; - const double pv = orig + elapsed / rRate; + const auto pv = slew.ValueAtTime(updateTime); { std::lock_guard lock(mut_); procVar_ = pv; } Update(); - updateTime += updateInterval; + updateTime += dt; } }); } From c53285e63ee26478e345e966a5dc800a17a0e235 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 10 Jan 2024 17:42:49 -0600 Subject: [PATCH 124/141] NotificationTester: Add XY stage simulation --- .../NotificationTester/NotificationTester.cpp | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) diff --git a/DeviceAdapters/NotificationTester/NotificationTester.cpp b/DeviceAdapters/NotificationTester/NotificationTester.cpp index 548f7c978..9683ac9c1 100644 --- a/DeviceAdapters/NotificationTester/NotificationTester.cpp +++ b/DeviceAdapters/NotificationTester/NotificationTester.cpp @@ -30,8 +30,34 @@ constexpr char DEVNAME_SYNC_PROPERTY[] = "NTSyncProperty"; constexpr char DEVNAME_ASYNC_PROPERTY[] = "NTAsyncProperty"; constexpr char DEVNAME_SYNC_STAGE[] = "NTSyncStage"; constexpr char DEVNAME_ASYNC_STAGE[] = "NTAsyncStage"; +constexpr char DEVNAME_SYNC_XY_STAGE[] = "NTSyncXYStage"; +constexpr char DEVNAME_ASYNC_XY_STAGE[] = "NTAsyncXYStage"; constexpr char PROPNAME_TEST_PROPERTY[] = "TestProperty"; +std::pair> ParseIntegerPair(const std::string& s) { + // Strings like " 100 ; -200 ", all spaces optional. + // (Not using ',' as delimiter as it is not allowed in property values.) + static constexpr char delimiter = ';'; + static constexpr auto invalid = std::make_pair(false, + std::array{}); + try { + std::size_t index{}; + const long x = std::stol(s, &index); + const auto delimPos = s.find_first_not_of(' ', index); + if (delimPos >= s.size() || s[delimPos] != delimiter) { + return invalid; + } + const std::string afterDelim = s.substr(delimPos + 1); + const long y = std::stol(afterDelim, &index); + if (afterDelim.find_first_not_of(' ', index) != std::string::npos) { + return invalid; + } + return {true, {x, y}}; + } catch (const std::exception&) { + return invalid; + } +} + template class NTestProp : public CGenericBase> { @@ -371,6 +397,221 @@ class NTestStage : public CStageBase> } }; +template +class NTestXYStage : public CXYStageBase> +{ + // The process model operates in steps (only set to integers; round upon + // readout). X and Y use the same step size. + static constexpr double umPerStep_ = 0.1; + std::string name_; + ProcModel model_; + DelayedNotifier delayer_; + + std::mutex notificationMut_; + bool notificationsEnabled_ = false; + +public: + explicit NTestXYStage(std::string name) : + name_(std::move(name)), + model_([this](std::array pv) { + const long ix = std::lround(pv[0]); + const long iy = std::lround(pv[1]); + this->LogMessage(("PV = " + std::to_string(ix) + ", " + + std::to_string(iy)).c_str(), true); + { + std::lock_guard lock(notificationMut_); + if (!notificationsEnabled_) { + return; + } + } + delayer_.Schedule([this, ix, iy] { + this->LogMessage(("Notifying: PV = " + std::to_string(ix) + ", " + + std::to_string(iy)).c_str(), true); + this->OnXYStagePositionChanged(umPerStep_* ix, umPerStep_* iy); + }); + }) + { + // Adjust default for stage-like velocity (100 um/s). + model_.ReciprocalSlewRateSeconds(0.01 * umPerStep_); + } + + int Initialize() final { + this->CreateFloatProperty("UmPerStep", umPerStep_, true); + + this->CreateStringProperty("NotificationsEnabled", + notificationsEnabled_ ? "Yes" : "No", false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(notificationsEnabled_ ? "Yes" : "No"); + } else if (eAct == MM::AfterSet) { + std::string value; + pProp->Get(value); + notificationsEnabled_ = (value == "Yes"); + } + return DEVICE_OK; + })); + this->AddAllowedValue("NotificationsEnabled", "No"); + this->AddAllowedValue("NotificationsEnabled", "Yes"); + + this->CreateStringProperty("ExternallySetSteps", "0; 0", false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + // Keep last-set value + } else if (eAct == MM::AfterSet) { + std::string commaSeparated; + pProp->Get(commaSeparated); + const auto okAndPair = ParseIntegerPair(commaSeparated); + if (!okAndPair.first) { + // Snap to current setpoint for next get + const auto sp = model_.Setpoint(); + pProp->Set((std::to_string(std::lround(sp[0])) + "; " + + std::to_string(std::lround(sp[1]))).c_str()); + return DEVICE_INVALID_PROPERTY_VALUE; + } + const auto sp = okAndPair.second; + this->LogMessage(("sp = " + std::to_string(sp[0]) + ", " + + std::to_string(sp[1])).c_str(), true); + model_.Setpoint({double(sp[0]), double(sp[1])}); + } + return DEVICE_OK; + })); + + if (ProcModel::isAsync) { + this->CreateFloatProperty("SlewTimePerStep_s", + model_.ReciprocalSlewRateSeconds(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(model_.ReciprocalSlewRateSeconds()); + } else if (eAct == MM::AfterSet) { + double seconds{}; + pProp->Get(seconds); + model_.ReciprocalSlewRateSeconds(seconds); + } + return DEVICE_OK; + })); + this->SetPropertyLimits("SlewTimePerStep_s", 0.0001, 10.0); + + this->CreateFloatProperty("UpdateInterval_s", + model_.UpdateIntervalSeconds(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(model_.UpdateIntervalSeconds()); + } else if (eAct == MM::AfterSet) { + double seconds{}; + pProp->Get(seconds); + model_.UpdateIntervalSeconds(seconds); + } + return DEVICE_OK; + })); + this->SetPropertyLimits("UpdateInterval_s", 0.0001, 10.0); + + using std::chrono::duration_cast; + using std::chrono::microseconds; + using FPSeconds = std::chrono::duration; + this->CreateFloatProperty("NotificationDelay_s", + duration_cast(delayer_.Delay()).count(), false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, + MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(duration_cast(delayer_.Delay()).count()); + } else if (eAct == MM::AfterSet) { + double seconds{}; + pProp->Get(seconds); + const auto delay = FPSeconds{seconds}; + delayer_.Delay(duration_cast(delay)); + } + return DEVICE_OK; + })); + this->SetPropertyLimits("NotificationDelay_s", 0.0, 1.0); + } + + return DEVICE_OK; + } + + int Shutdown() final { + model_.Halt(); + delayer_.CancelAll(); + return DEVICE_OK; + } + + bool Busy() final { + return model_.IsSlewing(); + } + + void GetName(char* name) const final { + CDeviceUtils::CopyLimitedString(name, name_.c_str()); + } + + int GetLimitsUm(double& xMin, double& xMax, double& yMin, double& yMax) final { + long xLo, xHi, yLo, yHi; + int ret = GetStepLimits(xLo, xHi, yLo, yHi); + if (ret != DEVICE_OK) { + return ret; + } + xMin = xLo * umPerStep_; + xMax = xHi * umPerStep_; + yMin = yLo * umPerStep_; + yMax = yHi * umPerStep_; + return DEVICE_OK; + } + + int SetPositionSteps(long x, long y) final { + this->LogMessage(("sp = " + std::to_string(x) + ", " + + std::to_string(y)).c_str(), true); + model_.Setpoint({double(x), double(y)}); + return DEVICE_OK; + } + + int GetPositionSteps(long& x, long& y) final { + const auto pv = model_.ProcessVariable(); + x = std::lround(pv[0]); + y = std::lround(pv[1]); + return DEVICE_OK; + } + + int Home() final { + // We could simulate homing, but it is not currently clear what + // notifications mean during a home (application code should explicitly + // read the position after a home). For now, pretend that the stage + // doesn't support homing. + return DEVICE_UNSUPPORTED_COMMAND; + } + + int Stop() final { + model_.Halt(); + return DEVICE_OK; + } + + int SetOrigin() final { + return DEVICE_UNSUPPORTED_COMMAND; + } + + int GetStepLimits(long& xMin, long& xMax, long& yMin, long& yMax) final { + // Conservatively keep steps within 32-bit range (we want exact integer + // steps to be preserved by the double-based process model). + xMin = yMin = -2147483648; + xMax = yMax = +2147483647; + return DEVICE_OK; + } + + double GetStepSizeXUm() final { + return umPerStep_; + } + + double GetStepSizeYUm() final { + return umPerStep_; + } + + int IsXYStageSequenceable(bool& flag) const final { + flag = false; + return DEVICE_OK; + } +}; + MODULE_API void InitializeModuleData() { RegisterDevice(DEVNAME_SYNC_PROPERTY, MM::GenericDevice, @@ -381,6 +622,10 @@ MODULE_API void InitializeModuleData() "Test synchronous stage position notifications"); RegisterDevice(DEVNAME_ASYNC_STAGE, MM::StageDevice, "Test asynchronous stage busy state and position notifications"); + RegisterDevice(DEVNAME_SYNC_XY_STAGE, MM::XYStageDevice, + "Test synchronous XY stage position notifications"); + RegisterDevice(DEVNAME_ASYNC_XY_STAGE, MM::XYStageDevice, + "Test asynchronous XY stage busy state and position notifications"); } MODULE_API MM::Device *CreateDevice(const char *deviceName) @@ -394,6 +639,10 @@ MODULE_API MM::Device *CreateDevice(const char *deviceName) return new NTestStage>(name); if (name == DEVNAME_ASYNC_STAGE) return new NTestStage>(name); + if (name == DEVNAME_SYNC_XY_STAGE) + return new NTestXYStage>(name); + if (name == DEVNAME_ASYNC_XY_STAGE) + return new NTestXYStage>(name); return nullptr; } From 8fbb80d524bbba86390d278e9074dbbc995f1bff Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 11 Jan 2024 17:54:09 -0600 Subject: [PATCH 125/141] Revert "Update MMTUCam.h" This reverts commit a97df73d3c54870d01e6c20303d789ccc19c6b51. --- DeviceAdapters/TUCam/MMTUCam.h | 44 ++++------------------------------ 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/DeviceAdapters/TUCam/MMTUCam.h b/DeviceAdapters/TUCam/MMTUCam.h index 662a53ecc..a6d6fac05 100755 --- a/DeviceAdapters/TUCam/MMTUCam.h +++ b/DeviceAdapters/TUCam/MMTUCam.h @@ -8,12 +8,12 @@ // microscope devices and enables testing of the rest of the // system without the need to connect to the actual hardware. // -// AUTHOR: fandayu, fandayu@tucsen.com 2024 +// AUTHOR: fandayu, fandayu@tucsen.com 2022 // // Karl Hoover (stuff such as programmable CCD size & the various image processors) // Arther Edelstein ( equipment error simulation) // -// COPYRIGHT: Tucsen Photonics Co., Ltd., 2024 +// COPYRIGHT: Tucsen Photonics Co., Ltd., 2022 // // // LICENSE: This file is distributed under the BSD license. @@ -91,13 +91,7 @@ const int SEVEN_SEGMENT_Y_OFFSET[] = {0, 0, 0, 1, 1, 1, 2}; #define DHYANA_4040BSI 0xE413 #define DHYANA_400BSIV3 0xE419 #define DHYANA_XF4040BSI 0xE41B -#define PID_FL_20 0xEC0D -#define PID_FL_9BW 0xE422 -#define PID_FL_9BW_LT 0xE426 -#define PID_FL_26BW 0xE423 -#define PID_ARIES16LT 0xE424 -#define PID_ARIES16 0xE425 /////////////////////// // ImgMode /////////////////////// @@ -257,11 +251,8 @@ class CMMTUCam : public CCameraBase int OnTestProperty(MM::PropertyBase* pProp, MM::ActionType eAct, long); int OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnBinningSum(MM::PropertyBase* pProp, MM::ActionType eAct); int OnPixelClock(MM::PropertyBase* pProp, MM::ActionType eAct); int OnExposure(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnBrightness(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnPixelRatio(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGlobalGain(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFrameRate(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSensorReset(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -279,14 +270,11 @@ class CMMTUCam : public CCameraBase int OnRollingScanReset(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTestImageMode(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnGlobalGainMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnShutterMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnModeSelect(MM::PropertyBase* pProp, MM::ActionType eAct); int OnImageMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnPixelType(MM::PropertyBase* pProp, MM::ActionType eAct); int OnBitDepth(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnBitDepthEum(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFlipH(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFlipV(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGamma(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -297,9 +285,7 @@ class CMMTUCam : public CCameraBase int OnRedGain(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGreenGain(MM::PropertyBase* pProp, MM::ActionType eAct); int OnBlueGain(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnATExpMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnATExposure(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnTimeStamp(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTemperature(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFan(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFanState(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -310,9 +296,7 @@ class CMMTUCam : public CCameraBase int OnTriggerExpMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerEdgeMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerDelay(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnTriggerFilter(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerFrames(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnTriggerTotalFrames(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerDoSoftware(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSharpness(MM::PropertyBase* pProp, MM::ActionType eAct); int OnDPCLevel(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -343,9 +327,7 @@ class CMMTUCam : public CCameraBase private: - int SetAllowedDepth(); int SetAllowedBinning(); - int SetAllowedBinningSum(); int SetAllowedPixelClock(); int SetAllowedFanGear(); @@ -356,21 +338,12 @@ class CMMTUCam : public CCameraBase int SetAllowedRSReset(); int SetAllowedTestImg(); int SetAllowedClrTemp(); - int SetAllowedShutterMode(); void TestResourceLocking(const bool); void GenerateEmptyImage(ImgBuffer& img); void GenerateSyntheticImage(ImgBuffer& img, double exp); int ResizeImageBuffer(); - void ResizeBinImageBufferFL9BW(int &width, int &height); - void ResizeBinImageBufferFL26BW(int &width, int &height); - - bool IsSupport95V2New() { return DHYANA_D95_V2 == m_nPID && m_nBCD >= 0x2000; } - bool IsSupport401DNew() { return DHYANA_401D == m_nPID && m_nBCD >= 0x2000; } - bool IsSupport400BSIV3New() { return DHYANA_400BSIV3 == m_nPID && m_nBCD >= 0x2000; } - bool IsSupportAries16() { return 0xE424 == m_nPID || 0xE425 == m_nPID; } - static const double nominalPixelSizeUm_; double exposureMaximum_; @@ -437,17 +410,12 @@ class CMMTUCam : public CCameraBase int WaitForFrame(ImgBuffer& img); bool SaveRaw(char *pfileName, unsigned char *pData, unsigned long ulSize); - bool isSupportFanCool(); bool isSupportFanWaterCool(); - bool isSupportSoftProtect(); + bool isSupportFanCool(); void UpdateSlitHeightRange(); void UpdateExpRange(); - void UpdateLevelsRange(); -/* - void LoadProfile(); - void SaveProfile(); -*/ + static int s_nNumCam; // The number of cameras static int s_nCntCam; // The count of camera @@ -457,12 +425,10 @@ class CMMTUCam : public CCameraBase int m_nIdxGain; // The gain mode int m_nMaxHeight; // The max height size int m_nTriType; // The trigger type - int m_nZeroTemp; // The zero temperature reference value char m_szImgPath[MAX_PATH]; // The save image path float m_fCurTemp; // The current temperature - float m_fValTemp; // The current temperature - float m_fScaTemp; // The scale of temperature + float m_fValTemp; // The temperature value int m_nMidTemp; // The middle value of temperature bool m_bROI; // The ROI state bool m_bSaving; // The tag of save image From 4a373b4a09d70c51cbfb4b05ea48bd25e6d53d55 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 11 Jan 2024 17:54:26 -0600 Subject: [PATCH 126/141] Revert "Update MMTUCam.cpp" This reverts commit c3a57c4a4dd763d980c8da94c60290c59c625579. --- DeviceAdapters/TUCam/MMTUCam.cpp | 1550 ++++-------------------------- 1 file changed, 163 insertions(+), 1387 deletions(-) diff --git a/DeviceAdapters/TUCam/MMTUCam.cpp b/DeviceAdapters/TUCam/MMTUCam.cpp index 403834f64..b5b0e9db7 100755 --- a/DeviceAdapters/TUCam/MMTUCam.cpp +++ b/DeviceAdapters/TUCam/MMTUCam.cpp @@ -8,9 +8,9 @@ // microscope devices and enables testing of the rest of the // system without the need to connect to the actual hardware. // -// AUTHOR: fandayu, fandayu@tucsen.com 2024 +// AUTHOR: fandayu, fandayu@tucsen.com 2022 // -// COPYRIGHT: Tucsen Photonics Co., Ltd., 2024 +// COPYRIGHT: Tucsen Photonics Co., Ltd., 2022 // LICENSE: This file is distributed under the BSD license. // License text is included with the source distribution. // @@ -70,14 +70,11 @@ const char* g_PropNameLED = "LEDMode"; const char* g_PropNameTEC = "TECMode"; const char* g_PropNamePI = "PIMode"; const char* g_PropNameDepth = "DepthMode"; -const char* g_PropNameShutter = "Shutter Mode"; const char* g_PropNameMdTgr = "Trigger Mode"; const char* g_PropNameMdExp = "Trigger Exposure Mode"; const char* g_PropNameMdEdg = "Trigger Edge Mode"; const char* g_PropNameMdDly = "Trigger Delay"; -const char* g_PropNameFilter= "Signal Filter"; const char* g_PropNameMdFrames = "Trigger Frames"; -const char* g_PropNameMdTFrames = "Trigger Total Frames"; const char* g_PropNameDoSFW = "Trigger Software Do"; const char* g_PropNameSharp = "Image Adjustment Sharpness"; const char* g_PropNameDPC = "Image Adjustment DPC"; @@ -100,13 +97,6 @@ const char* g_PropNameFrameRate = "Frame Rate"; const char* g_PropNameTestImg = "Test Image"; -const char* g_PropNameBrightness = "Targeting Level"; //"Brightness"; -const char* g_PropNamePixelRatio = "Metering Level"; //"Pixel Ratio"; -const char* g_PropNameImgMetadata= "Image Metadata"; -const char* g_PropNameATExpMode = "ATExposure Mode"; - -const char* g_PropNameBinningSum = "Binning Sum"; - const char* g_DeviceName = "Dhyana"; //"400Li" const char* g_SdkName = "TUCam"; //const char* g_SdkName = "CamCore"; @@ -158,8 +148,6 @@ const char* g_GLOBALRESET_ON = "Global Reset"; /// GlobalReset Hg const char* g_TRIGGER_OFF = "Off"; const char* g_TRIGGER_STD = "Standard"; -const char* g_TRIGGER_STDOVERLAP = "Standard(Overlap)"; -const char* g_TRIGGER_STDNONOVERLAP = "Standard(Non-Overlap)"; const char* g_TRIGGER_CC1 = "CC1"; const char* g_TRIGGER_SYN = "Synchronization"; const char* g_TRIGGER_GLB = "Global"; @@ -172,7 +160,6 @@ const char* g_TRIGGER_PORT3 = "3"; const char* g_TRIGGER_EXPSTART = "Exposure Start"; const char* g_TRIGGER_READEND = "Readout End"; const char* g_TRIGGER_GLBEXP = "Global Exposure"; -const char* g_TRIGGER_TRIREADY = "Trigger Ready"; const char* g_TRIGGER_LOW = "Low"; const char* g_TRIGGER_HIGH = "High"; @@ -305,7 +292,6 @@ CMMTUCam::CMMTUCam() : m_frame.ucFormatGet = TUFRM_FMT_USUAl; m_frame.pBuffer = NULL; - m_nZeroTemp = 50; m_nPID = 0; m_nBCD = 0; m_nIdxGain = 0; @@ -468,13 +454,6 @@ int CMMTUCam::Initialize() m_nBCD = valInfo.nValue; } - // Get Zero Temperature Value - valInfo.nID = TUIDI_ZEROTEMPERATURE_VALUE; - if (TUCAMRET_SUCCESS == TUCAM_Dev_GetInfo(m_opCam.hIdxTUCam, &valInfo)) - { - m_nZeroTemp = valInfo.nValue; - } - // Get camera type valInfo.nID = TUIDI_PRODUCT; if (TUCAMRET_SUCCESS == TUCAM_Dev_GetInfo(m_opCam.hIdxTUCam, &valInfo)) @@ -498,7 +477,7 @@ int CMMTUCam::Initialize() m_bTriEn = false; } - if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID || PID_FL_20BW == m_nPID || DHYANA_4040V2 == m_nPID || DHYANA_4040BSI == m_nPID || DHYANA_XF4040BSI == m_nPID) + if (PID_FL_20BW == m_nPID || DHYANA_4040V2 == m_nPID || DHYANA_4040BSI == m_nPID || DHYANA_XF4040BSI == m_nPID) { m_bOffsetEn = true; } @@ -540,30 +519,16 @@ int CMMTUCam::Initialize() if (nRet != DEVICE_OK) return nRet; - if (PID_FL_26BW == m_nPID) - { - // binning sum - pAct = new CPropertyAction(this, &CMMTUCam::OnBinningSum); - nRet = CreateProperty(g_PropNameBinningSum, "", MM::String, false, pAct); - assert(nRet == DEVICE_OK); - - nRet = SetAllowedBinningSum(); - if (nRet != DEVICE_OK) - return nRet; - } - // Bit depth capaAttr.idCapa = TUIDC_BITOFDEPTH; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) { if(capaAttr.nValMax != capaAttr.nValMin && m_nPID != DHYANA_D95_X100) { - if (capaAttr.nValMax > 8) + if (DHYANA_400DC_X100 == m_nPID || DHYANA_400DC_X45 == m_nPID) { - if (DHYANA_400DC_X100 == m_nPID || DHYANA_400DC_X45 == m_nPID) - { - TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_BITOFDEPTH, 8); - } + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_BITOFDEPTH, 8); + } pAct = new CPropertyAction (this, &CMMTUCam::OnBitDepth); nRet = CreateProperty(g_PropNameBODP, "8", MM::String, false, pAct); @@ -573,18 +538,9 @@ int CMMTUCam::Initialize() bitDepths.push_back("8"); bitDepths.push_back("16"); - nRet = SetAllowedValues(g_PropNameBODP, bitDepths); - if (nRet != DEVICE_OK) - return nRet; - } - else - { - pAct = new CPropertyAction(this, &CMMTUCam::OnBitDepthEum); - nRet = CreateProperty(g_PropNameBODP, "", MM::String, false, pAct); - SetAllowedDepth(); - if (nRet != DEVICE_OK) - return nRet; - } + nRet = SetAllowedValues(g_PropNameBODP, bitDepths); + if (nRet != DEVICE_OK) + return nRet; } } @@ -613,30 +569,13 @@ int CMMTUCam::Initialize() assert(nRet == DEVICE_OK); UpdateExpRange(); - } - - // Brightness - propAttr.nIdxChn = 0; - propAttr.idProp = TUIDP_BRIGHTNESS; - if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) - { - pAct = new CPropertyAction(this, &CMMTUCam::OnBrightness); - nRet = CreateProperty(g_PropNameBrightness, "0", MM::Integer, false, pAct); - assert(nRet == DEVICE_OK); - - SetPropertyLimits(g_PropNameBrightness, propAttr.dbValMin, propAttr.dbValMax); - } - - // PixelRatio - propAttr.nIdxChn = 0; - propAttr.idProp = TUIDP_PIXELRATIO; - if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) - { - pAct = new CPropertyAction(this, &CMMTUCam::OnPixelRatio); - nRet = CreateProperty(g_PropNamePixelRatio, "0", MM::Integer, false, pAct); - assert(nRet == DEVICE_OK); - - SetPropertyLimits(g_PropNamePixelRatio, propAttr.dbValMin, propAttr.dbValMax); + /*exposureMaximum_ = propAttr.dbValMax; + exposureMinimum_ = propAttr.dbValMin; + if(m_nPID == PID_FL_20BW) + { + exposureMaximum_ = 3600000; + } + SetPropertyLimits(MM::g_Keyword_Exposure, exposureMinimum_, exposureMaximum_);*/ } // Global Gain @@ -654,38 +593,9 @@ int CMMTUCam::Initialize() } else { - if (m_nPID == PID_FL_9BW || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID || IsSupportAries16()) - { - int nCnt = (int)(propAttr.dbValMax - propAttr.dbValMin + 1); - - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - valText.nID = TUIDP_GLOBALGAIN; - - vector gainValues; - - for (int i = 0; i StampValues; - StampValues.push_back("FALSE"); - StampValues.push_back("TRUE"); - - nRet = SetAllowedValues(g_PropNameImgMetadata, StampValues); - if (nRet != DEVICE_OK) - return nRet; - } - } - // Sensor reset capaAttr.idCapa = TUIDC_SENSORRESET; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) @@ -729,17 +619,6 @@ int CMMTUCam::Initialize() AddAllowedValue(g_PropNameReset, g_PropNameReset); } - // Auto Exposure Mode - capaAttr.idCapa = TUIDC_ATEXPOSURE_MODE; - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) - { - pAct = new CPropertyAction(this, &CMMTUCam::OnATExpMode); - nRet = CreateProperty(g_PropNameATExpMode, "0", MM::Integer, false, pAct); - assert(nRet == DEVICE_OK); - - SetPropertyLimits(g_PropNameATExpMode, capaAttr.nValMin, capaAttr.nValMax); - } - // Auto Exposure capaAttr.idCapa = TUIDC_ATEXPOSURE; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) @@ -790,18 +669,7 @@ int CMMTUCam::Initialize() if (nRet != DEVICE_OK) return nRet; } - - // Shutter - if (PID_FL_26BW == m_nPID) - { - pAct = new CPropertyAction(this, &CMMTUCam::OnShutterMode); - nRet = CreateProperty(g_PropNameShutter, "", MM::String, false, pAct); - assert(nRet == DEVICE_OK); - - nRet = SetAllowedShutterMode(); - if (nRet != DEVICE_OK) - return nRet; - } + // Gamma propAttr.nIdxChn= 0; @@ -928,30 +796,12 @@ int CMMTUCam::Initialize() if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr) && m_bTempEn) // For 401D 201D disable temperature { pAct = new CPropertyAction (this, &CMMTUCam::OnTemperature); + nRet = CreateProperty(g_PropNameTEMP, "0", MM::Integer, false, pAct); - if (propAttr.dbValMax > 100) - { - m_fScaTemp = 10; - nRet = CreateProperty(g_PropNameTEMP, "0.0", MM::Float, false, pAct); - } - else - { - m_fScaTemp = 1; - nRet = CreateProperty(g_PropNameTEMP, "0", MM::Integer, false, pAct); - } + m_nMidTemp = (int)((propAttr.dbValMax - propAttr.dbValMin) / 2); + SetPropertyLimits(g_PropNameTEMP, -m_nMidTemp, m_nMidTemp); + char sz[64] = { 0 }; - m_nMidTemp = m_nZeroTemp; - if (PID_FL_9BW_LT == m_nPID) - { - ///m_nMidTemp = 500; - SetPropertyLimits(g_PropNameTEMP, (propAttr.dbValMin - m_nMidTemp) / m_fScaTemp, (propAttr.dbValMax - m_nMidTemp) / m_fScaTemp); - } - else - { - SetPropertyLimits(g_PropNameTEMP, -m_nMidTemp / m_fScaTemp, m_nMidTemp / m_fScaTemp); - } - - char sz[64] = { 0 }; switch (m_nPID) { case PID_FL_20BW: @@ -962,8 +812,8 @@ int CMMTUCam::Initialize() SetProperty(g_PropNameTEMP, sz); } break; -// case DHYANA_400BSIV2: -// case DHYANA_400BSIV3: + case DHYANA_400BSIV2: + case DHYANA_400BSIV3: case DHYANA_D95_V2: case DHYANA_4040V2: case DHYANA_4040BSI: @@ -974,21 +824,11 @@ int CMMTUCam::Initialize() break; default: // Set default temperature - double dblTemp; - if (TUCAMRET_SUCCESS == TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE_TARGET, &dblTemp)) - { - sprintf(sz, "%.1f", (dblTemp - m_nMidTemp) / m_fScaTemp); - SetProperty(g_PropNameTEMP, sz); - } - else - { - if (TUCAMRET_SUCCESS == TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, 40.0f)) - { - sprintf(sz, "%d", -10); - SetProperty(g_PropNameTEMP, sz); - } - } - + if (TUCAMRET_SUCCESS == TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, 40.0f)) + { + sprintf(sz, "%d", -10); + SetProperty(g_PropNameTEMP, sz); + } break; } @@ -1035,17 +875,11 @@ int CMMTUCam::Initialize() capaAttr.idCapa = TUIDC_IMGMODESELECT; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) { - if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) - { - nRet = SetAllowedGainMode(); - } - else - { - int nImgMode = capaAttr.nValMax - capaAttr.nValMin + 1; - - if ((nImgMode < 0x3) && (capaAttr.nValMax < 0x02) && PID_FL_9BW != m_nPID && PID_FL_9BW_LT != m_nPID) - { - pAct = new CPropertyAction(this, &CMMTUCam::OnCMSMode); + int nImgMode = capaAttr.nValMax - capaAttr.nValMin +1; + + if((nImgMode < 0x3) && (capaAttr.nValMax < 0x02)) + { + pAct = new CPropertyAction (this, &CMMTUCam::OnCMSMode); nRet = CreateProperty(g_PropNameCMS, g_CMS_ON, MM::String, false, pAct); assert(nRet == DEVICE_OK); @@ -1054,11 +888,9 @@ int CMMTUCam::Initialize() CMSValues.push_back(g_CMS_OFF); CMSValues.push_back(g_CMS_ON); - nRet = SetAllowedValues(g_PropNameCMS, CMSValues); - - } - } + nRet = SetAllowedValues(g_PropNameCMS, CMSValues); + } if (nRet != DEVICE_OK) return nRet; } @@ -1223,31 +1055,17 @@ int CMMTUCam::Initialize() vectorModTgrValues; ModTgrValues.push_back(g_TRIGGER_OFF); - - if (IsSupport95V2New() || IsSupport401DNew() || IsSupport400BSIV3New()) - { - ModTgrValues.push_back(g_TRIGGER_STDOVERLAP); - ModTgrValues.push_back(g_TRIGGER_STDNONOVERLAP); - } - else - { - ModTgrValues.push_back(g_TRIGGER_STD); - } + ModTgrValues.push_back(g_TRIGGER_STD); int nImgMode = 0; switch (m_nPID) { - case PID_FL_9BW: - case PID_FL_9BW_LT: - case PID_FL_26BW: case PID_FL_20BW: case DHYANA_401D: case DHYANA_201D: case DHYANA_4040V2: case DHYANA_4040BSI: case DHYANA_XF4040BSI: - case PID_ARIES16LT: - case PID_ARIES16: { if (TU_PHXCAMERALINK == m_nDriverType) { @@ -1312,25 +1130,7 @@ int CMMTUCam::Initialize() nRet = CreateProperty(g_PropNameMdDly, "0", MM::Integer, false, pAct); assert(nRet == DEVICE_OK); - // Trigger Filter - capaAttr.idCapa = TUIDC_SIGNALFILTER; - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) - { - pAct = new CPropertyAction(this, &CMMTUCam::OnTriggerFilter); - nRet = CreateProperty(g_PropNameFilter, "0", MM::Integer, false, pAct); - assert(nRet == DEVICE_OK); - SetPropertyLimits(g_PropNameFilter, 1, 1000000); - } - - if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID) - { - // Trigger total frames - pAct = new CPropertyAction(this, &CMMTUCam::OnTriggerTotalFrames); - nRet = CreateProperty(g_PropNameMdTFrames, "1", MM::Integer, false, pAct); - assert(nRet == DEVICE_OK); - } - - SetPropertyLimits(g_PropNameMdTFrames, 1, 0xFFFF); + SetPropertyLimits(g_PropNameMdDly, 0, 10000000); if (TUCAMRET_SUCCESS == TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_ROLLINGSCANMODE, &m_rsPara.nMode)) { @@ -1339,7 +1139,7 @@ int CMMTUCam::Initialize() nRet = CreateProperty(g_PropNameMdFrames, "1", MM::Integer, false, pAct); assert(nRet == DEVICE_OK); - SetPropertyLimits(g_PropNameMdFrames, 1, 0xFFFF); + SetPropertyLimits(g_PropNameMdFrames, 1, 65535); } } } @@ -1524,10 +1324,6 @@ int CMMTUCam::Initialize() ModKindValues.push_back(g_TRIGGER_EXPSTART); ModKindValues.push_back(g_TRIGGER_READEND); ModKindValues.push_back(g_TRIGGER_GLBEXP); - if (IsSupport95V2New() || IsSupport401DNew() || IsSupport400BSIV3New()) - { - ModKindValues.push_back(g_TRIGGER_TRIREADY); - } ModKindValues.push_back(g_TRIGGER_LOW); ModKindValues.push_back(g_TRIGGER_HIGH); @@ -1728,12 +1524,10 @@ int CMMTUCam::SnapImage() StartCapture(); } - int nRet = DEVICE_ERR; - if (TUCCM_TRIGGER_SOFTWARE == m_tgrAttr.nTgrMode) { int nCnt = 0; - ///int nRet = DEVICE_ERR; + int nRet = DEVICE_ERR; do { @@ -1752,7 +1546,7 @@ int CMMTUCam::SnapImage() CDeviceUtils::SleepMs((long) exp); } ///TUDBG_PRINTF("SanpImage fastImage_ = %d",fastImage_); - nRet = WaitForFrame(img_); + WaitForFrame(img_); } } @@ -1763,7 +1557,7 @@ int CMMTUCam::SnapImage() StopCapture(); } - return nRet/*DEVICE_OK*/; + return DEVICE_OK; } @@ -1936,7 +1730,8 @@ int CMMTUCam::SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) roiY_ = y; m_bROI = true; -// StartCapture(); + + StartCapture(); ResizeImageBuffer(); Sleep(2); @@ -1987,7 +1782,7 @@ int CMMTUCam::ClearROI() roiY_ = 0; m_bROI = false; -// StartCapture(); + StartCapture(); ResizeImageBuffer(); return DEVICE_OK; @@ -2206,39 +2001,6 @@ int CMMTUCam::LineIntervalCal(int nVal, bool bExpChange) return nStep; } -int CMMTUCam::SetAllowedDepth() -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - TUCAM_CAPA_ATTR capaAttr; - capaAttr.idCapa = TUIDC_BITOFDEPTH; - - if (TUCAMRET_SUCCESS != TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) - { - return DEVICE_NOT_SUPPORTED; - } - - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nID = TUIDC_BITOFDEPTH; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - - vector depthValues; - int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; - - for (int i = 0; i binValues; - int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; - - for (int i = 0; i modeValues; - int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; - - for (int i = 0; i shutters; - - for (int i = 0; iIsStopped() /*&& !returnToSoftwareTriggers_*/) - { - if (m_bLiving) - { - m_bAcquisition = false; - StopCapture(); - } - return DEVICE_OK; - } - + OutputDebugString("[StopSequenceAcquisition]:Enter\n"); + + if (thd_->IsStopped() && !returnToSoftwareTriggers_) + return DEVICE_OK; + m_bLiving = false; if (NULL == m_opCam.hIdxTUCam) @@ -2784,13 +2423,13 @@ int CMMTUCam::StopSequenceAcquisition() ReleaseBuffer(); // Switch back to software trigger mode if that is what the user used - /*if (returnToSoftwareTriggers_) + if (returnToSoftwareTriggers_) { TUCAM_Cap_GetTrigger(m_opCam.hIdxTUCam, &m_tgrAttr); m_tgrAttr.nTgrMode = TUCCM_TRIGGER_SOFTWARE; TUCAM_Cap_SetTrigger(m_opCam.hIdxTUCam, m_tgrAttr); returnToSoftwareTriggers_ = false; - }*/ + } m_bAcquisition = false; ///StartCapture(); // avoid wasting time in the SnapImage function @@ -2906,22 +2545,18 @@ int CMMTUCam::RunSequenceOnThread(MM::MMTime startTime) } ret = WaitForFrame(img_); - /* if (!fastImage_) { GenerateSyntheticImage(img_, GetSequenceExposure()); } */ - if (DEVICE_OK == ret) - { - ret = InsertImage(); - } - - /* while (((double) (this->GetCurrentMMTime() - startTime).getMsec() / imageCounter_) < this->GetSequenceExposure()) + ret = InsertImage(); + + while (((double) (this->GetCurrentMMTime() - startTime).getMsec() / imageCounter_) < this->GetSequenceExposure()) { CDeviceUtils::SleepMs(1); - }*/ + } if (ret != DEVICE_OK) { @@ -3094,10 +2729,9 @@ int CMMTUCam::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) valText.nTextSize = 64; valText.pText = &szBuf[0]; - int i = 0; int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; - for (; i 0) - { - valText.dbValue = nIdx; - valText.nID = TUIDC_BINNING_SUM; - - TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); - pProp->Set(valText.pText); - } - else - { - valText.dbValue = 0; - valText.nID = TUIDC_RESOLUTION; - - TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); - pProp->Set(valText.pText); - } - } - else - { - int nIdx = 0; - TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_RESOLUTION, &nIdx); - - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nID = TUIDC_RESOLUTION; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - - valText.dbValue = nIdx; - TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); - pProp->Set(valText.pText); - } - - ret = DEVICE_OK; - } - break; - default: - break; - } - return ret; -} - -/** -* Handles "BinningSum" property. -*/ -int CMMTUCam::OnBinningSum(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - string val; - pProp->Get(val); - - if (val.length() != 0) - { - TUCAM_CAPA_ATTR capaAttr; - capaAttr.idCapa = TUIDC_BINNING_SUM; - - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) - { - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nID = TUIDC_BINNING_SUM; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - - int nCnt = (int)(capaAttr.nValMax - capaAttr.nValMin + 1); - - for (int i = 0; iSet(valText.pText); ret = DEVICE_OK; } - } - break; - case MM::BeforeGet: - { - int val = 0; - TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BINNING_SUM, &val); - - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nID = TUIDC_BINNING_SUM; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - - valText.dbValue = val; - TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); - - pProp->Set(valText.pText); - - ret = DEVICE_OK; - } - break; + break; default: break; } @@ -3438,84 +2911,6 @@ int CMMTUCam::OnExposure(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } -/** -* Handles "Brightness" property. -*/ -int CMMTUCam::OnBrightness(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - double dbVal = 0.0f; - pProp->Get(dbVal); - - TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_BRIGHTNESS, dbVal); - - ret = DEVICE_OK; - } - break; - case MM::BeforeGet: - { - double dbVal = 0.0f; - - TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_BRIGHTNESS, &dbVal); - - pProp->Set(dbVal); - - ret = DEVICE_OK; - } - break; - default: - break; - } - - return ret; -} - -/** -* Handles "OnPixelRatio" property. -*/ -int CMMTUCam::OnPixelRatio(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - double dbVal = 0.0f; - pProp->Get(dbVal); - - TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_PIXELRATIO, dbVal); - - ret = DEVICE_OK; - } - break; - case MM::BeforeGet: - { - double dbVal = 0.0f; - - TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_PIXELRATIO, &dbVal); - - pProp->Set(dbVal); - - ret = DEVICE_OK; - } - break; - default: - break; - } - - return ret; -} - /** * Handles "GlobalGain" property. */ @@ -3567,20 +2962,20 @@ int CMMTUCam::OnFrameRate(MM::PropertyBase* pProp, MM::ActionType eAct) { case MM::AfterSet: { - double dblRate; - pProp->Get(dblRate); + double dblGain; + pProp->Get(dblGain); - TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, dblRate); + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, dblGain); ret = DEVICE_OK; } break; case MM::BeforeGet: { - double dblRate = 0.0f; + double dblGain = 0.0f; - TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, &dblRate); - pProp->Set(dblRate); + TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, &dblGain); + pProp->Set(dblGain); ret = DEVICE_OK; } @@ -4416,94 +3811,6 @@ int CMMTUCam::OnTestImageMode(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } -/** -* Handles "OnGlobalGainMode" property. -*/ -int CMMTUCam::OnGlobalGainMode(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - if (IsCapturing()) - return DEVICE_CAMERA_BUSY_ACQUIRING; - - // the user just set the new value for the property, so we have to - // apply this value to the 'hardware'. - - string val; - pProp->Get(val); - - if (val.length() != 0) - { - TUCAM_PROP_ATTR propAttr; - propAttr.nIdxChn = 0; - propAttr.idProp = TUIDP_GLOBALGAIN; - - if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) - { - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nID = TUIDP_GLOBALGAIN; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - - int nCnt = (int)(propAttr.dbValMax - propAttr.dbValMin + 1); - - for (int i = 0; iSet(valText.pText); - - ret = DEVICE_OK; - } - break; - default: - break; - } - - return ret; -} - /** * Handles "OnGAINMode" property. */ @@ -4517,88 +3824,42 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) { case MM::AfterSet: { - if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) - { - string val; - pProp->Get(val); - - if (val.length() != 0) - { - TUCAM_CAPA_ATTR capaAttr; - capaAttr.idCapa = TUIDC_IMGMODESELECT; - - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) - { - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nID = TUIDC_IMGMODESELECT; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - - int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; - - for (int i = 0; i < nCnt; i++) - { - valText.dbValue = i; - TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); - - if (0 == val.compare(valText.pText)) - { - TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, i); - break; - } - } - } - - OnPropertyChanged(g_PropNameMode, val.c_str()); - - double val = 0; - TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_GLOBALGAIN, &val); - SetProperty(g_PropNameGain, CDeviceUtils::ConvertToString((int)val)); - - UpdateExpRange(); - - ret = DEVICE_OK; - } - } - else + int nVal = 0; + int nImgMode = 0; + int nGain = 0; + double dblExp = 0.0; + string val; + pProp->Get(val); + if (val.length() != 0) { - int nVal = 0; - int nImgMode = 0; - int nGain = 0; - double dblExp = 0.0; - string val; - pProp->Get(val); - if (val.length() != 0) - { - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, &nVal)) - { - if (DHYANA_D95_V2 == m_nPID) - { - bool bLiving = m_bLiving; - if (0 == val.compare(g_HDRBIT_ON)) - { - nImgMode = 0; //HDR - nGain = 0; - } - else if (0 == val.compare(g_HIGHBIT_ON)) - { - nImgMode = 0; //HIGH - nGain = 1; - } - else if (0 == val.compare(g_LOWBIT_ON)) - { - nImgMode = 0; //LOW - nGain = 2; - } - else if (0 == val.compare(g_STDHIGH_ON)) - { - nImgMode = 1; //STDH - } - else if (0 == val.compare(g_STDLOW_ON)) - { - nImgMode = 2; //STDL - } + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, &nVal)) + { + if (DHYANA_D95_V2 == m_nPID) + { + bool bLiving = m_bLiving; + if (0 == val.compare(g_HDRBIT_ON)) + { + nImgMode = 0; //HDR + nGain = 0; + } + else if(0 == val.compare(g_HIGHBIT_ON)) + { + nImgMode = 0; //HIGH + nGain = 1; + } + else if(0 == val.compare(g_LOWBIT_ON)) + { + nImgMode = 0; //LOW + nGain = 2; + } + else if(0 == val.compare(g_STDHIGH_ON)) + { + nImgMode = 1; //STDH + } + else if(0 == val.compare(g_STDLOW_ON)) + { + nImgMode = 2; //STDL + } if (nImgMode != nVal) { @@ -4656,28 +3917,27 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) UpdateExpRange(); - if ((DHYANA_400BSIV2 == m_nPID && m_nBCD > 0x04)) - { - vectorModTgrValues; - ModTgrValues.push_back(g_TRIGGER_OFF); - if (0 == val.compare(g_GRLOW_ON) || 0 == val.compare(g_GRHIGH_ON)) - { - ModTgrValues.push_back(g_TRIGGER_STD); - } - else - { - ModTgrValues.push_back(g_TRIGGER_STD); - ModTgrValues.push_back(g_TRIGGER_SYN); - } - ModTgrValues.push_back(g_TRIGGER_SWF); - ClearAllowedValues(g_PropNameMdTgr); - SetAllowedValues(g_PropNameMdTgr, ModTgrValues); - } - } - } - - ret = DEVICE_OK; - } + if ((DHYANA_400BSIV2 == m_nPID && m_nBCD > 0x04)) + { + vectorModTgrValues; + ModTgrValues.push_back(g_TRIGGER_OFF); + if (0 == val.compare(g_GRLOW_ON) || 0 == val.compare(g_GRHIGH_ON)) + { + ModTgrValues.push_back(g_TRIGGER_STD); + } + else + { + ModTgrValues.push_back(g_TRIGGER_STD); + ModTgrValues.push_back(g_TRIGGER_SYN); + } + ModTgrValues.push_back(g_TRIGGER_SWF); + ClearAllowedValues(g_PropNameMdTgr); + SetAllowedValues(g_PropNameMdTgr, ModTgrValues); + } + } + } + + ret = DEVICE_OK; } } break; @@ -4706,22 +3966,6 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) pProp->Set(g_HDRBIT_ON); } } - else if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) - { - int nIdx = 0; - TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, &nIdx); - - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nID = TUIDC_IMGMODESELECT; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - - valText.dbValue = nIdx; - TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); - - pProp->Set(valText.pText); - } else { if (1 == nVal) @@ -4752,82 +3996,6 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } -/** -* Handles "OnShutterMode" property. -*/ -int CMMTUCam::OnShutterMode(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - string val; - pProp->Get(val); - - if (val.length() != 0) - { - TUCAM_CAPA_ATTR capaAttr; - capaAttr.idCapa = TUIDC_SHUTTER; - - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) - { - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nID = TUIDC_SHUTTER; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - - int nCnt = (int)(capaAttr.nValMax - capaAttr.nValMin + 1); - - for (int i = 0; iSet(valText.pText); - - ret = DEVICE_OK; - } - break; - default: - break; - } - - return ret; -} - /** * Handles "OnModeSelect" property. */ @@ -5151,9 +4319,6 @@ int CMMTUCam::OnBitDepth(MM::PropertyBase* pProp, MM::ActionType eAct) if (NULL == m_opCam.hIdxTUCam) return DEVICE_NOT_CONNECTED; - if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID) - return DEVICE_OK; - int ret = DEVICE_ERR; switch(eAct) { @@ -5193,128 +4358,37 @@ int CMMTUCam::OnBitDepth(MM::PropertyBase* pProp, MM::ActionType eAct) SetProperty(MM::g_Keyword_PixelType, g_PixelType_8bit); } - if (m_nPID == PID_FL_9BW || PID_FL_9BW_LT == m_nPID || m_nPID == PID_FL_20BW || m_nPID == PID_FL_26BW) + if(m_nPID == PID_FL_20BW) { UpdateExpRange(); } -// StartCapture(); + StartCapture(); ResizeImageBuffer(); roiX_ = 0; roiY_ = 0; - } - - OnPropertyChanged(g_PropNameBODP, val.c_str()); - - ret = DEVICE_OK; - } - } - break; - case MM::BeforeGet: - { - int nVal = 0; - TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BITOFDEPTH, &nVal); - - if (16 == nVal) - { - pProp->Set("16"); - } - else - { - pProp->Set("8"); - } - - ret = DEVICE_OK; - } - break; - default: - break; - } - - return ret; -} - -/** -* Handles "BitDepth" property. -*/ -int CMMTUCam::OnBitDepthEum(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - if (IsCapturing()) - return DEVICE_CAMERA_BUSY_ACQUIRING; - - // the user just set the new value for the property, so we have to - // apply this value to the 'hardware'. - string val; - pProp->Get(val); - if (val.length() != 0) - { - TUCAM_CAPA_ATTR capaAttr; - capaAttr.idCapa = TUIDC_BITOFDEPTH; - - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) - { - m_bLiving = false; - TUCAM_Cap_Stop(m_opCam.hIdxTUCam); // Stop capture - ReleaseBuffer(); - - char szBuf[64] = { 0 }; - TUCAM_VALUE_TEXT valText; - valText.nID = TUIDC_BITOFDEPTH; - valText.nTextSize = 64; - valText.pText = &szBuf[0]; - - int i = 0; - int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; - - for (; iSet(valText.pText); + if (16 == nVal) + { + pProp->Set("16"); + } + else + { + pProp->Set("8"); + } ret = DEVICE_OK; } @@ -5832,45 +4906,6 @@ int CMMTUCam::OnBlueGain(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } -/** -* Handles "OnATExpMode" property. -*/ -int CMMTUCam::OnATExpMode(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - long lVal = 0; - pProp->Get(lVal); - - TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_ATEXPOSURE_MODE, (int)lVal); - - ret = DEVICE_OK; - } - break; - case MM::BeforeGet: - { - int nVal = 0; - - TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_ATEXPOSURE_MODE, &nVal); - - pProp->Set((long)nVal); - - ret = DEVICE_OK; - } - break; - default: - break; - } - - return ret; -} - /** * Handles "ATExposure" property. */ @@ -5933,68 +4968,6 @@ int CMMTUCam::OnATExposure(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } -/** -* Handles "OnTimeStamp" property. -*/ -int CMMTUCam::OnTimeStamp(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - string val; - pProp->Get(val); - if (val.length() != 0) - { - TUCAM_CAPA_ATTR capaAttr; - capaAttr.idCapa = TUIDC_ENABLETIMESTAMP; - - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) - { - if (0 == val.compare("TRUE")) - { - TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_ENABLETIMESTAMP, 1); - } - else - { - TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_ENABLETIMESTAMP, 0); - } - } - - OnPropertyChanged(g_PropNameATEXP, val.c_str()); - - ret = DEVICE_OK; - } - } - break; - case MM::BeforeGet: - { - int nVal = 0; - TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_ENABLETIMESTAMP, &nVal); - - if (1 == nVal) - { - pProp->Set("TRUE"); - } - else - { - pProp->Set("FALSE"); - } - - ret = DEVICE_OK; - } - break; - default: - break; - } - - return ret; -} - /** * Handles "Temperature" property. */ @@ -6012,24 +4985,14 @@ int CMMTUCam::OnTemperature(MM::PropertyBase* pProp, MM::ActionType eAct) pProp->Get(dblTemp); m_fValTemp = (float)dblTemp; -// TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, (dblTemp + m_nMidTemp)); - TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, (dblTemp * m_fScaTemp + m_nMidTemp)); + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, (dblTemp + 50)); ret = DEVICE_OK; } break; case MM::BeforeGet: { - double dblTemp; - if (TUCAMRET_SUCCESS == TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE_TARGET, &dblTemp)) - { -// pProp->Set((dblTemp - 50)); - pProp->Set((dblTemp - m_nMidTemp) / m_fScaTemp); - } - else - { - pProp->Set(m_fValTemp); - } + pProp->Set(m_fValTemp); ret = DEVICE_OK; } @@ -6351,14 +5314,10 @@ int CMMTUCam::OnTriggerMode(MM::PropertyBase* pProp, MM::ActionType eAct) { m_tgrAttr.nTgrMode = TUCCM_SEQUENCE; } - else if (0 == val.compare(g_TRIGGER_STD) || 0 == val.compare(g_TRIGGER_STDOVERLAP)) + else if (0 == val.compare(g_TRIGGER_STD)) { m_tgrAttr.nTgrMode = TUCCM_TRIGGER_STANDARD; } - else if (0 == val.compare(g_TRIGGER_STDNONOVERLAP)) - { - m_tgrAttr.nTgrMode = TUCCM_TRIGGER_STANDARD_NONOVERLAP; - } else if (0 == val.compare(g_TRIGGER_SYN)) { m_tgrAttr.nTgrMode = TUCCM_TRIGGER_SYNCHRONOUS; @@ -6406,19 +5365,7 @@ int CMMTUCam::OnTriggerMode(MM::PropertyBase* pProp, MM::ActionType eAct) } else if (TUCCM_TRIGGER_STANDARD == m_tgrAttr.nTgrMode) { - if (IsSupport95V2New() || IsSupport401DNew() || IsSupport400BSIV3New()) - { - pProp->Set(g_TRIGGER_STDOVERLAP); - } - else - { - pProp->Set(g_TRIGGER_STD); - } - - } - else if (TUCCM_TRIGGER_STANDARD_NONOVERLAP == m_tgrAttr.nTgrMode) - { - pProp->Set(g_TRIGGER_STDNONOVERLAP); + pProp->Set(g_TRIGGER_STD); } else if (TUCCM_TRIGGER_SYNCHRONOUS == m_tgrAttr.nTgrMode) { @@ -6610,41 +5557,6 @@ int CMMTUCam::OnTriggerDelay(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } -int CMMTUCam::OnTriggerFilter(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - long lVal = 0; - pProp->Get(lVal); - - TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_SIGNALFILTER, lVal); - - ret = DEVICE_OK; - } - break; - case MM::BeforeGet: - { - int nVal = 0; - TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_SIGNALFILTER, &nVal); - - pProp->Set((long)(nVal)); - - ret = DEVICE_OK; - } - break; - default: - break; - } - - return ret; -} - int CMMTUCam::OnTriggerFrames(MM::PropertyBase* pProp, MM::ActionType eAct) { if (NULL == m_opCam.hIdxTUCam) @@ -6684,45 +5596,6 @@ int CMMTUCam::OnTriggerFrames(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } -int CMMTUCam::OnTriggerTotalFrames(MM::PropertyBase* pProp, MM::ActionType eAct) -{ - if (NULL == m_opCam.hIdxTUCam) - return DEVICE_NOT_CONNECTED; - - int ret = DEVICE_ERR; - switch (eAct) - { - case MM::AfterSet: - { - long lVal = 0; - pProp->Get(lVal); - - if (TUCCM_SEQUENCE < m_tgrAttr.nTgrMode && m_tgrAttr.nTgrMode < TUCCM_TRIGGER_SOFTWARE) - { - m_tgrAttr.nFrames = lVal; - } - - TUCAM_Cap_SetTrigger(m_opCam.hIdxTUCam, m_tgrAttr); - - ret = DEVICE_OK; - } - break; - case MM::BeforeGet: - { - TUCAM_Cap_GetTrigger(m_opCam.hIdxTUCam, &m_tgrAttr); - - pProp->Set((long)(m_tgrAttr.nFrames)); - - ret = DEVICE_OK; - } - break; - default: - break; - } - - return ret; -} - int CMMTUCam::OnTriggerDoSoftware(MM::PropertyBase* pProp, MM::ActionType eAct) { if (NULL == m_opCam.hIdxTUCam) @@ -7092,10 +5965,6 @@ int CMMTUCam::OnTrgOutKindMode(MM::PropertyBase* pProp, MM::ActionType eAct) { m_tgrOutAttr.nTgrOutMode = 4; } - else if (0 == val.compare(g_TRIGGER_TRIREADY)) - { - m_tgrOutAttr.nTgrOutMode = 6; - } else if (0 == val.compare(g_TRIGGER_LOW)) { m_tgrOutAttr.nTgrOutMode = 0; @@ -7163,10 +6032,6 @@ int CMMTUCam::OnTrgOutKindMode(MM::PropertyBase* pProp, MM::ActionType eAct) { pProp->Set(g_TRIGGER_READEND); } - else if (6 == m_tgrOutAttr.nTgrOutMode) - { - pProp->Set(g_TRIGGER_TRIREADY); - } ret = DEVICE_OK; } @@ -7709,15 +6574,6 @@ int CMMTUCam::ResizeImageBuffer() if (TUCAMRET_SUCCESS != TUCAM_Dev_GetInfo(m_opCam.hIdxTUCam, &valHeight)) return DEVICE_NATIVE_MODULE_FAILED; - if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) - { - ResizeBinImageBufferFL9BW(valWidth.nValue, valHeight.nValue); - } - else if (PID_FL_26BW == m_nPID) - { - ResizeBinImageBufferFL26BW(valWidth.nValue, valHeight.nValue); - } - char sz[256] = {0}; sprintf(sz, "[ResizeImageBuffer]:Width:%d, Height:%d, BytesPerPixel:%d\n", valWidth.nValue, valHeight.nValue, m_frame.ucElemBytes * nChnnels); OutputDebugString(sz); @@ -7744,50 +6600,6 @@ int CMMTUCam::ResizeImageBuffer() return DEVICE_OK; } -void CMMTUCam::ResizeBinImageBufferFL9BW(int &width, int &height) -{ - int bin = 0; - TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BINNING_SUM, &bin); - - if (5 == bin) - bin = 8; - else if (4 == bin) - bin = 6; - else - bin += 1; - - if (!m_bROI) - { - int nMaxWid = width / bin; - int nMaxHei = height / bin; - - width = (nMaxWid >> 2) << 2; - height = (nMaxHei >> 2) << 2; - } -} - -void CMMTUCam::ResizeBinImageBufferFL26BW(int &width, int &height) -{ - int bin = 0; - TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BINNING_SUM, &bin); - - if (6 == bin) - bin = 8; - else if (7 == bin) - bin = 16; - else - bin += 1; - - if (!m_bROI) - { - int nMaxWid = width / bin; - int nMaxHei = height / bin; - - width = (nMaxWid >> 2) << 2; - height = (nMaxHei >> 2) << 2; - } -} - void CMMTUCam::GenerateEmptyImage(ImgBuffer& img) { MMThreadGuard g(imgPixelsLock_); @@ -8165,7 +6977,7 @@ void CMMTUCam::RunTemperature() dw = GetTickCount(); - if (isSupportSoftProtect()) // 400BSIV2 BCD = 0x05, 0x07, 0x09 ²»¿Éµ÷·çÉÈÃà»ú + if (isSupportFanWaterCool()) // 400BSIV2 BCD = 0x05, 0x07, 0x09 ä¸å¯è°ƒé£Žæ‰‡ç›¸æœº { int nFan = 0; TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_FAN_GEAR, &nFan); @@ -8365,7 +7177,7 @@ int CMMTUCam::WaitForFrame(ImgBuffer& img) MMThreadGuard g(imgPixelsLock_); m_frame.ucFormatGet = TUFRM_FMT_USUAl; // Set usual format - if (TUCAMRET_SUCCESS == TUCAM_Buf_WaitForFrame(m_opCam.hIdxTUCam, &m_frame, 1000)) + if (TUCAMRET_SUCCESS == TUCAM_Buf_WaitForFrame(m_opCam.hIdxTUCam, &m_frame)) { if (img.Height() == 0 || img.Width() == 0 || img.Depth() == 0) return DEVICE_OUT_OF_MEMORY; @@ -8557,11 +7369,7 @@ bool CMMTUCam::isSupportFanCool() { bool bSupport = false; bSupport = isSupportFanWaterCool(); - if (m_nPID == PID_FL_9BW || PID_FL_9BW_LT == m_nPID || PID_FL_20BW == m_nPID || m_nPID == PID_FL_26BW) - { - return true; - } - if (IsSupportAries16()) + if (PID_FL_20BW == m_nPID) { return true; } @@ -8588,17 +7396,6 @@ bool CMMTUCam::isSupportFanWaterCool() return false; } -bool CMMTUCam::isSupportSoftProtect() -{ - bool bSupport = false; - bSupport = isSupportFanWaterCool(); - if (IsSupport400BSIV3New() || IsSupport95V2New()) - { - return false; - } - return bSupport; -} - void CMMTUCam::UpdateSlitHeightRange() { int nImgMode = 0; @@ -8636,7 +7433,7 @@ void CMMTUCam::UpdateExpRange() exposureMinimum_ = propAttr.dbValMin; exposureMaximum_ = propAttr.dbValMax; - if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_20BW == m_nPID || PID_FL_26BW == m_nPID) + if (PID_FL_20BW == m_nPID) { exposureMaximum_ = 3600000; } @@ -8648,24 +7445,3 @@ void CMMTUCam::UpdateExpRange() SetPropertyLimits(MM::g_Keyword_Exposure, exposureMinimum_, exposureMaximum_); } - -void CMMTUCam::UpdateLevelsRange() -{ - TUCAM_PROP_ATTR propAttr; - propAttr.nIdxChn = 0; - propAttr.idProp = TUIDP_LFTLEVELS; - if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) - { - SetPropertyLimits(g_PropNameLLev, (int)propAttr.dbValMin, (int)propAttr.dbValMax); - - SetProperty(g_PropNameLLev, CDeviceUtils::ConvertToString((int)propAttr.dbValMin)); - } - - propAttr.idProp = TUIDP_RGTLEVELS; - if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) - { - SetPropertyLimits(g_PropNameRLev, (int)propAttr.dbValMin, (int)propAttr.dbValMax); - - SetProperty(g_PropNameRLev, CDeviceUtils::ConvertToString((int)propAttr.dbValMax)); - } -} From 034f19c60301911a380cf816973eba2e8dccb99b Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Thu, 11 Jan 2024 18:13:17 -0600 Subject: [PATCH 127/141] QSI: Use consistent paths in .vcxproj (I previously forgot to update the Debug to match Release.) --- DeviceAdapters/QSI/QSI.vcxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DeviceAdapters/QSI/QSI.vcxproj b/DeviceAdapters/QSI/QSI.vcxproj index 4bcadef65..a70bba956 100644 --- a/DeviceAdapters/QSI/QSI.vcxproj +++ b/DeviceAdapters/QSI/QSI.vcxproj @@ -74,14 +74,14 @@ true NotUsing pch.h - $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_2022_07_19\include;%(AdditionalIncludeDirectories) + $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_20200917\QSICameraCLib\include;%(AdditionalIncludeDirectories) Windows true false QSICameraCLib.lib;%(AdditionalDependencies) - $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_2022_07_19\lib;%(AdditionalLibraryDirectories) + $(MM_3RDPARTYPRIVATE)\QSI\QSISDK_20200917\QSICameraCLib\lib\x64;%(AdditionalLibraryDirectories) @@ -109,4 +109,4 @@ - \ No newline at end of file + From 99bf0c0f94e14680776f9664af52df76fd6da0eb Mon Sep 17 00:00:00 2001 From: fandayu <57091941+fandayu@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:57:08 +0800 Subject: [PATCH 128/141] Update MMTUCam.cpp support aries 16 lt/fl 26bw/fl 9bw --- DeviceAdapters/TUCam/MMTUCam.cpp | 1556 ++++++++++++++++++++++++++---- 1 file changed, 1390 insertions(+), 166 deletions(-) diff --git a/DeviceAdapters/TUCam/MMTUCam.cpp b/DeviceAdapters/TUCam/MMTUCam.cpp index b5b0e9db7..403834f64 100755 --- a/DeviceAdapters/TUCam/MMTUCam.cpp +++ b/DeviceAdapters/TUCam/MMTUCam.cpp @@ -8,9 +8,9 @@ // microscope devices and enables testing of the rest of the // system without the need to connect to the actual hardware. // -// AUTHOR: fandayu, fandayu@tucsen.com 2022 +// AUTHOR: fandayu, fandayu@tucsen.com 2024 // -// COPYRIGHT: Tucsen Photonics Co., Ltd., 2022 +// COPYRIGHT: Tucsen Photonics Co., Ltd., 2024 // LICENSE: This file is distributed under the BSD license. // License text is included with the source distribution. // @@ -70,11 +70,14 @@ const char* g_PropNameLED = "LEDMode"; const char* g_PropNameTEC = "TECMode"; const char* g_PropNamePI = "PIMode"; const char* g_PropNameDepth = "DepthMode"; +const char* g_PropNameShutter = "Shutter Mode"; const char* g_PropNameMdTgr = "Trigger Mode"; const char* g_PropNameMdExp = "Trigger Exposure Mode"; const char* g_PropNameMdEdg = "Trigger Edge Mode"; const char* g_PropNameMdDly = "Trigger Delay"; +const char* g_PropNameFilter= "Signal Filter"; const char* g_PropNameMdFrames = "Trigger Frames"; +const char* g_PropNameMdTFrames = "Trigger Total Frames"; const char* g_PropNameDoSFW = "Trigger Software Do"; const char* g_PropNameSharp = "Image Adjustment Sharpness"; const char* g_PropNameDPC = "Image Adjustment DPC"; @@ -97,6 +100,13 @@ const char* g_PropNameFrameRate = "Frame Rate"; const char* g_PropNameTestImg = "Test Image"; +const char* g_PropNameBrightness = "Targeting Level"; //"Brightness"; +const char* g_PropNamePixelRatio = "Metering Level"; //"Pixel Ratio"; +const char* g_PropNameImgMetadata= "Image Metadata"; +const char* g_PropNameATExpMode = "ATExposure Mode"; + +const char* g_PropNameBinningSum = "Binning Sum"; + const char* g_DeviceName = "Dhyana"; //"400Li" const char* g_SdkName = "TUCam"; //const char* g_SdkName = "CamCore"; @@ -148,6 +158,8 @@ const char* g_GLOBALRESET_ON = "Global Reset"; /// GlobalReset Hg const char* g_TRIGGER_OFF = "Off"; const char* g_TRIGGER_STD = "Standard"; +const char* g_TRIGGER_STDOVERLAP = "Standard(Overlap)"; +const char* g_TRIGGER_STDNONOVERLAP = "Standard(Non-Overlap)"; const char* g_TRIGGER_CC1 = "CC1"; const char* g_TRIGGER_SYN = "Synchronization"; const char* g_TRIGGER_GLB = "Global"; @@ -160,6 +172,7 @@ const char* g_TRIGGER_PORT3 = "3"; const char* g_TRIGGER_EXPSTART = "Exposure Start"; const char* g_TRIGGER_READEND = "Readout End"; const char* g_TRIGGER_GLBEXP = "Global Exposure"; +const char* g_TRIGGER_TRIREADY = "Trigger Ready"; const char* g_TRIGGER_LOW = "Low"; const char* g_TRIGGER_HIGH = "High"; @@ -292,6 +305,7 @@ CMMTUCam::CMMTUCam() : m_frame.ucFormatGet = TUFRM_FMT_USUAl; m_frame.pBuffer = NULL; + m_nZeroTemp = 50; m_nPID = 0; m_nBCD = 0; m_nIdxGain = 0; @@ -454,6 +468,13 @@ int CMMTUCam::Initialize() m_nBCD = valInfo.nValue; } + // Get Zero Temperature Value + valInfo.nID = TUIDI_ZEROTEMPERATURE_VALUE; + if (TUCAMRET_SUCCESS == TUCAM_Dev_GetInfo(m_opCam.hIdxTUCam, &valInfo)) + { + m_nZeroTemp = valInfo.nValue; + } + // Get camera type valInfo.nID = TUIDI_PRODUCT; if (TUCAMRET_SUCCESS == TUCAM_Dev_GetInfo(m_opCam.hIdxTUCam, &valInfo)) @@ -477,7 +498,7 @@ int CMMTUCam::Initialize() m_bTriEn = false; } - if (PID_FL_20BW == m_nPID || DHYANA_4040V2 == m_nPID || DHYANA_4040BSI == m_nPID || DHYANA_XF4040BSI == m_nPID) + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID || PID_FL_20BW == m_nPID || DHYANA_4040V2 == m_nPID || DHYANA_4040BSI == m_nPID || DHYANA_XF4040BSI == m_nPID) { m_bOffsetEn = true; } @@ -519,16 +540,30 @@ int CMMTUCam::Initialize() if (nRet != DEVICE_OK) return nRet; + if (PID_FL_26BW == m_nPID) + { + // binning sum + pAct = new CPropertyAction(this, &CMMTUCam::OnBinningSum); + nRet = CreateProperty(g_PropNameBinningSum, "", MM::String, false, pAct); + assert(nRet == DEVICE_OK); + + nRet = SetAllowedBinningSum(); + if (nRet != DEVICE_OK) + return nRet; + } + // Bit depth capaAttr.idCapa = TUIDC_BITOFDEPTH; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) { if(capaAttr.nValMax != capaAttr.nValMin && m_nPID != DHYANA_D95_X100) { - if (DHYANA_400DC_X100 == m_nPID || DHYANA_400DC_X45 == m_nPID) + if (capaAttr.nValMax > 8) { - TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_BITOFDEPTH, 8); - } + if (DHYANA_400DC_X100 == m_nPID || DHYANA_400DC_X45 == m_nPID) + { + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_BITOFDEPTH, 8); + } pAct = new CPropertyAction (this, &CMMTUCam::OnBitDepth); nRet = CreateProperty(g_PropNameBODP, "8", MM::String, false, pAct); @@ -538,9 +573,18 @@ int CMMTUCam::Initialize() bitDepths.push_back("8"); bitDepths.push_back("16"); - nRet = SetAllowedValues(g_PropNameBODP, bitDepths); - if (nRet != DEVICE_OK) - return nRet; + nRet = SetAllowedValues(g_PropNameBODP, bitDepths); + if (nRet != DEVICE_OK) + return nRet; + } + else + { + pAct = new CPropertyAction(this, &CMMTUCam::OnBitDepthEum); + nRet = CreateProperty(g_PropNameBODP, "", MM::String, false, pAct); + SetAllowedDepth(); + if (nRet != DEVICE_OK) + return nRet; + } } } @@ -569,13 +613,30 @@ int CMMTUCam::Initialize() assert(nRet == DEVICE_OK); UpdateExpRange(); - /*exposureMaximum_ = propAttr.dbValMax; - exposureMinimum_ = propAttr.dbValMin; - if(m_nPID == PID_FL_20BW) - { - exposureMaximum_ = 3600000; - } - SetPropertyLimits(MM::g_Keyword_Exposure, exposureMinimum_, exposureMaximum_);*/ + } + + // Brightness + propAttr.nIdxChn = 0; + propAttr.idProp = TUIDP_BRIGHTNESS; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnBrightness); + nRet = CreateProperty(g_PropNameBrightness, "0", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + + SetPropertyLimits(g_PropNameBrightness, propAttr.dbValMin, propAttr.dbValMax); + } + + // PixelRatio + propAttr.nIdxChn = 0; + propAttr.idProp = TUIDP_PIXELRATIO; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnPixelRatio); + nRet = CreateProperty(g_PropNamePixelRatio, "0", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + + SetPropertyLimits(g_PropNamePixelRatio, propAttr.dbValMin, propAttr.dbValMax); } // Global Gain @@ -593,9 +654,38 @@ int CMMTUCam::Initialize() } else { - nRet = SetAllowedGainMode(); - if (nRet != DEVICE_OK) - return nRet; + if (m_nPID == PID_FL_9BW || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID || IsSupportAries16()) + { + int nCnt = (int)(propAttr.dbValMax - propAttr.dbValMin + 1); + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + valText.nID = TUIDP_GLOBALGAIN; + + vector gainValues; + + for (int i = 0; i StampValues; + StampValues.push_back("FALSE"); + StampValues.push_back("TRUE"); + + nRet = SetAllowedValues(g_PropNameImgMetadata, StampValues); + if (nRet != DEVICE_OK) + return nRet; + } + } + // Sensor reset capaAttr.idCapa = TUIDC_SENSORRESET; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) @@ -619,6 +729,17 @@ int CMMTUCam::Initialize() AddAllowedValue(g_PropNameReset, g_PropNameReset); } + // Auto Exposure Mode + capaAttr.idCapa = TUIDC_ATEXPOSURE_MODE; + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnATExpMode); + nRet = CreateProperty(g_PropNameATExpMode, "0", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + + SetPropertyLimits(g_PropNameATExpMode, capaAttr.nValMin, capaAttr.nValMax); + } + // Auto Exposure capaAttr.idCapa = TUIDC_ATEXPOSURE; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) @@ -669,7 +790,18 @@ int CMMTUCam::Initialize() if (nRet != DEVICE_OK) return nRet; } - + + // Shutter + if (PID_FL_26BW == m_nPID) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnShutterMode); + nRet = CreateProperty(g_PropNameShutter, "", MM::String, false, pAct); + assert(nRet == DEVICE_OK); + + nRet = SetAllowedShutterMode(); + if (nRet != DEVICE_OK) + return nRet; + } // Gamma propAttr.nIdxChn= 0; @@ -796,12 +928,30 @@ int CMMTUCam::Initialize() if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr) && m_bTempEn) // For 401D 201D disable temperature { pAct = new CPropertyAction (this, &CMMTUCam::OnTemperature); - nRet = CreateProperty(g_PropNameTEMP, "0", MM::Integer, false, pAct); - m_nMidTemp = (int)((propAttr.dbValMax - propAttr.dbValMin) / 2); - SetPropertyLimits(g_PropNameTEMP, -m_nMidTemp, m_nMidTemp); - char sz[64] = { 0 }; + if (propAttr.dbValMax > 100) + { + m_fScaTemp = 10; + nRet = CreateProperty(g_PropNameTEMP, "0.0", MM::Float, false, pAct); + } + else + { + m_fScaTemp = 1; + nRet = CreateProperty(g_PropNameTEMP, "0", MM::Integer, false, pAct); + } + m_nMidTemp = m_nZeroTemp; + if (PID_FL_9BW_LT == m_nPID) + { + ///m_nMidTemp = 500; + SetPropertyLimits(g_PropNameTEMP, (propAttr.dbValMin - m_nMidTemp) / m_fScaTemp, (propAttr.dbValMax - m_nMidTemp) / m_fScaTemp); + } + else + { + SetPropertyLimits(g_PropNameTEMP, -m_nMidTemp / m_fScaTemp, m_nMidTemp / m_fScaTemp); + } + + char sz[64] = { 0 }; switch (m_nPID) { case PID_FL_20BW: @@ -812,8 +962,8 @@ int CMMTUCam::Initialize() SetProperty(g_PropNameTEMP, sz); } break; - case DHYANA_400BSIV2: - case DHYANA_400BSIV3: +// case DHYANA_400BSIV2: +// case DHYANA_400BSIV3: case DHYANA_D95_V2: case DHYANA_4040V2: case DHYANA_4040BSI: @@ -824,11 +974,21 @@ int CMMTUCam::Initialize() break; default: // Set default temperature - if (TUCAMRET_SUCCESS == TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, 40.0f)) - { - sprintf(sz, "%d", -10); - SetProperty(g_PropNameTEMP, sz); - } + double dblTemp; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE_TARGET, &dblTemp)) + { + sprintf(sz, "%.1f", (dblTemp - m_nMidTemp) / m_fScaTemp); + SetProperty(g_PropNameTEMP, sz); + } + else + { + if (TUCAMRET_SUCCESS == TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, 40.0f)) + { + sprintf(sz, "%d", -10); + SetProperty(g_PropNameTEMP, sz); + } + } + break; } @@ -875,11 +1035,17 @@ int CMMTUCam::Initialize() capaAttr.idCapa = TUIDC_IMGMODESELECT; if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) { - int nImgMode = capaAttr.nValMax - capaAttr.nValMin +1; - - if((nImgMode < 0x3) && (capaAttr.nValMax < 0x02)) - { - pAct = new CPropertyAction (this, &CMMTUCam::OnCMSMode); + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) + { + nRet = SetAllowedGainMode(); + } + else + { + int nImgMode = capaAttr.nValMax - capaAttr.nValMin + 1; + + if ((nImgMode < 0x3) && (capaAttr.nValMax < 0x02) && PID_FL_9BW != m_nPID && PID_FL_9BW_LT != m_nPID) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnCMSMode); nRet = CreateProperty(g_PropNameCMS, g_CMS_ON, MM::String, false, pAct); assert(nRet == DEVICE_OK); @@ -888,9 +1054,11 @@ int CMMTUCam::Initialize() CMSValues.push_back(g_CMS_OFF); CMSValues.push_back(g_CMS_ON); - nRet = SetAllowedValues(g_PropNameCMS, CMSValues); + nRet = SetAllowedValues(g_PropNameCMS, CMSValues); + + } + } - } if (nRet != DEVICE_OK) return nRet; } @@ -1055,17 +1223,31 @@ int CMMTUCam::Initialize() vectorModTgrValues; ModTgrValues.push_back(g_TRIGGER_OFF); - ModTgrValues.push_back(g_TRIGGER_STD); + + if (IsSupport95V2New() || IsSupport401DNew() || IsSupport400BSIV3New()) + { + ModTgrValues.push_back(g_TRIGGER_STDOVERLAP); + ModTgrValues.push_back(g_TRIGGER_STDNONOVERLAP); + } + else + { + ModTgrValues.push_back(g_TRIGGER_STD); + } int nImgMode = 0; switch (m_nPID) { + case PID_FL_9BW: + case PID_FL_9BW_LT: + case PID_FL_26BW: case PID_FL_20BW: case DHYANA_401D: case DHYANA_201D: case DHYANA_4040V2: case DHYANA_4040BSI: case DHYANA_XF4040BSI: + case PID_ARIES16LT: + case PID_ARIES16: { if (TU_PHXCAMERALINK == m_nDriverType) { @@ -1130,7 +1312,25 @@ int CMMTUCam::Initialize() nRet = CreateProperty(g_PropNameMdDly, "0", MM::Integer, false, pAct); assert(nRet == DEVICE_OK); - SetPropertyLimits(g_PropNameMdDly, 0, 10000000); + // Trigger Filter + capaAttr.idCapa = TUIDC_SIGNALFILTER; + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + pAct = new CPropertyAction(this, &CMMTUCam::OnTriggerFilter); + nRet = CreateProperty(g_PropNameFilter, "0", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + SetPropertyLimits(g_PropNameFilter, 1, 1000000); + } + + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID) + { + // Trigger total frames + pAct = new CPropertyAction(this, &CMMTUCam::OnTriggerTotalFrames); + nRet = CreateProperty(g_PropNameMdTFrames, "1", MM::Integer, false, pAct); + assert(nRet == DEVICE_OK); + } + + SetPropertyLimits(g_PropNameMdTFrames, 1, 0xFFFF); if (TUCAMRET_SUCCESS == TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_ROLLINGSCANMODE, &m_rsPara.nMode)) { @@ -1139,7 +1339,7 @@ int CMMTUCam::Initialize() nRet = CreateProperty(g_PropNameMdFrames, "1", MM::Integer, false, pAct); assert(nRet == DEVICE_OK); - SetPropertyLimits(g_PropNameMdFrames, 1, 65535); + SetPropertyLimits(g_PropNameMdFrames, 1, 0xFFFF); } } } @@ -1324,6 +1524,10 @@ int CMMTUCam::Initialize() ModKindValues.push_back(g_TRIGGER_EXPSTART); ModKindValues.push_back(g_TRIGGER_READEND); ModKindValues.push_back(g_TRIGGER_GLBEXP); + if (IsSupport95V2New() || IsSupport401DNew() || IsSupport400BSIV3New()) + { + ModKindValues.push_back(g_TRIGGER_TRIREADY); + } ModKindValues.push_back(g_TRIGGER_LOW); ModKindValues.push_back(g_TRIGGER_HIGH); @@ -1524,10 +1728,12 @@ int CMMTUCam::SnapImage() StartCapture(); } + int nRet = DEVICE_ERR; + if (TUCCM_TRIGGER_SOFTWARE == m_tgrAttr.nTgrMode) { int nCnt = 0; - int nRet = DEVICE_ERR; + ///int nRet = DEVICE_ERR; do { @@ -1546,7 +1752,7 @@ int CMMTUCam::SnapImage() CDeviceUtils::SleepMs((long) exp); } ///TUDBG_PRINTF("SanpImage fastImage_ = %d",fastImage_); - WaitForFrame(img_); + nRet = WaitForFrame(img_); } } @@ -1557,7 +1763,7 @@ int CMMTUCam::SnapImage() StopCapture(); } - return DEVICE_OK; + return nRet/*DEVICE_OK*/; } @@ -1730,8 +1936,7 @@ int CMMTUCam::SetROI(unsigned x, unsigned y, unsigned xSize, unsigned ySize) roiY_ = y; m_bROI = true; - - StartCapture(); +// StartCapture(); ResizeImageBuffer(); Sleep(2); @@ -1782,7 +1987,7 @@ int CMMTUCam::ClearROI() roiY_ = 0; m_bROI = false; - StartCapture(); +// StartCapture(); ResizeImageBuffer(); return DEVICE_OK; @@ -2001,6 +2206,39 @@ int CMMTUCam::LineIntervalCal(int nVal, bool bExpChange) return nStep; } +int CMMTUCam::SetAllowedDepth() +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_BITOFDEPTH; + + if (TUCAMRET_SUCCESS != TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + return DEVICE_NOT_SUPPORTED; + } + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_BITOFDEPTH; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + vector depthValues; + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (int i = 0; i binValues; + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (int i = 0; i modeValues; + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (int i = 0; i shutters; + + for (int i = 0; iIsStopped() && !returnToSoftwareTriggers_) - return DEVICE_OK; - + TUDBG_PRINTF("[StopSequenceAcquisition]:Enter \n"); + if (thd_->IsStopped() /*&& !returnToSoftwareTriggers_*/) + { + if (m_bLiving) + { + m_bAcquisition = false; + StopCapture(); + } + return DEVICE_OK; + } + m_bLiving = false; if (NULL == m_opCam.hIdxTUCam) @@ -2423,13 +2784,13 @@ int CMMTUCam::StopSequenceAcquisition() ReleaseBuffer(); // Switch back to software trigger mode if that is what the user used - if (returnToSoftwareTriggers_) + /*if (returnToSoftwareTriggers_) { TUCAM_Cap_GetTrigger(m_opCam.hIdxTUCam, &m_tgrAttr); m_tgrAttr.nTgrMode = TUCCM_TRIGGER_SOFTWARE; TUCAM_Cap_SetTrigger(m_opCam.hIdxTUCam, m_tgrAttr); returnToSoftwareTriggers_ = false; - } + }*/ m_bAcquisition = false; ///StartCapture(); // avoid wasting time in the SnapImage function @@ -2545,18 +2906,22 @@ int CMMTUCam::RunSequenceOnThread(MM::MMTime startTime) } ret = WaitForFrame(img_); + /* if (!fastImage_) { GenerateSyntheticImage(img_, GetSequenceExposure()); } */ - ret = InsertImage(); - - while (((double) (this->GetCurrentMMTime() - startTime).getMsec() / imageCounter_) < this->GetSequenceExposure()) + if (DEVICE_OK == ret) + { + ret = InsertImage(); + } + + /* while (((double) (this->GetCurrentMMTime() - startTime).getMsec() / imageCounter_) < this->GetSequenceExposure()) { CDeviceUtils::SleepMs(1); - } + }*/ if (ret != DEVICE_OK) { @@ -2729,9 +3094,10 @@ int CMMTUCam::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) valText.nTextSize = 64; valText.pText = &szBuf[0]; + int i = 0; int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; - for (int i=0; iSet(valText.pText); + if (nIdx > 0) + { + valText.dbValue = nIdx; + valText.nID = TUIDC_BINNING_SUM; + + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + pProp->Set(valText.pText); + } + else + { + valText.dbValue = 0; + valText.nID = TUIDC_RESOLUTION; + + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + pProp->Set(valText.pText); + } + } + else + { + int nIdx = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_RESOLUTION, &nIdx); + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_RESOLUTION; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + valText.dbValue = nIdx; + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + pProp->Set(valText.pText); + } + ret = DEVICE_OK; } break; @@ -2784,6 +3229,88 @@ int CMMTUCam::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "BinningSum" property. +*/ +int CMMTUCam::OnBinningSum(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + string val; + pProp->Get(val); + + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_BINNING_SUM; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_BINNING_SUM; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int nCnt = (int)(capaAttr.nValMax - capaAttr.nValMin + 1); + + for (int i = 0; iSet(valText.pText); + + ret = DEVICE_OK; + } + break; + default: + break; + } + return ret; +} + /** * Handles "PixelClock" property. */ @@ -2911,6 +3438,84 @@ int CMMTUCam::OnExposure(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "Brightness" property. +*/ +int CMMTUCam::OnBrightness(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + double dbVal = 0.0f; + pProp->Get(dbVal); + + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_BRIGHTNESS, dbVal); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + double dbVal = 0.0f; + + TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_BRIGHTNESS, &dbVal); + + pProp->Set(dbVal); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + +/** +* Handles "OnPixelRatio" property. +*/ +int CMMTUCam::OnPixelRatio(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + double dbVal = 0.0f; + pProp->Get(dbVal); + + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_PIXELRATIO, dbVal); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + double dbVal = 0.0f; + + TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_PIXELRATIO, &dbVal); + + pProp->Set(dbVal); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "GlobalGain" property. */ @@ -2962,20 +3567,20 @@ int CMMTUCam::OnFrameRate(MM::PropertyBase* pProp, MM::ActionType eAct) { case MM::AfterSet: { - double dblGain; - pProp->Get(dblGain); + double dblRate; + pProp->Get(dblRate); - TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, dblGain); + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, dblRate); ret = DEVICE_OK; } break; case MM::BeforeGet: { - double dblGain = 0.0f; + double dblRate = 0.0f; - TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, &dblGain); - pProp->Set(dblGain); + TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_FRAME_RATE, &dblRate); + pProp->Set(dblRate); ret = DEVICE_OK; } @@ -3811,6 +4416,94 @@ int CMMTUCam::OnTestImageMode(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "OnGlobalGainMode" property. +*/ +int CMMTUCam::OnGlobalGainMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + // the user just set the new value for the property, so we have to + // apply this value to the 'hardware'. + + string val; + pProp->Get(val); + + if (val.length() != 0) + { + TUCAM_PROP_ATTR propAttr; + propAttr.nIdxChn = 0; + propAttr.idProp = TUIDP_GLOBALGAIN; + + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDP_GLOBALGAIN; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int nCnt = (int)(propAttr.dbValMax - propAttr.dbValMin + 1); + + for (int i = 0; iSet(valText.pText); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "OnGAINMode" property. */ @@ -3824,42 +4517,88 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) { case MM::AfterSet: { - int nVal = 0; - int nImgMode = 0; - int nGain = 0; - double dblExp = 0.0; - string val; - pProp->Get(val); - if (val.length() != 0) + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) { - if (TUCAMRET_SUCCESS == TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, &nVal)) - { - if (DHYANA_D95_V2 == m_nPID) - { - bool bLiving = m_bLiving; - if (0 == val.compare(g_HDRBIT_ON)) - { - nImgMode = 0; //HDR - nGain = 0; - } - else if(0 == val.compare(g_HIGHBIT_ON)) - { - nImgMode = 0; //HIGH - nGain = 1; - } - else if(0 == val.compare(g_LOWBIT_ON)) - { - nImgMode = 0; //LOW - nGain = 2; - } - else if(0 == val.compare(g_STDHIGH_ON)) - { - nImgMode = 1; //STDH - } - else if(0 == val.compare(g_STDLOW_ON)) - { - nImgMode = 2; //STDL - } + string val; + pProp->Get(val); + + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_IMGMODESELECT; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_IMGMODESELECT; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (int i = 0; i < nCnt; i++) + { + valText.dbValue = i; + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + + if (0 == val.compare(valText.pText)) + { + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, i); + break; + } + } + } + + OnPropertyChanged(g_PropNameMode, val.c_str()); + + double val = 0; + TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_GLOBALGAIN, &val); + SetProperty(g_PropNameGain, CDeviceUtils::ConvertToString((int)val)); + + UpdateExpRange(); + + ret = DEVICE_OK; + } + } + else + { + int nVal = 0; + int nImgMode = 0; + int nGain = 0; + double dblExp = 0.0; + string val; + pProp->Get(val); + if (val.length() != 0) + { + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, &nVal)) + { + if (DHYANA_D95_V2 == m_nPID) + { + bool bLiving = m_bLiving; + if (0 == val.compare(g_HDRBIT_ON)) + { + nImgMode = 0; //HDR + nGain = 0; + } + else if (0 == val.compare(g_HIGHBIT_ON)) + { + nImgMode = 0; //HIGH + nGain = 1; + } + else if (0 == val.compare(g_LOWBIT_ON)) + { + nImgMode = 0; //LOW + nGain = 2; + } + else if (0 == val.compare(g_STDHIGH_ON)) + { + nImgMode = 1; //STDH + } + else if (0 == val.compare(g_STDLOW_ON)) + { + nImgMode = 2; //STDL + } if (nImgMode != nVal) { @@ -3917,27 +4656,28 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) UpdateExpRange(); - if ((DHYANA_400BSIV2 == m_nPID && m_nBCD > 0x04)) - { - vectorModTgrValues; - ModTgrValues.push_back(g_TRIGGER_OFF); - if (0 == val.compare(g_GRLOW_ON) || 0 == val.compare(g_GRHIGH_ON)) - { - ModTgrValues.push_back(g_TRIGGER_STD); - } - else - { - ModTgrValues.push_back(g_TRIGGER_STD); - ModTgrValues.push_back(g_TRIGGER_SYN); - } - ModTgrValues.push_back(g_TRIGGER_SWF); - ClearAllowedValues(g_PropNameMdTgr); - SetAllowedValues(g_PropNameMdTgr, ModTgrValues); - } - } - } - - ret = DEVICE_OK; + if ((DHYANA_400BSIV2 == m_nPID && m_nBCD > 0x04)) + { + vectorModTgrValues; + ModTgrValues.push_back(g_TRIGGER_OFF); + if (0 == val.compare(g_GRLOW_ON) || 0 == val.compare(g_GRHIGH_ON)) + { + ModTgrValues.push_back(g_TRIGGER_STD); + } + else + { + ModTgrValues.push_back(g_TRIGGER_STD); + ModTgrValues.push_back(g_TRIGGER_SYN); + } + ModTgrValues.push_back(g_TRIGGER_SWF); + ClearAllowedValues(g_PropNameMdTgr); + SetAllowedValues(g_PropNameMdTgr, ModTgrValues); + } + } + } + + ret = DEVICE_OK; + } } } break; @@ -3966,6 +4706,22 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) pProp->Set(g_HDRBIT_ON); } } + else if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) + { + int nIdx = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_IMGMODESELECT, &nIdx); + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_IMGMODESELECT; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + valText.dbValue = nIdx; + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + + pProp->Set(valText.pText); + } else { if (1 == nVal) @@ -3996,6 +4752,82 @@ int CMMTUCam::OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "OnShutterMode" property. +*/ +int CMMTUCam::OnShutterMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + string val; + pProp->Get(val); + + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_SHUTTER; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_SHUTTER; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int nCnt = (int)(capaAttr.nValMax - capaAttr.nValMin + 1); + + for (int i = 0; iSet(valText.pText); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "OnModeSelect" property. */ @@ -4319,6 +5151,9 @@ int CMMTUCam::OnBitDepth(MM::PropertyBase* pProp, MM::ActionType eAct) if (NULL == m_opCam.hIdxTUCam) return DEVICE_NOT_CONNECTED; + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_26BW == m_nPID) + return DEVICE_OK; + int ret = DEVICE_ERR; switch(eAct) { @@ -4358,37 +5193,128 @@ int CMMTUCam::OnBitDepth(MM::PropertyBase* pProp, MM::ActionType eAct) SetProperty(MM::g_Keyword_PixelType, g_PixelType_8bit); } - if(m_nPID == PID_FL_20BW) + if (m_nPID == PID_FL_9BW || PID_FL_9BW_LT == m_nPID || m_nPID == PID_FL_20BW || m_nPID == PID_FL_26BW) { UpdateExpRange(); } - StartCapture(); +// StartCapture(); ResizeImageBuffer(); roiX_ = 0; roiY_ = 0; } - OnPropertyChanged(g_PropNameBODP, val.c_str()); + OnPropertyChanged(g_PropNameBODP, val.c_str()); + + ret = DEVICE_OK; + } + } + break; + case MM::BeforeGet: + { + int nVal = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BITOFDEPTH, &nVal); + + if (16 == nVal) + { + pProp->Set("16"); + } + else + { + pProp->Set("8"); + } + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + +/** +* Handles "BitDepth" property. +*/ +int CMMTUCam::OnBitDepthEum(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + if (IsCapturing()) + return DEVICE_CAMERA_BUSY_ACQUIRING; + + // the user just set the new value for the property, so we have to + // apply this value to the 'hardware'. + string val; + pProp->Get(val); + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_BITOFDEPTH; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + m_bLiving = false; + TUCAM_Cap_Stop(m_opCam.hIdxTUCam); // Stop capture + ReleaseBuffer(); + + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_BITOFDEPTH; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + int i = 0; + int nCnt = capaAttr.nValMax - capaAttr.nValMin + 1; + + for (; iSet("16"); - } - else - { - pProp->Set("8"); - } + char szBuf[64] = { 0 }; + TUCAM_VALUE_TEXT valText; + valText.nID = TUIDC_BITOFDEPTH; + valText.nTextSize = 64; + valText.pText = &szBuf[0]; + + valText.dbValue = nIdx; + TUCAM_Capa_GetValueText(m_opCam.hIdxTUCam, &valText); + pProp->Set(valText.pText); ret = DEVICE_OK; } @@ -4906,6 +5832,45 @@ int CMMTUCam::OnBlueGain(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "OnATExpMode" property. +*/ +int CMMTUCam::OnATExpMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + long lVal = 0; + pProp->Get(lVal); + + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_ATEXPOSURE_MODE, (int)lVal); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + int nVal = 0; + + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_ATEXPOSURE_MODE, &nVal); + + pProp->Set((long)nVal); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "ATExposure" property. */ @@ -4968,6 +5933,68 @@ int CMMTUCam::OnATExposure(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +/** +* Handles "OnTimeStamp" property. +*/ +int CMMTUCam::OnTimeStamp(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + string val; + pProp->Get(val); + if (val.length() != 0) + { + TUCAM_CAPA_ATTR capaAttr; + capaAttr.idCapa = TUIDC_ENABLETIMESTAMP; + + if (TUCAMRET_SUCCESS == TUCAM_Capa_GetAttr(m_opCam.hIdxTUCam, &capaAttr)) + { + if (0 == val.compare("TRUE")) + { + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_ENABLETIMESTAMP, 1); + } + else + { + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_ENABLETIMESTAMP, 0); + } + } + + OnPropertyChanged(g_PropNameATEXP, val.c_str()); + + ret = DEVICE_OK; + } + } + break; + case MM::BeforeGet: + { + int nVal = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_ENABLETIMESTAMP, &nVal); + + if (1 == nVal) + { + pProp->Set("TRUE"); + } + else + { + pProp->Set("FALSE"); + } + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + /** * Handles "Temperature" property. */ @@ -4985,14 +6012,24 @@ int CMMTUCam::OnTemperature(MM::PropertyBase* pProp, MM::ActionType eAct) pProp->Get(dblTemp); m_fValTemp = (float)dblTemp; - TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, (dblTemp + 50)); +// TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, (dblTemp + m_nMidTemp)); + TUCAM_Prop_SetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE, (dblTemp * m_fScaTemp + m_nMidTemp)); ret = DEVICE_OK; } break; case MM::BeforeGet: { - pProp->Set(m_fValTemp); + double dblTemp; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetValue(m_opCam.hIdxTUCam, TUIDP_TEMPERATURE_TARGET, &dblTemp)) + { +// pProp->Set((dblTemp - 50)); + pProp->Set((dblTemp - m_nMidTemp) / m_fScaTemp); + } + else + { + pProp->Set(m_fValTemp); + } ret = DEVICE_OK; } @@ -5314,10 +6351,14 @@ int CMMTUCam::OnTriggerMode(MM::PropertyBase* pProp, MM::ActionType eAct) { m_tgrAttr.nTgrMode = TUCCM_SEQUENCE; } - else if (0 == val.compare(g_TRIGGER_STD)) + else if (0 == val.compare(g_TRIGGER_STD) || 0 == val.compare(g_TRIGGER_STDOVERLAP)) { m_tgrAttr.nTgrMode = TUCCM_TRIGGER_STANDARD; } + else if (0 == val.compare(g_TRIGGER_STDNONOVERLAP)) + { + m_tgrAttr.nTgrMode = TUCCM_TRIGGER_STANDARD_NONOVERLAP; + } else if (0 == val.compare(g_TRIGGER_SYN)) { m_tgrAttr.nTgrMode = TUCCM_TRIGGER_SYNCHRONOUS; @@ -5365,7 +6406,19 @@ int CMMTUCam::OnTriggerMode(MM::PropertyBase* pProp, MM::ActionType eAct) } else if (TUCCM_TRIGGER_STANDARD == m_tgrAttr.nTgrMode) { - pProp->Set(g_TRIGGER_STD); + if (IsSupport95V2New() || IsSupport401DNew() || IsSupport400BSIV3New()) + { + pProp->Set(g_TRIGGER_STDOVERLAP); + } + else + { + pProp->Set(g_TRIGGER_STD); + } + + } + else if (TUCCM_TRIGGER_STANDARD_NONOVERLAP == m_tgrAttr.nTgrMode) + { + pProp->Set(g_TRIGGER_STDNONOVERLAP); } else if (TUCCM_TRIGGER_SYNCHRONOUS == m_tgrAttr.nTgrMode) { @@ -5557,6 +6610,41 @@ int CMMTUCam::OnTriggerDelay(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +int CMMTUCam::OnTriggerFilter(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + long lVal = 0; + pProp->Get(lVal); + + TUCAM_Capa_SetValue(m_opCam.hIdxTUCam, TUIDC_SIGNALFILTER, lVal); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + int nVal = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_SIGNALFILTER, &nVal); + + pProp->Set((long)(nVal)); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + int CMMTUCam::OnTriggerFrames(MM::PropertyBase* pProp, MM::ActionType eAct) { if (NULL == m_opCam.hIdxTUCam) @@ -5596,6 +6684,45 @@ int CMMTUCam::OnTriggerFrames(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +int CMMTUCam::OnTriggerTotalFrames(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (NULL == m_opCam.hIdxTUCam) + return DEVICE_NOT_CONNECTED; + + int ret = DEVICE_ERR; + switch (eAct) + { + case MM::AfterSet: + { + long lVal = 0; + pProp->Get(lVal); + + if (TUCCM_SEQUENCE < m_tgrAttr.nTgrMode && m_tgrAttr.nTgrMode < TUCCM_TRIGGER_SOFTWARE) + { + m_tgrAttr.nFrames = lVal; + } + + TUCAM_Cap_SetTrigger(m_opCam.hIdxTUCam, m_tgrAttr); + + ret = DEVICE_OK; + } + break; + case MM::BeforeGet: + { + TUCAM_Cap_GetTrigger(m_opCam.hIdxTUCam, &m_tgrAttr); + + pProp->Set((long)(m_tgrAttr.nFrames)); + + ret = DEVICE_OK; + } + break; + default: + break; + } + + return ret; +} + int CMMTUCam::OnTriggerDoSoftware(MM::PropertyBase* pProp, MM::ActionType eAct) { if (NULL == m_opCam.hIdxTUCam) @@ -5965,6 +7092,10 @@ int CMMTUCam::OnTrgOutKindMode(MM::PropertyBase* pProp, MM::ActionType eAct) { m_tgrOutAttr.nTgrOutMode = 4; } + else if (0 == val.compare(g_TRIGGER_TRIREADY)) + { + m_tgrOutAttr.nTgrOutMode = 6; + } else if (0 == val.compare(g_TRIGGER_LOW)) { m_tgrOutAttr.nTgrOutMode = 0; @@ -6032,6 +7163,10 @@ int CMMTUCam::OnTrgOutKindMode(MM::PropertyBase* pProp, MM::ActionType eAct) { pProp->Set(g_TRIGGER_READEND); } + else if (6 == m_tgrOutAttr.nTgrOutMode) + { + pProp->Set(g_TRIGGER_TRIREADY); + } ret = DEVICE_OK; } @@ -6574,6 +7709,15 @@ int CMMTUCam::ResizeImageBuffer() if (TUCAMRET_SUCCESS != TUCAM_Dev_GetInfo(m_opCam.hIdxTUCam, &valHeight)) return DEVICE_NATIVE_MODULE_FAILED; + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID) + { + ResizeBinImageBufferFL9BW(valWidth.nValue, valHeight.nValue); + } + else if (PID_FL_26BW == m_nPID) + { + ResizeBinImageBufferFL26BW(valWidth.nValue, valHeight.nValue); + } + char sz[256] = {0}; sprintf(sz, "[ResizeImageBuffer]:Width:%d, Height:%d, BytesPerPixel:%d\n", valWidth.nValue, valHeight.nValue, m_frame.ucElemBytes * nChnnels); OutputDebugString(sz); @@ -6600,6 +7744,50 @@ int CMMTUCam::ResizeImageBuffer() return DEVICE_OK; } +void CMMTUCam::ResizeBinImageBufferFL9BW(int &width, int &height) +{ + int bin = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BINNING_SUM, &bin); + + if (5 == bin) + bin = 8; + else if (4 == bin) + bin = 6; + else + bin += 1; + + if (!m_bROI) + { + int nMaxWid = width / bin; + int nMaxHei = height / bin; + + width = (nMaxWid >> 2) << 2; + height = (nMaxHei >> 2) << 2; + } +} + +void CMMTUCam::ResizeBinImageBufferFL26BW(int &width, int &height) +{ + int bin = 0; + TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_BINNING_SUM, &bin); + + if (6 == bin) + bin = 8; + else if (7 == bin) + bin = 16; + else + bin += 1; + + if (!m_bROI) + { + int nMaxWid = width / bin; + int nMaxHei = height / bin; + + width = (nMaxWid >> 2) << 2; + height = (nMaxHei >> 2) << 2; + } +} + void CMMTUCam::GenerateEmptyImage(ImgBuffer& img) { MMThreadGuard g(imgPixelsLock_); @@ -6977,7 +8165,7 @@ void CMMTUCam::RunTemperature() dw = GetTickCount(); - if (isSupportFanWaterCool()) // 400BSIV2 BCD = 0x05, 0x07, 0x09 ä¸å¯è°ƒé£Žæ‰‡ç›¸æœº + if (isSupportSoftProtect()) // 400BSIV2 BCD = 0x05, 0x07, 0x09 ²»¿Éµ÷·çÉÈÃà»ú { int nFan = 0; TUCAM_Capa_GetValue(m_opCam.hIdxTUCam, TUIDC_FAN_GEAR, &nFan); @@ -7177,7 +8365,7 @@ int CMMTUCam::WaitForFrame(ImgBuffer& img) MMThreadGuard g(imgPixelsLock_); m_frame.ucFormatGet = TUFRM_FMT_USUAl; // Set usual format - if (TUCAMRET_SUCCESS == TUCAM_Buf_WaitForFrame(m_opCam.hIdxTUCam, &m_frame)) + if (TUCAMRET_SUCCESS == TUCAM_Buf_WaitForFrame(m_opCam.hIdxTUCam, &m_frame, 1000)) { if (img.Height() == 0 || img.Width() == 0 || img.Depth() == 0) return DEVICE_OUT_OF_MEMORY; @@ -7369,7 +8557,11 @@ bool CMMTUCam::isSupportFanCool() { bool bSupport = false; bSupport = isSupportFanWaterCool(); - if (PID_FL_20BW == m_nPID) + if (m_nPID == PID_FL_9BW || PID_FL_9BW_LT == m_nPID || PID_FL_20BW == m_nPID || m_nPID == PID_FL_26BW) + { + return true; + } + if (IsSupportAries16()) { return true; } @@ -7396,6 +8588,17 @@ bool CMMTUCam::isSupportFanWaterCool() return false; } +bool CMMTUCam::isSupportSoftProtect() +{ + bool bSupport = false; + bSupport = isSupportFanWaterCool(); + if (IsSupport400BSIV3New() || IsSupport95V2New()) + { + return false; + } + return bSupport; +} + void CMMTUCam::UpdateSlitHeightRange() { int nImgMode = 0; @@ -7433,7 +8636,7 @@ void CMMTUCam::UpdateExpRange() exposureMinimum_ = propAttr.dbValMin; exposureMaximum_ = propAttr.dbValMax; - if (PID_FL_20BW == m_nPID) + if (PID_FL_9BW == m_nPID || PID_FL_9BW_LT == m_nPID || PID_FL_20BW == m_nPID || PID_FL_26BW == m_nPID) { exposureMaximum_ = 3600000; } @@ -7445,3 +8648,24 @@ void CMMTUCam::UpdateExpRange() SetPropertyLimits(MM::g_Keyword_Exposure, exposureMinimum_, exposureMaximum_); } + +void CMMTUCam::UpdateLevelsRange() +{ + TUCAM_PROP_ATTR propAttr; + propAttr.nIdxChn = 0; + propAttr.idProp = TUIDP_LFTLEVELS; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + SetPropertyLimits(g_PropNameLLev, (int)propAttr.dbValMin, (int)propAttr.dbValMax); + + SetProperty(g_PropNameLLev, CDeviceUtils::ConvertToString((int)propAttr.dbValMin)); + } + + propAttr.idProp = TUIDP_RGTLEVELS; + if (TUCAMRET_SUCCESS == TUCAM_Prop_GetAttr(m_opCam.hIdxTUCam, &propAttr)) + { + SetPropertyLimits(g_PropNameRLev, (int)propAttr.dbValMin, (int)propAttr.dbValMax); + + SetProperty(g_PropNameRLev, CDeviceUtils::ConvertToString((int)propAttr.dbValMax)); + } +} From 3b352c2c9dbc59228187efd4823b0a97058a0d24 Mon Sep 17 00:00:00 2001 From: fandayu <57091941+fandayu@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:57:34 +0800 Subject: [PATCH 129/141] Update MMTUCam.h --- DeviceAdapters/TUCam/MMTUCam.h | 44 ++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/DeviceAdapters/TUCam/MMTUCam.h b/DeviceAdapters/TUCam/MMTUCam.h index a6d6fac05..662a53ecc 100755 --- a/DeviceAdapters/TUCam/MMTUCam.h +++ b/DeviceAdapters/TUCam/MMTUCam.h @@ -8,12 +8,12 @@ // microscope devices and enables testing of the rest of the // system without the need to connect to the actual hardware. // -// AUTHOR: fandayu, fandayu@tucsen.com 2022 +// AUTHOR: fandayu, fandayu@tucsen.com 2024 // // Karl Hoover (stuff such as programmable CCD size & the various image processors) // Arther Edelstein ( equipment error simulation) // -// COPYRIGHT: Tucsen Photonics Co., Ltd., 2022 +// COPYRIGHT: Tucsen Photonics Co., Ltd., 2024 // // // LICENSE: This file is distributed under the BSD license. @@ -91,7 +91,13 @@ const int SEVEN_SEGMENT_Y_OFFSET[] = {0, 0, 0, 1, 1, 1, 2}; #define DHYANA_4040BSI 0xE413 #define DHYANA_400BSIV3 0xE419 #define DHYANA_XF4040BSI 0xE41B +#define PID_FL_20 0xEC0D +#define PID_FL_9BW 0xE422 +#define PID_FL_9BW_LT 0xE426 +#define PID_FL_26BW 0xE423 +#define PID_ARIES16LT 0xE424 +#define PID_ARIES16 0xE425 /////////////////////// // ImgMode /////////////////////// @@ -251,8 +257,11 @@ class CMMTUCam : public CCameraBase int OnTestProperty(MM::PropertyBase* pProp, MM::ActionType eAct, long); int OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnBinningSum(MM::PropertyBase* pProp, MM::ActionType eAct); int OnPixelClock(MM::PropertyBase* pProp, MM::ActionType eAct); int OnExposure(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnBrightness(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnPixelRatio(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGlobalGain(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFrameRate(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSensorReset(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -270,11 +279,14 @@ class CMMTUCam : public CCameraBase int OnRollingScanReset(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTestImageMode(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnGlobalGainMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGAINMode(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnShutterMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnModeSelect(MM::PropertyBase* pProp, MM::ActionType eAct); int OnImageMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnPixelType(MM::PropertyBase* pProp, MM::ActionType eAct); int OnBitDepth(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnBitDepthEum(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFlipH(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFlipV(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGamma(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -285,7 +297,9 @@ class CMMTUCam : public CCameraBase int OnRedGain(MM::PropertyBase* pProp, MM::ActionType eAct); int OnGreenGain(MM::PropertyBase* pProp, MM::ActionType eAct); int OnBlueGain(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnATExpMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnATExposure(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTimeStamp(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTemperature(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFan(MM::PropertyBase* pProp, MM::ActionType eAct); int OnFanState(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -296,7 +310,9 @@ class CMMTUCam : public CCameraBase int OnTriggerExpMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerEdgeMode(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerDelay(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTriggerFilter(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerFrames(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTriggerTotalFrames(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTriggerDoSoftware(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSharpness(MM::PropertyBase* pProp, MM::ActionType eAct); int OnDPCLevel(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -327,7 +343,9 @@ class CMMTUCam : public CCameraBase private: + int SetAllowedDepth(); int SetAllowedBinning(); + int SetAllowedBinningSum(); int SetAllowedPixelClock(); int SetAllowedFanGear(); @@ -338,12 +356,21 @@ class CMMTUCam : public CCameraBase int SetAllowedRSReset(); int SetAllowedTestImg(); int SetAllowedClrTemp(); + int SetAllowedShutterMode(); void TestResourceLocking(const bool); void GenerateEmptyImage(ImgBuffer& img); void GenerateSyntheticImage(ImgBuffer& img, double exp); int ResizeImageBuffer(); + void ResizeBinImageBufferFL9BW(int &width, int &height); + void ResizeBinImageBufferFL26BW(int &width, int &height); + + bool IsSupport95V2New() { return DHYANA_D95_V2 == m_nPID && m_nBCD >= 0x2000; } + bool IsSupport401DNew() { return DHYANA_401D == m_nPID && m_nBCD >= 0x2000; } + bool IsSupport400BSIV3New() { return DHYANA_400BSIV3 == m_nPID && m_nBCD >= 0x2000; } + bool IsSupportAries16() { return 0xE424 == m_nPID || 0xE425 == m_nPID; } + static const double nominalPixelSizeUm_; double exposureMaximum_; @@ -410,12 +437,17 @@ class CMMTUCam : public CCameraBase int WaitForFrame(ImgBuffer& img); bool SaveRaw(char *pfileName, unsigned char *pData, unsigned long ulSize); - bool isSupportFanWaterCool(); bool isSupportFanCool(); + bool isSupportFanWaterCool(); + bool isSupportSoftProtect(); void UpdateSlitHeightRange(); void UpdateExpRange(); - + void UpdateLevelsRange(); +/* + void LoadProfile(); + void SaveProfile(); +*/ static int s_nNumCam; // The number of cameras static int s_nCntCam; // The count of camera @@ -425,10 +457,12 @@ class CMMTUCam : public CCameraBase int m_nIdxGain; // The gain mode int m_nMaxHeight; // The max height size int m_nTriType; // The trigger type + int m_nZeroTemp; // The zero temperature reference value char m_szImgPath[MAX_PATH]; // The save image path float m_fCurTemp; // The current temperature - float m_fValTemp; // The temperature value + float m_fValTemp; // The current temperature + float m_fScaTemp; // The scale of temperature int m_nMidTemp; // The middle value of temperature bool m_bROI; // The ROI state bool m_bSaving; // The tag of save image From 71db3145109334aab030644f263706fba7793ddc Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Tue, 16 Jan 2024 17:12:21 -0600 Subject: [PATCH 130/141] TUCam: Use new SDK (20240112) --- DeviceAdapters/TUCam/MMTUCam.vcxproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DeviceAdapters/TUCam/MMTUCam.vcxproj b/DeviceAdapters/TUCam/MMTUCam.vcxproj index 337a30602..76a78f2fe 100644 --- a/DeviceAdapters/TUCam/MMTUCam.vcxproj +++ b/DeviceAdapters/TUCam/MMTUCam.vcxproj @@ -65,7 +65,7 @@ Level4 ProgramDatabase 4290;4996;%(DisableSpecificWarnings) - $(MM_3RDPARTYPUBLIC)\TUCam\TUCam_20220808\sdk\inc; %(AdditionalIncludeDirectories) + $(MM_3RDPARTYPUBLIC)\TUCam\TUCam_20240112\inc;%(AdditionalIncludeDirectories) $(OutDir)$(TargetName)$(TargetExt) @@ -78,7 +78,7 @@ MachineX64 - $(MM_3RDPARTYPUBLIC)\TUCam\TUCam_20220808\sdk\lib\$(Platform) + $(MM_3RDPARTYPUBLIC)\TUCam\TUCam_20240112\lib\$(Platform) TUCam.lib @@ -97,7 +97,7 @@ Level4 ProgramDatabase 4290;4996;%(DisableSpecificWarnings) - $(MM_3RDPARTYPUBLIC)\TUCam\TUCam_20220808\sdk\inc; %(AdditionalIncludeDirectories) + $(MM_3RDPARTYPUBLIC)\TUCam\TUCam_20240112\inc;%(AdditionalIncludeDirectories) $(OutDir)$(TargetName)$(TargetExt) @@ -111,7 +111,7 @@ MachineX64 - $(MM_3RDPARTYPUBLIC)\TUCam\TUCam_20220808\sdk\lib\$(Platform) + $(MM_3RDPARTYPUBLIC)\TUCam\TUCam_20240112\lib\$(Platform) TUCam.lib @@ -135,4 +135,4 @@ - + \ No newline at end of file From 5a0ba3b27b671a3fae38eb19ead29985b2ebfb04 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 22 Jan 2024 14:42:42 -0600 Subject: [PATCH 131/141] MMDevice: Remove gettimeofday() for Windows It is a bad idea to define functions named like POSIX standard ones, because other people can have the same idea and they can clash (we previously had this issue with Python < 3.7 and gettimeofday()). Fortunately no device adapter is currently using this function on Windows, so just remove the dead code. In modern C++, std::chrono offers better time functions for any new code that may need similar facilities. A few errors corrected in 3 device adapters that used definitions from Windows.h, which is no longer included (without WIN32_LEAN_AND_MEAN) by DeviceUtils.h. --- .../IlluminateLEDArray/LEDArray.cpp | 2 +- DeviceAdapters/IlluminateLEDArray/LEDArray.h | 2 +- DeviceAdapters/PointGrey/PointGrey.cpp | 4 +- DeviceAdapters/SigmaKoki/Camera.cpp | 2 +- MMDevice/DeviceUtils.cpp | 41 ------------------- MMDevice/DeviceUtils.h | 25 ----------- 6 files changed, 5 insertions(+), 71 deletions(-) diff --git a/DeviceAdapters/IlluminateLEDArray/LEDArray.cpp b/DeviceAdapters/IlluminateLEDArray/LEDArray.cpp index 6e24ece37..2540c6414 100644 --- a/DeviceAdapters/IlluminateLEDArray/LEDArray.cpp +++ b/DeviceAdapters/IlluminateLEDArray/LEDArray.cpp @@ -908,7 +908,7 @@ int LedArray::ReadResponse() { return DEVICE_ERR; } -int LedArray::SetShutter(boolean open) +int LedArray::SetShutter(bool open) { //TODO maybe update property? shutterOpen_ = open; diff --git a/DeviceAdapters/IlluminateLEDArray/LEDArray.h b/DeviceAdapters/IlluminateLEDArray/LEDArray.h index f2f30a6dd..626b2ad76 100644 --- a/DeviceAdapters/IlluminateLEDArray/LEDArray.h +++ b/DeviceAdapters/IlluminateLEDArray/LEDArray.h @@ -271,7 +271,7 @@ class LedArray: public CSLMBase int OnBrightness(MM::PropertyBase* pPropt, MM::ActionType eAct); - int SetShutter(boolean open); + int SetShutter(bool open); bool GetShutter(); private: diff --git a/DeviceAdapters/PointGrey/PointGrey.cpp b/DeviceAdapters/PointGrey/PointGrey.cpp index 3cce8dd1d..c6a4d3b91 100644 --- a/DeviceAdapters/PointGrey/PointGrey.cpp +++ b/DeviceAdapters/PointGrey/PointGrey.cpp @@ -1928,7 +1928,7 @@ int PointGrey::CameraID(PGRGuid id, std::string* camIdString) */ int PointGrey::CameraGUIDfromOurID(BusManager* busMgr, PGRGuid* guid, std::string ourId) { - boolean found = false; + bool found = false; unsigned int numCameras; PGRGuid localGuid; Error error = busMgr->GetNumOfCameras(&numCameras); @@ -1984,7 +1984,7 @@ int PointGrey::VideoModeAndFrameRateEnumsFromString(std::string readableString, } // find matching Videomode and Framerate by cycling brute force through our arrays - boolean found = false; + bool found = false; unsigned int counter = 0; while (!found && counter < g_NumVideoModes) { if (parts[0] == g_VideoModes[counter]) { diff --git a/DeviceAdapters/SigmaKoki/Camera.cpp b/DeviceAdapters/SigmaKoki/Camera.cpp index f2ba1c37b..fb2013c3c 100644 --- a/DeviceAdapters/SigmaKoki/Camera.cpp +++ b/DeviceAdapters/SigmaKoki/Camera.cpp @@ -1156,7 +1156,7 @@ int Camera::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) { BOOL ans = TRUE; WORD mode = 0; - byte skippH, skippV, hBin, vBin; + BYTE skippH, skippV, hBin, vBin; cout << "Product is " << productName_ << endl; diff --git a/MMDevice/DeviceUtils.cpp b/MMDevice/DeviceUtils.cpp index c2624c4dd..edcec6967 100644 --- a/MMDevice/DeviceUtils.cpp +++ b/MMDevice/DeviceUtils.cpp @@ -188,44 +188,3 @@ bool CDeviceUtils::CheckEnvironment(std::string env) } return bvalue; } - - - -#if defined(_WIN32) && !defined(MMDEVICE_NO_GETTIMEOFDAY) - -int gettimeofday(struct timeval *tv, struct timezone *tz) -{ - FILETIME ft; - unsigned __int64 tmpres = 0; - static int tzflag; - - if (NULL != tv) - { - GetSystemTimeAsFileTime(&ft); - - tmpres |= ft.dwHighDateTime; - tmpres <<= 32; - tmpres |= ft.dwLowDateTime; - - /*converting file time to unix epoch*/ - tmpres -= DELTA_EPOCH_IN_MICROSECS; - tmpres /= 10; /*convert into microseconds*/ - tv->tv_sec = (long)(tmpres / 1000000UL); - tv->tv_usec = (long)(tmpres % 1000000UL); - } - - if (NULL != tz) - { - if (!tzflag) - { - _tzset(); - tzflag++; - } - tz->tz_minuteswest = _timezone / 60; - tz->tz_dsttime = _daylight; - } - - return 0; -} - -#endif diff --git a/MMDevice/DeviceUtils.h b/MMDevice/DeviceUtils.h index bee2f5ce1..32eb6577c 100644 --- a/MMDevice/DeviceUtils.h +++ b/MMDevice/DeviceUtils.h @@ -23,31 +23,6 @@ #include "MMDeviceConstants.h" #include #include -#ifdef _WIN32 -#include -#include -#endif - -#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) - #define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64 -#else - #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL -#endif - -// Definition of struct timezone and gettimeofday can be disabled in case -// interfacing with some other system that also tries to define conflicting -// symbols (e.g. Python <= 3.6). -#if defined(_WIN32) && !defined(MMDEVICE_NO_GETTIMEOFDAY) -struct timezone -{ - int tz_minuteswest; /* minutes W of Greenwich */ - int tz_dsttime; /* type of dst correction */ -}; - -int gettimeofday(struct timeval *tv__, struct timezone *tz__); - -#endif - class CDeviceUtils { From d5ea2cf71dd03be1300a5aed3121cf5d56823487 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 22 Jan 2024 17:13:04 -0600 Subject: [PATCH 132/141] MMDevice/MMCore: Export SWIG include dir for Meson This is required for pymmcore and MMCoreJ Meson build to get the include directories to pass to SWIG, due to the lack of direct SWIG support in Meson. --- MMCore/meson.build | 5 +++++ MMDevice/meson.build | 3 +++ 2 files changed, 8 insertions(+) diff --git a/MMCore/meson.build b/MMCore/meson.build index a24403066..fd9fbcecc 100644 --- a/MMCore/meson.build +++ b/MMCore/meson.build @@ -98,3 +98,8 @@ mmcore = declare_dependency( link_with: mmcore_lib, dependencies: mmdevice_dep, ) + +# For providing include dir to SWIG when using this project as a subproject +swig_include_dirs = mmdevice_proj.get_variable('swig_include_dirs') + [ + meson.current_source_dir(), +] diff --git a/MMDevice/meson.build b/MMDevice/meson.build index 9f6bdf496..a7f21a8d2 100644 --- a/MMDevice/meson.build +++ b/MMDevice/meson.build @@ -58,3 +58,6 @@ mmdevice = declare_dependency( include_directories: mmdevice_include_dir, link_with: mmdevice_lib, ) + +# For providing include dir to SWIG when using this project as a subproject +swig_include_dirs = [meson.current_source_dir()] From 678a4903cd9fd61c0d8f813b4a695183dde2c763 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 22 Jan 2024 17:41:28 -0600 Subject: [PATCH 133/141] MMDevice/MMCore: Make Meson build tests optional --- MMCore/meson.build | 16 +++++++++++++++- MMCore/meson_options.txt | 3 +++ MMCore/unittest/meson.build | 4 +++- MMDevice/meson_options.txt | 3 +++ MMDevice/unittest/meson.build | 4 +++- 5 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 MMCore/meson_options.txt create mode 100644 MMDevice/meson_options.txt diff --git a/MMCore/meson.build b/MMCore/meson.build index fd9fbcecc..0373d739a 100644 --- a/MMCore/meson.build +++ b/MMCore/meson.build @@ -19,7 +19,21 @@ endif # MMDevice must be copied into subprojects/ for this experimental build to work # (unless MMCore is itself being used as a subproject). -mmdevice_proj = subproject('MMDevice') +if get_option('tests').enabled() + tests_option = 'enabled' +elif get_option('tests').disabled() + tests_option = 'disabled' +else + tests_option = 'auto' +endif +mmdevice_proj = subproject( + 'MMDevice', + # Propagate value of 'tests' option ('yield: true' in MMDeivce's 'tests' + # option did not seem to work; Meson 1.3.1). + default_options: { + 'tests': tests_option, + }, +) mmdevice_dep = mmdevice_proj.get_variable('mmdevice') mmcore_sources = files( diff --git a/MMCore/meson_options.txt b/MMCore/meson_options.txt new file mode 100644 index 000000000..e9af6fe9a --- /dev/null +++ b/MMCore/meson_options.txt @@ -0,0 +1,3 @@ +option('tests', type: 'feature', value: 'enabled', + description: 'Build unit tests', +) diff --git a/MMCore/unittest/meson.build b/MMCore/unittest/meson.build index 416d51109..67feffc25 100644 --- a/MMCore/unittest/meson.build +++ b/MMCore/unittest/meson.build @@ -5,6 +5,8 @@ catch2_with_main_dep = dependency( 'catch2-with-main', allow_fallback: true, include_type: 'system', + required: get_option('tests'), + disabler: true, ) mmcore_test_sources = files( @@ -28,4 +30,4 @@ mmcore_test_exe = executable( ], ) -test('MMCore tests', mmcore_test_exe) \ No newline at end of file +test('MMCore tests', mmcore_test_exe) diff --git a/MMDevice/meson_options.txt b/MMDevice/meson_options.txt new file mode 100644 index 000000000..4f5d777ee --- /dev/null +++ b/MMDevice/meson_options.txt @@ -0,0 +1,3 @@ +option('tests', type: 'feature', value: 'enabled', yield: true, + description: 'Build unit tests', +) diff --git a/MMDevice/unittest/meson.build b/MMDevice/unittest/meson.build index 7c4f77276..dbca08cff 100644 --- a/MMDevice/unittest/meson.build +++ b/MMDevice/unittest/meson.build @@ -5,6 +5,8 @@ catch2_with_main_dep = dependency( 'catch2-with-main', allow_fallback: true, include_type: 'system', + required: get_option('tests'), + disabler: true, ) mmdevice_test_sources = files( @@ -23,4 +25,4 @@ mmdevice_test_exe = executable( test( 'MMDevice unit tests', mmdevice_test_exe, -) \ No newline at end of file +) From 127b0ffeb08c8b7ceebab93f76ec49d9d12cbbe3 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 22 Jan 2024 17:43:49 -0600 Subject: [PATCH 134/141] MMDevice/MMCore: Add 'threads' dep to Meson build Required in some Linux environments. --- MMCore/meson.build | 5 ++++- MMDevice/meson.build | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/MMCore/meson.build b/MMCore/meson.build index 0373d739a..3049fbb65 100644 --- a/MMCore/meson.build +++ b/MMCore/meson.build @@ -99,7 +99,10 @@ mmcore_lib = static_library( 'MMCore', sources: mmcore_sources, include_directories: mmcore_include_dir, - dependencies: mmdevice_dep, + dependencies: [ + mmdevice_dep, + dependency('threads'), + ], cpp_args: [ '-D_CRT_SECURE_NO_WARNINGS', # TODO Eliminate the need ], diff --git a/MMDevice/meson.build b/MMDevice/meson.build index a7f21a8d2..dcddc8cb0 100644 --- a/MMDevice/meson.build +++ b/MMDevice/meson.build @@ -50,6 +50,9 @@ mmdevice_lib = static_library( # MMDevice does not depend on any external library. This is a big advantage # in simplifing its usage (given hundreds of device adapters depending on # MMDevice), so think twice before adding dependencies. + dependencies: [ + dependency('threads'), + ], ) subdir('unittest') From 1f51dab926045b2d21b7523933bd8e45201c4425 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 24 Jan 2024 08:38:36 -0600 Subject: [PATCH 135/141] Delete unmaintained CMake script This uses obsolete CMake constructs (not "modern CMake"). And it will (continue to) bit-rot in the absense of regular testing. --- .../AndorLaserCombiner/CMakeLists.txt | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 DeviceAdapters/AndorLaserCombiner/CMakeLists.txt diff --git a/DeviceAdapters/AndorLaserCombiner/CMakeLists.txt b/DeviceAdapters/AndorLaserCombiner/CMakeLists.txt deleted file mode 100644 index f1bb86084..000000000 --- a/DeviceAdapters/AndorLaserCombiner/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -project( ANDOR_LASER_COMBINER ) -cmake_minimum_required( VERSION 2.6 ) -set(CMAKE_SUPPRESS_REGENERATION TRUE) # get rid of ZERO_CHECK project - -# Find SDK headers -set( SDK_INCLUDE_DIRS - "../../../3rdparty/Andor/ALC/ALC_REV_SDK/Include" ) -find_path( SDK_INC - NAMES ALC_REV.h LaserState.h - PATHS ${SDK_INCLUDE_DIRS} ) -if ( ${SDK_INC} MATCHES "SDK_INC-NOTFOUND" ) - message( FATAL_ERROR "Could not find ALC SDK headers in ${SDK_INC}" ) -endif () -message( STATUS "Found ALC SDK headers " ${SDK_INC} ) - -# Common libraries -set( COMMON_INC - "../../../3rdpartypublic/boost" ) - -include_directories( ${SDK_INC} ${COMMON_INC} ) -add_definitions( -D_USRDLL -DMODULE_EXPORTS ) -set( ALC_SOURCES - AndorLaserCombiner.cpp AndorLaserCombiner.h - ${SDK_INC}/ALC_REV.h ${SDK_INC}/LaserState.h - ../../MMDevice/DeviceUtils.cpp ../../MMDevice/ModuleInterface.cpp ../../MMDevice/Property.cpp ) -add_library( mmgr_dal_AndorLaserCombiner SHARED ${ALC_SOURCES} ) From 41db67952e37b236a8d8f4dd8efdb5163307728e Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 24 Jan 2024 08:42:39 -0600 Subject: [PATCH 136/141] Remove redundant MODULE_EXPORTS in device adapters MODULE_EXPORTS is defined in MMDeviceAdapter.props, but some device adapter projects (old ones, and copied or cargo-culted ones) redundantly defined it. ```sh find DeviceAdapters -name '*.vcxproj' -print0 \ | xargs -0 gsed -i 's/MODULE_EXPORTS;//' ``` (`gsed` being GNU sed) --- DeviceAdapters/AAAOTF/AAAOTF.vcxproj | 4 ++-- DeviceAdapters/ABS/ABSCamera.vcxproj | 4 ++-- DeviceAdapters/AOTF/AOTF.vcxproj | 4 ++-- DeviceAdapters/ASIFW1000/ASIFW1000.vcxproj | 4 ++-- DeviceAdapters/ASIStage/ASIStage.vcxproj | 4 ++-- DeviceAdapters/ASITiger/ASITiger.vcxproj | 4 ++-- DeviceAdapters/ASIWPTR/ASIwptr.vcxproj | 4 ++-- .../AgilentLaserCombiner/AgilentLaserCombiner.vcxproj | 4 ++-- DeviceAdapters/Aladdin/Aladdin.vcxproj | 4 ++-- DeviceAdapters/Andor/Andor.vcxproj | 4 ++-- .../AndorLaserCombiner/AndorLaserCombiner.vcxproj | 4 ++-- DeviceAdapters/AndorSDK3/AndorSDK3.vcxproj | 4 ++-- DeviceAdapters/AndorShamrock/AndorShamrock.vcxproj | 4 ++-- DeviceAdapters/Aquinas/Aquinas.vcxproj | 4 ++-- DeviceAdapters/Arduino/Arduino.vcxproj | 4 ++-- .../Arduino32bitBoards/Arduino32bitBoards.vcxproj | 4 ++-- DeviceAdapters/ArduinoCounter/ArduinoCounter.vcxproj | 4 ++-- DeviceAdapters/Atik/AtikCamera.vcxproj | 4 ++-- DeviceAdapters/BDPathway/BDPathway.vcxproj | 4 ++-- DeviceAdapters/Basler/BaslerPylon.vcxproj | 4 ++-- DeviceAdapters/BaumerOptronic/BaumerOptronic.vcxproj | 4 ++-- .../BlueboxOptics_niji/BlueboxOptics_niji.vcxproj | 4 ++-- DeviceAdapters/CARVII/CARVII.vcxproj | 4 ++-- DeviceAdapters/CSUW1/CSUW1.vcxproj | 4 ++-- DeviceAdapters/Cobolt/Cobolt.vcxproj | 4 ++-- DeviceAdapters/CoherentCube/CoherentCube.vcxproj | 4 ++-- DeviceAdapters/Conix/Conix.vcxproj | 4 ++-- DeviceAdapters/Corvus/Corvus.vcxproj | 4 ++-- DeviceAdapters/DTOpenLayer/DTOpenLayer.vcxproj | 4 ++-- DeviceAdapters/DemoCamera/DemoCamera.vcxproj | 4 ++-- DeviceAdapters/DirectElectron/vsprojects/DECamera.vcxproj | 4 ++-- DeviceAdapters/Dragonfly/Dragonfly.vcxproj | 4 ++-- DeviceAdapters/FLICamera/FLICamera.vcxproj | 4 ++-- DeviceAdapters/Fli/FirstLightImagingCameras.vcxproj | 4 ++-- DeviceAdapters/FreeSerialPort/FreeSerialPort.vcxproj | 4 ++-- DeviceAdapters/GenericSLM/GenericSLM.vcxproj | 4 ++-- DeviceAdapters/GigECamera/GigECamera.vcxproj | 4 ++-- DeviceAdapters/HIDManager/HIDManager.vcxproj | 4 ++-- DeviceAdapters/IDS_uEye/IDS_uEye.vcxproj | 4 ++-- DeviceAdapters/IlluminateLEDArray/LEDArray.vcxproj | 4 ++-- .../ImageProcessorChain/ImageProcessorChain.vcxproj | 4 ++-- .../IntegratedLaserEngine/IntegratedLaserEngine.vcxproj | 4 ++-- DeviceAdapters/JAI/JAI.vcxproj | 4 ++-- DeviceAdapters/K8055/K8055.vcxproj | 4 ++-- DeviceAdapters/K8061/K8061.vcxproj | 4 ++-- .../LaserQuantumLaser/LaserQuantumLaser.vcxproj | 4 ++-- DeviceAdapters/LeicaDMI/LeicaDMI.vcxproj | 4 ++-- DeviceAdapters/LeicaDMR/LeicaDMR.vcxproj | 4 ++-- DeviceAdapters/LeicaDMSTC/LeicaDMSTC.vcxproj | 4 ++-- .../LightSheetManager/LightSheetManager.vcxproj | 4 ++-- DeviceAdapters/Ludl/Ludl.vcxproj | 4 ++-- DeviceAdapters/LudlLow/LudlLow.vcxproj | 4 ++-- DeviceAdapters/Lumencor/Lumencor.vcxproj | 4 ++-- DeviceAdapters/LumencorCIA/LumencorCIA.vcxproj | 4 ++-- DeviceAdapters/LumencorSpectra/LumencorSpectra.vcxproj | 4 ++-- DeviceAdapters/MCCDAQ/MCCDAQ.vcxproj | 4 ++-- DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj | 4 ++-- DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.vcxproj | 4 ++-- DeviceAdapters/MP285/MP285.vcxproj | 4 ++-- DeviceAdapters/MT20/MT20.vcxproj | 4 ++-- DeviceAdapters/MaestroServo/MaestroServo.vcxproj | 4 ++-- DeviceAdapters/Marzhauser-LStep/MarzhauserLStep.vcxproj | 4 ++-- DeviceAdapters/Marzhauser/Marzhauser.vcxproj | 4 ++-- DeviceAdapters/MicroFPGA/MicroFPGA.vcxproj | 8 ++++---- DeviceAdapters/MicroPoint/MicroPoint.vcxproj | 4 ++-- DeviceAdapters/Mightex/Mightex.vcxproj | 4 ++-- DeviceAdapters/Mightex_BLS/Mightex_BLS.vcxproj | 4 ++-- DeviceAdapters/Modbus/Modbus.vcxproj | 4 ++-- DeviceAdapters/Motic/MoticCamera.vcxproj | 4 ++-- DeviceAdapters/MoticMicroscope/MoticMicroscope.vcxproj | 4 ++-- DeviceAdapters/NI100X/NI100X.vcxproj | 4 ++-- DeviceAdapters/NIDAQ/NIDAQ.vcxproj | 4 ++-- DeviceAdapters/NIMultiAnalog/NIMultiAnalog.vcxproj | 4 ++-- DeviceAdapters/Neos/Neos.vcxproj | 4 ++-- DeviceAdapters/NewportCONEX/NewportCONEX.vcxproj | 4 ++-- DeviceAdapters/Nikon/Nikon.vcxproj | 4 ++-- DeviceAdapters/NikonTE2000/NikonTE2000.vcxproj | 4 ++-- DeviceAdapters/OVP_ECS2/OVP_ECS2.vcxproj | 4 ++-- DeviceAdapters/ObjectiveImaging/ObjectiveImaging.vcxproj | 4 ++-- DeviceAdapters/Omicron/Omicron.vcxproj | 4 ++-- DeviceAdapters/OpenCVgrabber/OpenCVgrabber.vcxproj | 4 ++-- DeviceAdapters/Oxxius/Oxxius.vcxproj | 4 ++-- DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj | 4 ++-- DeviceAdapters/PCO_Generic/PCO_Camera.vcxproj | 4 ++-- DeviceAdapters/PI/PI.vcxproj | 4 ++-- DeviceAdapters/PICAM/PICAM.vcxproj | 4 ++-- DeviceAdapters/PI_GCS/PI_GCS.vcxproj | 4 ++-- DeviceAdapters/PI_GCS_2/PI_GCS_2.vcxproj | 4 ++-- DeviceAdapters/PVCAM/PVCAM.vcxproj | 4 ++-- DeviceAdapters/ParallelPort/ParallelPort.vcxproj | 4 ++-- DeviceAdapters/Pecon/Pecon.vcxproj | 4 ++-- DeviceAdapters/PicardStage/PicardStage.vcxproj | 4 ++-- .../Piezosystem_30DV50/Piezosystem_30DV50.vcxproj | 4 ++-- .../Piezosystem_NV120_1/Piezosystem_NV120_1.vcxproj | 4 ++-- .../Piezosystem_NV40_1/Piezosystem_NV40_1.vcxproj | 4 ++-- .../Piezosystem_NV40_3/Piezosystem_NV40_3.vcxproj | 4 ++-- .../Piezosystem_dDrive/Piezosystem_dDrive.vcxproj | 4 ++-- DeviceAdapters/Piper/Piper.vcxproj | 4 ++-- DeviceAdapters/Pixelink/Pixelink.vcxproj | 4 ++-- DeviceAdapters/PointGrey/PointGrey.vcxproj | 4 ++-- DeviceAdapters/PrecisExcite/PrecisExcite.vcxproj | 4 ++-- DeviceAdapters/Prior/Prior.vcxproj | 4 ++-- DeviceAdapters/PriorLegacy/PriorLegacy.vcxproj | 4 ++-- DeviceAdapters/QCam/QCam.vcxproj | 4 ++-- DeviceAdapters/Rapp/Rapp.vcxproj | 4 ++-- DeviceAdapters/RaptorEPIX/HoribaEPIX.vcxproj | 4 ++-- DeviceAdapters/RaptorEPIX/RaptorEPIX.vcxproj | 4 ++-- DeviceAdapters/Sapphire/Sapphire.vcxproj | 4 ++-- DeviceAdapters/Scientifica/Scientifica.vcxproj | 4 ++-- DeviceAdapters/ScopeLED/ScopeLED.vcxproj | 4 ++-- DeviceAdapters/SerialManager/SerialManager.vcxproj | 4 ++-- DeviceAdapters/SigmaKoki/SigmaKoki.vcxproj | 4 ++-- DeviceAdapters/Skyra/Skyra.vcxproj | 4 ++-- DeviceAdapters/SmarActHCU-3D/SmarActHCU-3D.vcxproj | 4 ++-- DeviceAdapters/SouthPort/SouthPort.vcxproj | 4 ++-- DeviceAdapters/SpectralLMM5/SpectralLMM5.vcxproj | 4 ++-- DeviceAdapters/Standa/Standa.vcxproj | 4 ++-- DeviceAdapters/SutterLambda/SutterLambda.vcxproj | 4 ++-- DeviceAdapters/SutterLambda2/SutterLambda2.vcxproj | 4 ++-- DeviceAdapters/SutterStage/SutterStage.vcxproj | 4 ++-- DeviceAdapters/TISCam/TIScam.vcxproj | 4 ++-- DeviceAdapters/TSI/TSI.vcxproj | 4 ++-- DeviceAdapters/TUCam/MMTUCam.vcxproj | 4 ++-- DeviceAdapters/TeesnySLM/TeensySLM.vcxproj | 4 ++-- DeviceAdapters/Thorlabs/Thorlabs.vcxproj | 4 ++-- DeviceAdapters/ThorlabsAPTStage/ThorlabsAPTStage.vcxproj | 4 ++-- DeviceAdapters/ThorlabsDCxxxx/ThorlabsDCxxxx.vcxproj | 4 ++-- .../ThorlabsElliptecSlider/ThorlabsElliptecSlider.vcxproj | 4 ++-- .../ThorlabsFilterWheel/ThorlabsFilterWheel.vcxproj | 4 ++-- DeviceAdapters/ThorlabsPM100x/ThorlabsPM100x.vcxproj | 4 ++-- DeviceAdapters/ThorlabsSC10/ThorlabsSC10.vcxproj | 4 ++-- .../ThorlabsUSBCamera/ThorlabsUSBCamera.vcxproj | 4 ++-- DeviceAdapters/Tofra/Tofra.vcxproj | 4 ++-- .../Toptica_iBeamSmartCW/Toptica_iBeamSmartCW.vcxproj | 4 ++-- DeviceAdapters/TwainCamera/TwainCamera.vcxproj | 4 ++-- DeviceAdapters/TwoPhoton/TwoPhoton.vcxproj | 4 ++-- DeviceAdapters/USBManager/USBManager.vcxproj | 4 ++-- DeviceAdapters/USB_Viper_QPL/USB_Viper_QPL.vcxproj | 4 ++-- .../UniversalMMHubSerial/UniversalMMHubSerial.vcxproj | 4 ++-- .../UniversalMMHubUsb/UniversalMMHubUsb.vcxproj | 4 ++-- DeviceAdapters/Utilities/Utilities.vcxproj | 4 ++-- DeviceAdapters/VariLC/VariLC.vcxproj | 4 ++-- DeviceAdapters/VarispecLCTF/VarispecLCTF.vcxproj | 4 ++-- DeviceAdapters/Vincent/Vincent.vcxproj | 4 ++-- DeviceAdapters/Vortran/Stradus.vcxproj | 4 ++-- DeviceAdapters/Vortran/VersaLase.vcxproj | 4 ++-- DeviceAdapters/WieneckeSinske/WieneckeSinske.vcxproj | 4 ++-- .../XCite120PC_Exacte/XCite120PC_Exacte.vcxproj | 4 ++-- DeviceAdapters/XCiteLed/XCiteLed.vcxproj | 4 ++-- DeviceAdapters/XCiteXT600/XCiteXT600.vcxproj | 4 ++-- DeviceAdapters/Xcite/Xcite.vcxproj | 4 ++-- DeviceAdapters/Ximea/XIMEACamera.vcxproj | 4 ++-- DeviceAdapters/YodnE600/YodnE600.vcxproj | 2 +- DeviceAdapters/Yokogawa/CSUX/CSUX.vcxproj | 4 ++-- DeviceAdapters/Yokogawa/Yokogawa.vcxproj | 4 ++-- DeviceAdapters/Zaber/Zaber.vcxproj | 4 ++-- DeviceAdapters/ZeissAxioZoom/ZeissAxioZoom.vcxproj | 4 ++-- DeviceAdapters/ZeissCAN/ZeissCAN.vcxproj | 4 ++-- DeviceAdapters/ZeissCAN29/ZeissCAN29.vcxproj | 4 ++-- DeviceAdapters/dc1394/dc1394.vcxproj | 4 ++-- DeviceAdapters/kdv/KDV.vcxproj | 4 ++-- DeviceAdapters/nPoint/NPointC400.vcxproj | 4 ++-- 162 files changed, 325 insertions(+), 325 deletions(-) diff --git a/DeviceAdapters/AAAOTF/AAAOTF.vcxproj b/DeviceAdapters/AAAOTF/AAAOTF.vcxproj index 0a89a78e0..94a8c28a2 100644 --- a/DeviceAdapters/AAAOTF/AAAOTF.vcxproj +++ b/DeviceAdapters/AAAOTF/AAAOTF.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ABS/ABSCamera.vcxproj b/DeviceAdapters/ABS/ABSCamera.vcxproj index 4a5764203..3de213e3a 100644 --- a/DeviceAdapters/ABS/ABSCamera.vcxproj +++ b/DeviceAdapters/ABS/ABSCamera.vcxproj @@ -56,7 +56,7 @@ Disabled ./include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -76,7 +76,7 @@ MaxSpeed true ./include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/AOTF/AOTF.vcxproj b/DeviceAdapters/AOTF/AOTF.vcxproj index 1fe82a9ca..4cc840963 100644 --- a/DeviceAdapters/AOTF/AOTF.vcxproj +++ b/DeviceAdapters/AOTF/AOTF.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ASIFW1000/ASIFW1000.vcxproj b/DeviceAdapters/ASIFW1000/ASIFW1000.vcxproj index eb67b07ca..5c01acc5f 100644 --- a/DeviceAdapters/ASIFW1000/ASIFW1000.vcxproj +++ b/DeviceAdapters/ASIFW1000/ASIFW1000.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ASIStage/ASIStage.vcxproj b/DeviceAdapters/ASIStage/ASIStage.vcxproj index 78c31155a..5e316356d 100644 --- a/DeviceAdapters/ASIStage/ASIStage.vcxproj +++ b/DeviceAdapters/ASIStage/ASIStage.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ASITiger/ASITiger.vcxproj b/DeviceAdapters/ASITiger/ASITiger.vcxproj index d94ea84ed..d6f9b5dee 100644 --- a/DeviceAdapters/ASITiger/ASITiger.vcxproj +++ b/DeviceAdapters/ASITiger/ASITiger.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ASIWPTR/ASIwptr.vcxproj b/DeviceAdapters/ASIWPTR/ASIwptr.vcxproj index 846dc7e22..b4974caf5 100644 --- a/DeviceAdapters/ASIWPTR/ASIwptr.vcxproj +++ b/DeviceAdapters/ASIWPTR/ASIwptr.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/AgilentLaserCombiner/AgilentLaserCombiner.vcxproj b/DeviceAdapters/AgilentLaserCombiner/AgilentLaserCombiner.vcxproj index 11c019cab..8366720b3 100644 --- a/DeviceAdapters/AgilentLaserCombiner/AgilentLaserCombiner.vcxproj +++ b/DeviceAdapters/AgilentLaserCombiner/AgilentLaserCombiner.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\Agilent\LaserCombiner;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -77,7 +77,7 @@ $(MM_3RDPARTYPRIVATE)\Agilent\LaserCombiner;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Aladdin/Aladdin.vcxproj b/DeviceAdapters/Aladdin/Aladdin.vcxproj index 7c0333717..99f316a12 100644 --- a/DeviceAdapters/Aladdin/Aladdin.vcxproj +++ b/DeviceAdapters/Aladdin/Aladdin.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Andor/Andor.vcxproj b/DeviceAdapters/Andor/Andor.vcxproj index 06543d473..f2b158b08 100644 --- a/DeviceAdapters/Andor/Andor.vcxproj +++ b/DeviceAdapters/Andor/Andor.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\Andor\SDK 2.95.30003.0;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPRIVATE)\Andor\SDK 2.95.30003.0;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/AndorLaserCombiner/AndorLaserCombiner.vcxproj b/DeviceAdapters/AndorLaserCombiner/AndorLaserCombiner.vcxproj index d782a7442..0f40d9fa5 100644 --- a/DeviceAdapters/AndorLaserCombiner/AndorLaserCombiner.vcxproj +++ b/DeviceAdapters/AndorLaserCombiner/AndorLaserCombiner.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)/Andor/ALC/SDK2.4/Include; %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -74,7 +74,7 @@ $(MM_3RDPARTYPRIVATE)/Andor/ALC/SDK2.4/Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/AndorSDK3/AndorSDK3.vcxproj b/DeviceAdapters/AndorSDK3/AndorSDK3.vcxproj index 3b20cd3d3..6b0b25608 100644 --- a/DeviceAdapters/AndorSDK3/AndorSDK3.vcxproj +++ b/DeviceAdapters/AndorSDK3/AndorSDK3.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/AndorShamrock/AndorShamrock.vcxproj b/DeviceAdapters/AndorShamrock/AndorShamrock.vcxproj index 1f812f3f7..4f7dd0d60 100755 --- a/DeviceAdapters/AndorShamrock/AndorShamrock.vcxproj +++ b/DeviceAdapters/AndorShamrock/AndorShamrock.vcxproj @@ -58,7 +58,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\ShamrockSDK\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -79,7 +79,7 @@ $(MM_3RDPARTYPRIVATE)\ShamrockSDK\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Aquinas/Aquinas.vcxproj b/DeviceAdapters/Aquinas/Aquinas.vcxproj index abec0b54c..87969e1d4 100644 --- a/DeviceAdapters/Aquinas/Aquinas.vcxproj +++ b/DeviceAdapters/Aquinas/Aquinas.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -71,7 +71,7 @@ MaxSpeed true - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Arduino/Arduino.vcxproj b/DeviceAdapters/Arduino/Arduino.vcxproj index 9a9e9944d..4cd4566bc 100644 --- a/DeviceAdapters/Arduino/Arduino.vcxproj +++ b/DeviceAdapters/Arduino/Arduino.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks 4290;%(DisableSpecificWarnings) @@ -72,7 +72,7 @@ MaxSpeed true %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Arduino32bitBoards/Arduino32bitBoards.vcxproj b/DeviceAdapters/Arduino32bitBoards/Arduino32bitBoards.vcxproj index acc1b2bbb..efb53fdf6 100644 --- a/DeviceAdapters/Arduino32bitBoards/Arduino32bitBoards.vcxproj +++ b/DeviceAdapters/Arduino32bitBoards/Arduino32bitBoards.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks 4290;%(DisableSpecificWarnings) @@ -72,7 +72,7 @@ MaxSpeed true %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/ArduinoCounter/ArduinoCounter.vcxproj b/DeviceAdapters/ArduinoCounter/ArduinoCounter.vcxproj index 7b7884e23..7c73b01d5 100644 --- a/DeviceAdapters/ArduinoCounter/ArduinoCounter.vcxproj +++ b/DeviceAdapters/ArduinoCounter/ArduinoCounter.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks 4290;%(DisableSpecificWarnings) @@ -72,7 +72,7 @@ MaxSpeed true %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Atik/AtikCamera.vcxproj b/DeviceAdapters/Atik/AtikCamera.vcxproj index 34900af34..c53fc01ef 100644 --- a/DeviceAdapters/Atik/AtikCamera.vcxproj +++ b/DeviceAdapters/Atik/AtikCamera.vcxproj @@ -57,7 +57,7 @@ Disabled $(MM_3RDPARTYPRIVATE)/Atik/AtikCamerasSDK_2023_03_31/include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) false EnableFastChecks @@ -76,7 +76,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)/Atik/AtikCamerasSDK_2023_03_31/include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/BDPathway/BDPathway.vcxproj b/DeviceAdapters/BDPathway/BDPathway.vcxproj index fcc9f6aaf..48e2b735d 100644 --- a/DeviceAdapters/BDPathway/BDPathway.vcxproj +++ b/DeviceAdapters/BDPathway/BDPathway.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Basler/BaslerPylon.vcxproj b/DeviceAdapters/Basler/BaslerPylon.vcxproj index 608b0e91a..6887a2011 100644 --- a/DeviceAdapters/Basler/BaslerPylon.vcxproj +++ b/DeviceAdapters/Basler/BaslerPylon.vcxproj @@ -57,7 +57,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\Basler\Windows\7.1.0\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -79,7 +79,7 @@ true Speed $(MM_3RDPARTYPRIVATE)\Basler\Windows\7.1.0\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/BaumerOptronic/BaumerOptronic.vcxproj b/DeviceAdapters/BaumerOptronic/BaumerOptronic.vcxproj index 3e874b4bb..1c2f9a3f5 100644 --- a/DeviceAdapters/BaumerOptronic/BaumerOptronic.vcxproj +++ b/DeviceAdapters/BaumerOptronic/BaumerOptronic.vcxproj @@ -54,7 +54,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\Leica\camera\SDK1.7\BSTDFxLib\x64\Inc;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -74,7 +74,7 @@ $(MM_3RDPARTYPRIVATE)\Leica\camera\SDK1.7\BSTDFxLib\x64\Inc;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/BlueboxOptics_niji/BlueboxOptics_niji.vcxproj b/DeviceAdapters/BlueboxOptics_niji/BlueboxOptics_niji.vcxproj index fafbfb52c..cf046bc20 100644 --- a/DeviceAdapters/BlueboxOptics_niji/BlueboxOptics_niji.vcxproj +++ b/DeviceAdapters/BlueboxOptics_niji/BlueboxOptics_niji.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/CARVII/CARVII.vcxproj b/DeviceAdapters/CARVII/CARVII.vcxproj index 99066abc2..22f15d7be 100644 --- a/DeviceAdapters/CARVII/CARVII.vcxproj +++ b/DeviceAdapters/CARVII/CARVII.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -71,7 +71,7 @@ MaxSpeed true - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/CSUW1/CSUW1.vcxproj b/DeviceAdapters/CSUW1/CSUW1.vcxproj index f83a0e3d7..126183015 100644 --- a/DeviceAdapters/CSUW1/CSUW1.vcxproj +++ b/DeviceAdapters/CSUW1/CSUW1.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Cobolt/Cobolt.vcxproj b/DeviceAdapters/Cobolt/Cobolt.vcxproj index 69c331909..ed40fb2bf 100644 --- a/DeviceAdapters/Cobolt/Cobolt.vcxproj +++ b/DeviceAdapters/Cobolt/Cobolt.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -74,7 +74,7 @@ OnlyExplicitInline true - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true true diff --git a/DeviceAdapters/CoherentCube/CoherentCube.vcxproj b/DeviceAdapters/CoherentCube/CoherentCube.vcxproj index 38144ffbe..b12fd7fc7 100644 --- a/DeviceAdapters/CoherentCube/CoherentCube.vcxproj +++ b/DeviceAdapters/CoherentCube/CoherentCube.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Conix/Conix.vcxproj b/DeviceAdapters/Conix/Conix.vcxproj index 18915badf..c12d473c2 100644 --- a/DeviceAdapters/Conix/Conix.vcxproj +++ b/DeviceAdapters/Conix/Conix.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Corvus/Corvus.vcxproj b/DeviceAdapters/Corvus/Corvus.vcxproj index e5c37a0d1..eba0ee6b9 100644 --- a/DeviceAdapters/Corvus/Corvus.vcxproj +++ b/DeviceAdapters/Corvus/Corvus.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/DTOpenLayer/DTOpenLayer.vcxproj b/DeviceAdapters/DTOpenLayer/DTOpenLayer.vcxproj index 602e0011d..6e3e9e09b 100644 --- a/DeviceAdapters/DTOpenLayer/DTOpenLayer.vcxproj +++ b/DeviceAdapters/DTOpenLayer/DTOpenLayer.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\DataTranslation\SDK\Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPRIVATE)\DataTranslation\SDK\Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/DemoCamera/DemoCamera.vcxproj b/DeviceAdapters/DemoCamera/DemoCamera.vcxproj index 9c38b333f..d33d57348 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.vcxproj +++ b/DeviceAdapters/DemoCamera/DemoCamera.vcxproj @@ -56,7 +56,7 @@ Disabled true Speed - NOMINMAX;WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + NOMINMAX;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -77,7 +77,7 @@ MaxSpeed true Speed - NOMINMAX;WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + NOMINMAX;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/DirectElectron/vsprojects/DECamera.vcxproj b/DeviceAdapters/DirectElectron/vsprojects/DECamera.vcxproj index c24b720b7..ffd6e9b6b 100644 --- a/DeviceAdapters/DirectElectron/vsprojects/DECamera.vcxproj +++ b/DeviceAdapters/DirectElectron/vsprojects/DECamera.vcxproj @@ -55,7 +55,7 @@ Disabled ..\src\DEClientLib;..\src\DEMessaging;..\..\..;$(MM_PROTOBUF_INCLUDEDIR);%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ ..\src\DEClientLib;..\src\DEMessaging;..\..\..;$(MM_PROTOBUF_INCLUDEDIR);%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Dragonfly/Dragonfly.vcxproj b/DeviceAdapters/Dragonfly/Dragonfly.vcxproj index 50c7f95f8..94dea928e 100644 --- a/DeviceAdapters/Dragonfly/Dragonfly.vcxproj +++ b/DeviceAdapters/Dragonfly/Dragonfly.vcxproj @@ -63,7 +63,7 @@ Disabled $(MM_MMDEVICE_INCLUDEDIR);$(MM_3RDPARTYPRIVATE)/Andor/ASD/SDK0.5/Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true NotUsing @@ -84,7 +84,7 @@ $(MM_MMDEVICE_INCLUDEDIR);$(MM_3RDPARTYPRIVATE)/Andor/ASD/SDK0.5/Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/FLICamera/FLICamera.vcxproj b/DeviceAdapters/FLICamera/FLICamera.vcxproj index 804e540bd..ab3d95c9f 100644 --- a/DeviceAdapters/FLICamera/FLICamera.vcxproj +++ b/DeviceAdapters/FLICamera/FLICamera.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\FLI\inc_2013_03_21;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPRIVATE)\FLI\inc_2013_03_21;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Fli/FirstLightImagingCameras.vcxproj b/DeviceAdapters/Fli/FirstLightImagingCameras.vcxproj index 562272695..97d40631b 100644 --- a/DeviceAdapters/Fli/FirstLightImagingCameras.vcxproj +++ b/DeviceAdapters/Fli/FirstLightImagingCameras.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\FirstLightSdk\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPRIVATE)\FirstLightSdk\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/FreeSerialPort/FreeSerialPort.vcxproj b/DeviceAdapters/FreeSerialPort/FreeSerialPort.vcxproj index bc07ee163..895843efb 100644 --- a/DeviceAdapters/FreeSerialPort/FreeSerialPort.vcxproj +++ b/DeviceAdapters/FreeSerialPort/FreeSerialPort.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -72,7 +72,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/GenericSLM/GenericSLM.vcxproj b/DeviceAdapters/GenericSLM/GenericSLM.vcxproj index da62cb4f7..25c64c9d9 100644 --- a/DeviceAdapters/GenericSLM/GenericSLM.vcxproj +++ b/DeviceAdapters/GenericSLM/GenericSLM.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/GigECamera/GigECamera.vcxproj b/DeviceAdapters/GigECamera/GigECamera.vcxproj index ea797b349..292d8ccc0 100644 --- a/DeviceAdapters/GigECamera/GigECamera.vcxproj +++ b/DeviceAdapters/GigECamera/GigECamera.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\JAI\SDK-x64-1.4.1\library\CPP\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;TESTRESOURCELOCKING;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;TESTRESOURCELOCKING;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPRIVATE)\JAI\SDK-x64-1.4.1\library\CPP\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;TESTRESOURCELOCKING;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;TESTRESOURCELOCKING;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/HIDManager/HIDManager.vcxproj b/DeviceAdapters/HIDManager/HIDManager.vcxproj index c309573eb..c7619795b 100644 --- a/DeviceAdapters/HIDManager/HIDManager.vcxproj +++ b/DeviceAdapters/HIDManager/HIDManager.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPUBLIC)\hidapi\hidapi-0.11.2;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -77,7 +77,7 @@ $(MM_3RDPARTYPUBLIC)\hidapi\hidapi-0.11.2;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/IDS_uEye/IDS_uEye.vcxproj b/DeviceAdapters/IDS_uEye/IDS_uEye.vcxproj index 0206b0c94..bb8eaf744 100644 --- a/DeviceAdapters/IDS_uEye/IDS_uEye.vcxproj +++ b/DeviceAdapters/IDS_uEye/IDS_uEye.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)/IDS/uEye-4.95.1/develop/include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)/IDS/uEye-4.95.1/develop/include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/IlluminateLEDArray/LEDArray.vcxproj b/DeviceAdapters/IlluminateLEDArray/LEDArray.vcxproj index 14bf5a0b7..4c49f2a70 100644 --- a/DeviceAdapters/IlluminateLEDArray/LEDArray.vcxproj +++ b/DeviceAdapters/IlluminateLEDArray/LEDArray.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks 4290;%(DisableSpecificWarnings) @@ -74,7 +74,7 @@ MaxSpeed true %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/ImageProcessorChain/ImageProcessorChain.vcxproj b/DeviceAdapters/ImageProcessorChain/ImageProcessorChain.vcxproj index 66329dd3e..e97daa27c 100644 --- a/DeviceAdapters/ImageProcessorChain/ImageProcessorChain.vcxproj +++ b/DeviceAdapters/ImageProcessorChain/ImageProcessorChain.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ MaxSpeed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/IntegratedLaserEngine/IntegratedLaserEngine.vcxproj b/DeviceAdapters/IntegratedLaserEngine/IntegratedLaserEngine.vcxproj index 16e7e7cf1..a021d2ebb 100644 --- a/DeviceAdapters/IntegratedLaserEngine/IntegratedLaserEngine.vcxproj +++ b/DeviceAdapters/IntegratedLaserEngine/IntegratedLaserEngine.vcxproj @@ -66,7 +66,7 @@ Disabled $(MM_3RDPARTYPUBLIC)/boost-versions/boost_1_77_0;$(MM_3RDPARTYPRIVATE)/Andor/ALC/SDK2.5/Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -85,7 +85,7 @@ $(MM_3RDPARTYPUBLIC)/boost-versions/boost_1_77_0;$(MM_3RDPARTYPRIVATE)/Andor/ALC/SDK2.5/Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/JAI/JAI.vcxproj b/DeviceAdapters/JAI/JAI.vcxproj index ac4682892..999abf8b5 100644 --- a/DeviceAdapters/JAI/JAI.vcxproj +++ b/DeviceAdapters/JAI/JAI.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)/JAI/eBUS-SDK/Includes;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)/JAI/eBUS-SDK/Includes;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/K8055/K8055.vcxproj b/DeviceAdapters/K8055/K8055.vcxproj index 630e083d4..fe862a340 100644 --- a/DeviceAdapters/K8055/K8055.vcxproj +++ b/DeviceAdapters/K8055/K8055.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/K8061/K8061.vcxproj b/DeviceAdapters/K8061/K8061.vcxproj index 864add596..34ade99ff 100644 --- a/DeviceAdapters/K8061/K8061.vcxproj +++ b/DeviceAdapters/K8061/K8061.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/LaserQuantumLaser/LaserQuantumLaser.vcxproj b/DeviceAdapters/LaserQuantumLaser/LaserQuantumLaser.vcxproj index 99df7d047..043ae77b1 100644 --- a/DeviceAdapters/LaserQuantumLaser/LaserQuantumLaser.vcxproj +++ b/DeviceAdapters/LaserQuantumLaser/LaserQuantumLaser.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/LeicaDMI/LeicaDMI.vcxproj b/DeviceAdapters/LeicaDMI/LeicaDMI.vcxproj index b419a5310..887b9b6f2 100644 --- a/DeviceAdapters/LeicaDMI/LeicaDMI.vcxproj +++ b/DeviceAdapters/LeicaDMI/LeicaDMI.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -74,7 +74,7 @@ MaxSpeed true %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/LeicaDMR/LeicaDMR.vcxproj b/DeviceAdapters/LeicaDMR/LeicaDMR.vcxproj index f92ae5d40..af4c61796 100644 --- a/DeviceAdapters/LeicaDMR/LeicaDMR.vcxproj +++ b/DeviceAdapters/LeicaDMR/LeicaDMR.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/LeicaDMSTC/LeicaDMSTC.vcxproj b/DeviceAdapters/LeicaDMSTC/LeicaDMSTC.vcxproj index d21bb052d..e3b42ae2d 100644 --- a/DeviceAdapters/LeicaDMSTC/LeicaDMSTC.vcxproj +++ b/DeviceAdapters/LeicaDMSTC/LeicaDMSTC.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/LightSheetManager/LightSheetManager.vcxproj b/DeviceAdapters/LightSheetManager/LightSheetManager.vcxproj index 8caade87a..0ad3ea46b 100644 --- a/DeviceAdapters/LightSheetManager/LightSheetManager.vcxproj +++ b/DeviceAdapters/LightSheetManager/LightSheetManager.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -77,7 +77,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Ludl/Ludl.vcxproj b/DeviceAdapters/Ludl/Ludl.vcxproj index 71879bf46..0d5690b51 100644 --- a/DeviceAdapters/Ludl/Ludl.vcxproj +++ b/DeviceAdapters/Ludl/Ludl.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/LudlLow/LudlLow.vcxproj b/DeviceAdapters/LudlLow/LudlLow.vcxproj index d1e035383..28c8b1955 100644 --- a/DeviceAdapters/LudlLow/LudlLow.vcxproj +++ b/DeviceAdapters/LudlLow/LudlLow.vcxproj @@ -53,7 +53,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -70,7 +70,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Lumencor/Lumencor.vcxproj b/DeviceAdapters/Lumencor/Lumencor.vcxproj index 0de9a460e..9c65d2d99 100644 --- a/DeviceAdapters/Lumencor/Lumencor.vcxproj +++ b/DeviceAdapters/Lumencor/Lumencor.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/LumencorCIA/LumencorCIA.vcxproj b/DeviceAdapters/LumencorCIA/LumencorCIA.vcxproj index 9ea58a292..d01624337 100644 --- a/DeviceAdapters/LumencorCIA/LumencorCIA.vcxproj +++ b/DeviceAdapters/LumencorCIA/LumencorCIA.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -72,7 +72,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/LumencorSpectra/LumencorSpectra.vcxproj b/DeviceAdapters/LumencorSpectra/LumencorSpectra.vcxproj index d2c671090..f5a0adb18 100644 --- a/DeviceAdapters/LumencorSpectra/LumencorSpectra.vcxproj +++ b/DeviceAdapters/LumencorSpectra/LumencorSpectra.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/MCCDAQ/MCCDAQ.vcxproj b/DeviceAdapters/MCCDAQ/MCCDAQ.vcxproj index c0f0bc223..57d213f8a 100644 --- a/DeviceAdapters/MCCDAQ/MCCDAQ.vcxproj +++ b/DeviceAdapters/MCCDAQ/MCCDAQ.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\MeasurementComputing\UniversalLibrary\6.27\DAQ\C;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPRIVATE)\MeasurementComputing\UniversalLibrary\6.27\DAQ\C;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj index 0b5ef89c1..c2b0d4359 100644 --- a/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj +++ b/DeviceAdapters/MCL_MicroDrive/MCL_MicroDrive.vcxproj @@ -53,7 +53,7 @@ Disabled %(AdditionalIncludeDirectories) - MODULE_EXPORTS;WIN32;%(PreprocessorDefinitions) + WIN32;%(PreprocessorDefinitions) EnableFastChecks MultiThreaded @@ -70,7 +70,7 @@ %(AdditionalIncludeDirectories) - MODULE_EXPORTS;WIN32;%(PreprocessorDefinitions) + WIN32;%(PreprocessorDefinitions) MultiThreaded diff --git a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.vcxproj b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.vcxproj index a9b8cde62..3bb8f546f 100644 --- a/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.vcxproj +++ b/DeviceAdapters/MCL_NanoDrive/MCL_NanoDrive.vcxproj @@ -53,7 +53,7 @@ Disabled %(AdditionalIncludeDirectories) - MODULE_EXPORTS;WIN32;%(PreprocessorDefinitions) + WIN32;%(PreprocessorDefinitions) EnableFastChecks MultiThreaded @@ -70,7 +70,7 @@ %(AdditionalIncludeDirectories) - MODULE_EXPORTS;WIN32;%(PreprocessorDefinitions) + WIN32;%(PreprocessorDefinitions) MultiThreaded diff --git a/DeviceAdapters/MP285/MP285.vcxproj b/DeviceAdapters/MP285/MP285.vcxproj index d33ddbfe0..3bfd70041 100644 --- a/DeviceAdapters/MP285/MP285.vcxproj +++ b/DeviceAdapters/MP285/MP285.vcxproj @@ -56,7 +56,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks @@ -76,7 +76,7 @@ Disabled - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/MT20/MT20.vcxproj b/DeviceAdapters/MT20/MT20.vcxproj index de3a25e54..191021525 100644 --- a/DeviceAdapters/MT20/MT20.vcxproj +++ b/DeviceAdapters/MT20/MT20.vcxproj @@ -53,7 +53,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) EnableFastChecks @@ -69,7 +69,7 @@ MaxSpeed true %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/MaestroServo/MaestroServo.vcxproj b/DeviceAdapters/MaestroServo/MaestroServo.vcxproj index a58ea9167..902cb3cfe 100644 --- a/DeviceAdapters/MaestroServo/MaestroServo.vcxproj +++ b/DeviceAdapters/MaestroServo/MaestroServo.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -74,7 +74,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Marzhauser-LStep/MarzhauserLStep.vcxproj b/DeviceAdapters/Marzhauser-LStep/MarzhauserLStep.vcxproj index 69eab0167..fa2e03ee6 100644 --- a/DeviceAdapters/Marzhauser-LStep/MarzhauserLStep.vcxproj +++ b/DeviceAdapters/Marzhauser-LStep/MarzhauserLStep.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -74,7 +74,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Marzhauser/Marzhauser.vcxproj b/DeviceAdapters/Marzhauser/Marzhauser.vcxproj index e71da3c6a..fcb356a89 100644 --- a/DeviceAdapters/Marzhauser/Marzhauser.vcxproj +++ b/DeviceAdapters/Marzhauser/Marzhauser.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/MicroFPGA/MicroFPGA.vcxproj b/DeviceAdapters/MicroFPGA/MicroFPGA.vcxproj index 4f27af4a3..169a838af 100644 --- a/DeviceAdapters/MicroFPGA/MicroFPGA.vcxproj +++ b/DeviceAdapters/MicroFPGA/MicroFPGA.vcxproj @@ -78,7 +78,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true EnableFastChecks 4290;%(DisableSpecificWarnings) @@ -95,7 +95,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true EnableFastChecks 4290;%(DisableSpecificWarnings) @@ -110,7 +110,7 @@ MaxSpeed true %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) @@ -129,7 +129,7 @@ MaxSpeed true %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/MicroPoint/MicroPoint.vcxproj b/DeviceAdapters/MicroPoint/MicroPoint.vcxproj index 1437d7fef..be9ab365e 100644 --- a/DeviceAdapters/MicroPoint/MicroPoint.vcxproj +++ b/DeviceAdapters/MicroPoint/MicroPoint.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -74,7 +74,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Mightex/Mightex.vcxproj b/DeviceAdapters/Mightex/Mightex.vcxproj index 57054c099..bf3db316d 100644 --- a/DeviceAdapters/Mightex/Mightex.vcxproj +++ b/DeviceAdapters/Mightex/Mightex.vcxproj @@ -55,7 +55,7 @@ Disabled $(VCInstallDir)include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) EnableFastChecks true @@ -79,7 +79,7 @@ $(VCInstallDir)include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Mightex_BLS/Mightex_BLS.vcxproj b/DeviceAdapters/Mightex_BLS/Mightex_BLS.vcxproj index f53234766..2bdba5ef4 100644 --- a/DeviceAdapters/Mightex_BLS/Mightex_BLS.vcxproj +++ b/DeviceAdapters/Mightex_BLS/Mightex_BLS.vcxproj @@ -55,7 +55,7 @@ Disabled $(VCInstallDir)include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) EnableFastChecks true @@ -79,7 +79,7 @@ $(VCInstallDir)include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Modbus/Modbus.vcxproj b/DeviceAdapters/Modbus/Modbus.vcxproj index baae1daf0..8d71ba351 100644 --- a/DeviceAdapters/Modbus/Modbus.vcxproj +++ b/DeviceAdapters/Modbus/Modbus.vcxproj @@ -56,7 +56,7 @@ Disabled true Speed - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -80,7 +80,7 @@ MaxSpeed true Speed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Motic/MoticCamera.vcxproj b/DeviceAdapters/Motic/MoticCamera.vcxproj index 75ab9c378..ec6d8c87a 100644 --- a/DeviceAdapters/Motic/MoticCamera.vcxproj +++ b/DeviceAdapters/Motic/MoticCamera.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPUBLIC)\Motic;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ MaxSpeed true $(MM_3RDPARTYPUBLIC)\Motic;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/MoticMicroscope/MoticMicroscope.vcxproj b/DeviceAdapters/MoticMicroscope/MoticMicroscope.vcxproj index f40c322dd..5e1b34bb9 100644 --- a/DeviceAdapters/MoticMicroscope/MoticMicroscope.vcxproj +++ b/DeviceAdapters/MoticMicroscope/MoticMicroscope.vcxproj @@ -57,7 +57,7 @@ true Speed $(MM_3RDPARTYPUBLIC)/Motic;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true 4290;%(DisableSpecificWarnings) @@ -79,7 +79,7 @@ true Speed $(MM_3RDPARTYPUBLIC)/Motic;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/NI100X/NI100X.vcxproj b/DeviceAdapters/NI100X/NI100X.vcxproj index 6fa1a0aad..1029f9b95 100644 --- a/DeviceAdapters/NI100X/NI100X.vcxproj +++ b/DeviceAdapters/NI100X/NI100X.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\NationalInstruments\DAQmx_9.2\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)\NationalInstruments\DAQmx_9.2\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/NIDAQ/NIDAQ.vcxproj b/DeviceAdapters/NIDAQ/NIDAQ.vcxproj index 1cf027fe2..587636144 100644 --- a/DeviceAdapters/NIDAQ/NIDAQ.vcxproj +++ b/DeviceAdapters/NIDAQ/NIDAQ.vcxproj @@ -57,7 +57,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\NationalInstruments\DAQmx_9.2\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -76,7 +76,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)\NationalInstruments\DAQmx_9.2\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/NIMultiAnalog/NIMultiAnalog.vcxproj b/DeviceAdapters/NIMultiAnalog/NIMultiAnalog.vcxproj index a20f59a29..2e6d5f48c 100644 --- a/DeviceAdapters/NIMultiAnalog/NIMultiAnalog.vcxproj +++ b/DeviceAdapters/NIMultiAnalog/NIMultiAnalog.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\NationalInstruments\DAQmx_9.2\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)\NationalInstruments\DAQmx_9.2\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Neos/Neos.vcxproj b/DeviceAdapters/Neos/Neos.vcxproj index 1c7575f0a..dd81d71f2 100644 --- a/DeviceAdapters/Neos/Neos.vcxproj +++ b/DeviceAdapters/Neos/Neos.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -74,7 +74,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/NewportCONEX/NewportCONEX.vcxproj b/DeviceAdapters/NewportCONEX/NewportCONEX.vcxproj index 6ca442b64..ea48364a7 100644 --- a/DeviceAdapters/NewportCONEX/NewportCONEX.vcxproj +++ b/DeviceAdapters/NewportCONEX/NewportCONEX.vcxproj @@ -53,7 +53,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Nikon/Nikon.vcxproj b/DeviceAdapters/Nikon/Nikon.vcxproj index 24de2e15b..c2707f621 100644 --- a/DeviceAdapters/Nikon/Nikon.vcxproj +++ b/DeviceAdapters/Nikon/Nikon.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/NikonTE2000/NikonTE2000.vcxproj b/DeviceAdapters/NikonTE2000/NikonTE2000.vcxproj index 0daa8a811..2a5ab7212 100644 --- a/DeviceAdapters/NikonTE2000/NikonTE2000.vcxproj +++ b/DeviceAdapters/NikonTE2000/NikonTE2000.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/OVP_ECS2/OVP_ECS2.vcxproj b/DeviceAdapters/OVP_ECS2/OVP_ECS2.vcxproj index 03fafc19a..5817996fa 100644 --- a/DeviceAdapters/OVP_ECS2/OVP_ECS2.vcxproj +++ b/DeviceAdapters/OVP_ECS2/OVP_ECS2.vcxproj @@ -54,7 +54,7 @@ %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true 4290;%(DisableSpecificWarnings) @@ -72,7 +72,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/ObjectiveImaging/ObjectiveImaging.vcxproj b/DeviceAdapters/ObjectiveImaging/ObjectiveImaging.vcxproj index 6ac0839cd..d871fbf04 100644 --- a/DeviceAdapters/ObjectiveImaging/ObjectiveImaging.vcxproj +++ b/DeviceAdapters/ObjectiveImaging/ObjectiveImaging.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\ObjectiveImaging\OASIS_SDK_V408_20\Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -74,7 +74,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)\ObjectiveImaging\OASIS_SDK_V408_20\Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Omicron/Omicron.vcxproj b/DeviceAdapters/Omicron/Omicron.vcxproj index 01ac91e40..cf0b93787 100644 --- a/DeviceAdapters/Omicron/Omicron.vcxproj +++ b/DeviceAdapters/Omicron/Omicron.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/OpenCVgrabber/OpenCVgrabber.vcxproj b/DeviceAdapters/OpenCVgrabber/OpenCVgrabber.vcxproj index ea743616e..fe45fe03f 100644 --- a/DeviceAdapters/OpenCVgrabber/OpenCVgrabber.vcxproj +++ b/DeviceAdapters/OpenCVgrabber/OpenCVgrabber.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPUBLIC)\OpenCV2.4.13.6\VS2019\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -76,7 +76,7 @@ $(MM_3RDPARTYPUBLIC)\OpenCV2.4.13.6\VS2019\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Oxxius/Oxxius.vcxproj b/DeviceAdapters/Oxxius/Oxxius.vcxproj index 7c01d3823..42bb6e1be 100644 --- a/DeviceAdapters/Oxxius/Oxxius.vcxproj +++ b/DeviceAdapters/Oxxius/Oxxius.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj b/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj index 1e3a7d3ce..16a36d06e 100644 --- a/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj +++ b/DeviceAdapters/OxxiusCombiner/OxxiusCombiner.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PCO_Generic/PCO_Camera.vcxproj b/DeviceAdapters/PCO_Generic/PCO_Camera.vcxproj index 23e8024ea..88a88e36b 100644 --- a/DeviceAdapters/PCO_Generic/PCO_Camera.vcxproj +++ b/DeviceAdapters/PCO_Generic/PCO_Camera.vcxproj @@ -56,7 +56,7 @@ Disabled lib\pco_generic;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;_LIGHT;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_LIGHT;%(PreprocessorDefinitions) EnableFastChecks true @@ -77,7 +77,7 @@ lib\pco_generic;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;_LIGHT;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;_LIGHT;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PI/PI.vcxproj b/DeviceAdapters/PI/PI.vcxproj index 1968992ea..8b8ee970b 100644 --- a/DeviceAdapters/PI/PI.vcxproj +++ b/DeviceAdapters/PI/PI.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PICAM/PICAM.vcxproj b/DeviceAdapters/PICAM/PICAM.vcxproj index f0d0a2abe..b6e8592a0 100644 --- a/DeviceAdapters/PICAM/PICAM.vcxproj +++ b/DeviceAdapters/PICAM/PICAM.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\PrincetonInstruments\PICam\Picam-2.7.2.1403\Includes;%(AdditionalIncludeDirectories) - WIN64;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN64;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPRIVATE)\PrincetonInstruments\PICam\Picam-2.7.2.1403\Includes;%(AdditionalIncludeDirectories) - WIN64;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN64;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PI_GCS/PI_GCS.vcxproj b/DeviceAdapters/PI_GCS/PI_GCS.vcxproj index a94019235..105bb7da1 100644 --- a/DeviceAdapters/PI_GCS/PI_GCS.vcxproj +++ b/DeviceAdapters/PI_GCS/PI_GCS.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PI_GCS_2/PI_GCS_2.vcxproj b/DeviceAdapters/PI_GCS_2/PI_GCS_2.vcxproj index 79d40ce70..da62c13af 100644 --- a/DeviceAdapters/PI_GCS_2/PI_GCS_2.vcxproj +++ b/DeviceAdapters/PI_GCS_2/PI_GCS_2.vcxproj @@ -54,7 +54,7 @@ Disabled - x64;WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + x64;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -72,7 +72,7 @@ X64 - x64;WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + x64;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PVCAM/PVCAM.vcxproj b/DeviceAdapters/PVCAM/PVCAM.vcxproj index a69d2fe98..7641d2817 100644 --- a/DeviceAdapters/PVCAM/PVCAM.vcxproj +++ b/DeviceAdapters/PVCAM/PVCAM.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ParallelPort/ParallelPort.vcxproj b/DeviceAdapters/ParallelPort/ParallelPort.vcxproj index bcb84cceb..fad743c48 100644 --- a/DeviceAdapters/ParallelPort/ParallelPort.vcxproj +++ b/DeviceAdapters/ParallelPort/ParallelPort.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Pecon/Pecon.vcxproj b/DeviceAdapters/Pecon/Pecon.vcxproj index d570b8999..5947142f6 100644 --- a/DeviceAdapters/Pecon/Pecon.vcxproj +++ b/DeviceAdapters/Pecon/Pecon.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PicardStage/PicardStage.vcxproj b/DeviceAdapters/PicardStage/PicardStage.vcxproj index cc4274d6c..61ddf8d9f 100644 --- a/DeviceAdapters/PicardStage/PicardStage.vcxproj +++ b/DeviceAdapters/PicardStage/PicardStage.vcxproj @@ -50,7 +50,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true @@ -70,7 +70,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Piezosystem_30DV50/Piezosystem_30DV50.vcxproj b/DeviceAdapters/Piezosystem_30DV50/Piezosystem_30DV50.vcxproj index c97e173d3..823098b0a 100644 --- a/DeviceAdapters/Piezosystem_30DV50/Piezosystem_30DV50.vcxproj +++ b/DeviceAdapters/Piezosystem_30DV50/Piezosystem_30DV50.vcxproj @@ -56,7 +56,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ MaxSpeed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Piezosystem_NV120_1/Piezosystem_NV120_1.vcxproj b/DeviceAdapters/Piezosystem_NV120_1/Piezosystem_NV120_1.vcxproj index bdf57800b..f32c4a29d 100644 --- a/DeviceAdapters/Piezosystem_NV120_1/Piezosystem_NV120_1.vcxproj +++ b/DeviceAdapters/Piezosystem_NV120_1/Piezosystem_NV120_1.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ MaxSpeed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Piezosystem_NV40_1/Piezosystem_NV40_1.vcxproj b/DeviceAdapters/Piezosystem_NV40_1/Piezosystem_NV40_1.vcxproj index 7fffd04c4..c61bea520 100644 --- a/DeviceAdapters/Piezosystem_NV40_1/Piezosystem_NV40_1.vcxproj +++ b/DeviceAdapters/Piezosystem_NV40_1/Piezosystem_NV40_1.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ MaxSpeed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Piezosystem_NV40_3/Piezosystem_NV40_3.vcxproj b/DeviceAdapters/Piezosystem_NV40_3/Piezosystem_NV40_3.vcxproj index a11f6985f..b6fce56d0 100644 --- a/DeviceAdapters/Piezosystem_NV40_3/Piezosystem_NV40_3.vcxproj +++ b/DeviceAdapters/Piezosystem_NV40_3/Piezosystem_NV40_3.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ MaxSpeed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Piezosystem_dDrive/Piezosystem_dDrive.vcxproj b/DeviceAdapters/Piezosystem_dDrive/Piezosystem_dDrive.vcxproj index 5ecd95bdf..2b9c5d776 100644 --- a/DeviceAdapters/Piezosystem_dDrive/Piezosystem_dDrive.vcxproj +++ b/DeviceAdapters/Piezosystem_dDrive/Piezosystem_dDrive.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ MaxSpeed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Piper/Piper.vcxproj b/DeviceAdapters/Piper/Piper.vcxproj index 5b262f360..000c16374 100644 --- a/DeviceAdapters/Piper/Piper.vcxproj +++ b/DeviceAdapters/Piper/Piper.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)/PiperApi;$(MM_BOOST_INCLUDEDIR);%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_AFXEXT;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_AFXEXT;%(PreprocessorDefinitions) EnableFastChecks true @@ -79,7 +79,7 @@ MaxSpeed $(MM_3RDPARTYPRIVATE)/PiperApi;$(MM_BOOST_INCLUDEDIR);%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_AFXEXT;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_AFXEXT;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Pixelink/Pixelink.vcxproj b/DeviceAdapters/Pixelink/Pixelink.vcxproj index a2e7258ea..163c7d3e1 100644 --- a/DeviceAdapters/Pixelink/Pixelink.vcxproj +++ b/DeviceAdapters/Pixelink/Pixelink.vcxproj @@ -56,7 +56,7 @@ Disabled true Speed - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true EnableFastChecks true @@ -81,7 +81,7 @@ MaxSpeed true Speed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PointGrey/PointGrey.vcxproj b/DeviceAdapters/PointGrey/PointGrey.vcxproj index 4f16972a3..fecc2f1f0 100644 --- a/DeviceAdapters/PointGrey/PointGrey.vcxproj +++ b/DeviceAdapters/PointGrey/PointGrey.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\Point Grey Research\FlyCapture2\2.13.3.61\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ Disabled false $(MM_3RDPARTYPRIVATE)\Point Grey Research\FlyCapture2\2.13.3.61\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PrecisExcite/PrecisExcite.vcxproj b/DeviceAdapters/PrecisExcite/PrecisExcite.vcxproj index 096852cad..643e6dc3f 100644 --- a/DeviceAdapters/PrecisExcite/PrecisExcite.vcxproj +++ b/DeviceAdapters/PrecisExcite/PrecisExcite.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Prior/Prior.vcxproj b/DeviceAdapters/Prior/Prior.vcxproj index bc59d6dab..e351b357d 100644 --- a/DeviceAdapters/Prior/Prior.vcxproj +++ b/DeviceAdapters/Prior/Prior.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/PriorLegacy/PriorLegacy.vcxproj b/DeviceAdapters/PriorLegacy/PriorLegacy.vcxproj index a04cc0e3f..eaade2c19 100644 --- a/DeviceAdapters/PriorLegacy/PriorLegacy.vcxproj +++ b/DeviceAdapters/PriorLegacy/PriorLegacy.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -74,7 +74,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/QCam/QCam.vcxproj b/DeviceAdapters/QCam/QCam.vcxproj index b89625367..3953ddae1 100644 --- a/DeviceAdapters/QCam/QCam.vcxproj +++ b/DeviceAdapters/QCam/QCam.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\QImaging\QCam SDK 2.0.13.1\Headers;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPRIVATE)\QImaging\QCam SDK 2.0.13.1\Headers;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Rapp/Rapp.vcxproj b/DeviceAdapters/Rapp/Rapp.vcxproj index 6b85adf2e..553eacc49 100644 --- a/DeviceAdapters/Rapp/Rapp.vcxproj +++ b/DeviceAdapters/Rapp/Rapp.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\Rapp\2022-02-17 UGA-40 SDK\inc;$(MM_3RDPARTYPRIVATE)\Rapp\2022-02-17 UGA-40 SDK\UGA40Example;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPRIVATE)\Rapp\2022-02-17 UGA-40 SDK\inc;$(MM_3RDPARTYPRIVATE)\Rapp\2022-02-17 UGA-40 SDK\UGA40Example;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/RaptorEPIX/HoribaEPIX.vcxproj b/DeviceAdapters/RaptorEPIX/HoribaEPIX.vcxproj index cf7233406..4ad975482 100644 --- a/DeviceAdapters/RaptorEPIX/HoribaEPIX.vcxproj +++ b/DeviceAdapters/RaptorEPIX/HoribaEPIX.vcxproj @@ -56,7 +56,7 @@ Disabled true Speed - HORIBA_COMPILE;WIN32;WIN64;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + HORIBA_COMPILE;WIN32;WIN64;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true 4290;%(DisableSpecificWarnings) @@ -78,7 +78,7 @@ MaxSpeed true Speed - HORIBA_COMPILE;WIN32;WIN64;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + HORIBA_COMPILE;WIN32;WIN64;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) $(MM_3RDPARTYPRIVATE)\EPIX\XCLIB-3.8.31188.27115\inc;%(AdditionalIncludeDirectories) diff --git a/DeviceAdapters/RaptorEPIX/RaptorEPIX.vcxproj b/DeviceAdapters/RaptorEPIX/RaptorEPIX.vcxproj index 2d29e9a24..be45906b0 100644 --- a/DeviceAdapters/RaptorEPIX/RaptorEPIX.vcxproj +++ b/DeviceAdapters/RaptorEPIX/RaptorEPIX.vcxproj @@ -56,7 +56,7 @@ Disabled true Speed - NOT_HORIBA_COMPILE;WIN32;WIN64;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + NOT_HORIBA_COMPILE;WIN32;WIN64;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true 4290;%(DisableSpecificWarnings) @@ -78,7 +78,7 @@ MaxSpeed true Speed - NOT_HORIBA_COMPILE;WIN32;WIN64;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + NOT_HORIBA_COMPILE;WIN32;WIN64;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) $(MM_3RDPARTYPRIVATE)\EPIX\XCLIB-3.8.31188.27115\inc;%(AdditionalIncludeDirectories) diff --git a/DeviceAdapters/Sapphire/Sapphire.vcxproj b/DeviceAdapters/Sapphire/Sapphire.vcxproj index 3aea33216..74abbaf9f 100644 --- a/DeviceAdapters/Sapphire/Sapphire.vcxproj +++ b/DeviceAdapters/Sapphire/Sapphire.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Scientifica/Scientifica.vcxproj b/DeviceAdapters/Scientifica/Scientifica.vcxproj index 7bff93c4d..92a90ecdd 100644 --- a/DeviceAdapters/Scientifica/Scientifica.vcxproj +++ b/DeviceAdapters/Scientifica/Scientifica.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ScopeLED/ScopeLED.vcxproj b/DeviceAdapters/ScopeLED/ScopeLED.vcxproj index 09e6b8013..751a35590 100644 --- a/DeviceAdapters/ScopeLED/ScopeLED.vcxproj +++ b/DeviceAdapters/ScopeLED/ScopeLED.vcxproj @@ -52,7 +52,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;SCOPELED_EXPORTS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;SCOPELED_EXPORTS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -68,7 +68,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;SCOPELED_EXPORTS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;SCOPELED_EXPORTS;_USRDLL;%(PreprocessorDefinitions) diff --git a/DeviceAdapters/SerialManager/SerialManager.vcxproj b/DeviceAdapters/SerialManager/SerialManager.vcxproj index bf595a826..8a40c4b94 100644 --- a/DeviceAdapters/SerialManager/SerialManager.vcxproj +++ b/DeviceAdapters/SerialManager/SerialManager.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ MaxSpeed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/SigmaKoki/SigmaKoki.vcxproj b/DeviceAdapters/SigmaKoki/SigmaKoki.vcxproj index 17c64ee25..7e042b67b 100644 --- a/DeviceAdapters/SigmaKoki/SigmaKoki.vcxproj +++ b/DeviceAdapters/SigmaKoki/SigmaKoki.vcxproj @@ -56,7 +56,7 @@ - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4127;4290;%(DisableSpecificWarnings) $(MM_3RDPARTYPRIVATE)\Sentech\StCamUSBPack_EN_220919\2_SDK\StandardSDK(v3.17)\StandardSDK(v3.17)\include;%(AdditionalIncludeDirectories) @@ -71,7 +71,7 @@ true true true - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4127;4290;%(DisableSpecificWarnings) $(MM_3RDPARTYPRIVATE)\Sentech\StCamUSBPack_EN_220919\2_SDK\StandardSDK(v3.17)\StandardSDK(v3.17)\include;%(AdditionalIncludeDirectories) diff --git a/DeviceAdapters/Skyra/Skyra.vcxproj b/DeviceAdapters/Skyra/Skyra.vcxproj index 08cd12e91..231b6a4a3 100644 --- a/DeviceAdapters/Skyra/Skyra.vcxproj +++ b/DeviceAdapters/Skyra/Skyra.vcxproj @@ -56,7 +56,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ OnlyExplicitInline true - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true true diff --git a/DeviceAdapters/SmarActHCU-3D/SmarActHCU-3D.vcxproj b/DeviceAdapters/SmarActHCU-3D/SmarActHCU-3D.vcxproj index 81808c206..6082aa82c 100644 --- a/DeviceAdapters/SmarActHCU-3D/SmarActHCU-3D.vcxproj +++ b/DeviceAdapters/SmarActHCU-3D/SmarActHCU-3D.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/SouthPort/SouthPort.vcxproj b/DeviceAdapters/SouthPort/SouthPort.vcxproj index e6a572b76..4a165d9b4 100644 --- a/DeviceAdapters/SouthPort/SouthPort.vcxproj +++ b/DeviceAdapters/SouthPort/SouthPort.vcxproj @@ -53,7 +53,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -73,7 +73,7 @@ true true true - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) %(AdditionalLibraryDirectories) diff --git a/DeviceAdapters/SpectralLMM5/SpectralLMM5.vcxproj b/DeviceAdapters/SpectralLMM5/SpectralLMM5.vcxproj index 58ccd4d89..b74dec662 100644 --- a/DeviceAdapters/SpectralLMM5/SpectralLMM5.vcxproj +++ b/DeviceAdapters/SpectralLMM5/SpectralLMM5.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Standa/Standa.vcxproj b/DeviceAdapters/Standa/Standa.vcxproj index d4ab7fd95..bdf0e090d 100644 --- a/DeviceAdapters/Standa/Standa.vcxproj +++ b/DeviceAdapters/Standa/Standa.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)/Standa/MicroSMC/MicroSMCx64;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) USMCDLL.lib;%(AdditionalDependencies) @@ -65,7 +65,7 @@ $(MM_3RDPARTYPRIVATE)/Standa/MicroSMC/MicroSMCx64;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) USMCDLL.lib;%(AdditionalDependencies) diff --git a/DeviceAdapters/SutterLambda/SutterLambda.vcxproj b/DeviceAdapters/SutterLambda/SutterLambda.vcxproj index 3f8dcbce1..a9331534a 100644 --- a/DeviceAdapters/SutterLambda/SutterLambda.vcxproj +++ b/DeviceAdapters/SutterLambda/SutterLambda.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/SutterLambda2/SutterLambda2.vcxproj b/DeviceAdapters/SutterLambda2/SutterLambda2.vcxproj index 7ef510da4..4a2fe06bd 100644 --- a/DeviceAdapters/SutterLambda2/SutterLambda2.vcxproj +++ b/DeviceAdapters/SutterLambda2/SutterLambda2.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;DefineShutterOnTenDashTwo;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;DefineShutterOnTenDashTwo;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;DefineShutterOnTenDashTwo;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;DefineShutterOnTenDashTwo;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/SutterStage/SutterStage.vcxproj b/DeviceAdapters/SutterStage/SutterStage.vcxproj index d682c7fed..8e1b40bbc 100644 --- a/DeviceAdapters/SutterStage/SutterStage.vcxproj +++ b/DeviceAdapters/SutterStage/SutterStage.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/TISCam/TIScam.vcxproj b/DeviceAdapters/TISCam/TIScam.vcxproj index bfe3690ee..2f4fb6705 100644 --- a/DeviceAdapters/TISCam/TIScam.vcxproj +++ b/DeviceAdapters/TISCam/TIScam.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\TheImagingSource\classlib_3_5_6\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -77,7 +77,7 @@ $(MM_3RDPARTYPRIVATE)\TheImagingSource\classlib_3_5_6\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/TSI/TSI.vcxproj b/DeviceAdapters/TSI/TSI.vcxproj index 157bd7ac0..21c4d8f67 100644 --- a/DeviceAdapters/TSI/TSI.vcxproj +++ b/DeviceAdapters/TSI/TSI.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)/Thorlabs/Scientific_Camera_Interfaces_Windows-2.1/Scientific Camera Interfaces/SDK/Native Toolkit/load_dll_helpers;$(MM_3RDPARTYPRIVATE)/Thorlabs/Scientific_Camera_Interfaces_Windows-2.1/Scientific Camera Interfaces/SDK/Native Toolkit/include;$(MM_3RDPARTYPRIVATE)/Thorlabs/Scientific_Camera_Interfaces_Windows-2.1/Scientific Camera Interfaces/SDK/Legacy (Obsolete)/Native Scientific CCD Camera Toolkit/include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -74,7 +74,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)/Thorlabs/Scientific_Camera_Interfaces_Windows-2.1/Scientific Camera Interfaces/SDK/Native Toolkit/load_dll_helpers;$(MM_3RDPARTYPRIVATE)/Thorlabs/Scientific_Camera_Interfaces_Windows-2.1/Scientific Camera Interfaces/SDK/Native Toolkit/include;$(MM_3RDPARTYPRIVATE)/Thorlabs/Scientific_Camera_Interfaces_Windows-2.1/Scientific Camera Interfaces/SDK/Legacy (Obsolete)/Native Scientific CCD Camera Toolkit/include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/TUCam/MMTUCam.vcxproj b/DeviceAdapters/TUCam/MMTUCam.vcxproj index 76a78f2fe..1c8845a52 100644 --- a/DeviceAdapters/TUCam/MMTUCam.vcxproj +++ b/DeviceAdapters/TUCam/MMTUCam.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) Async EnableFastChecks MultiThreadedDebugDLL @@ -88,7 +88,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) Async MultiThreadedDLL true diff --git a/DeviceAdapters/TeesnySLM/TeensySLM.vcxproj b/DeviceAdapters/TeesnySLM/TeensySLM.vcxproj index 5f570108e..a928f21b5 100644 --- a/DeviceAdapters/TeesnySLM/TeensySLM.vcxproj +++ b/DeviceAdapters/TeesnySLM/TeensySLM.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks 4290;%(DisableSpecificWarnings) @@ -74,7 +74,7 @@ MaxSpeed true %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Thorlabs/Thorlabs.vcxproj b/DeviceAdapters/Thorlabs/Thorlabs.vcxproj index 10507a9a8..fa58362d9 100644 --- a/DeviceAdapters/Thorlabs/Thorlabs.vcxproj +++ b/DeviceAdapters/Thorlabs/Thorlabs.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -71,7 +71,7 @@ MaxSpeed true - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) false diff --git a/DeviceAdapters/ThorlabsAPTStage/ThorlabsAPTStage.vcxproj b/DeviceAdapters/ThorlabsAPTStage/ThorlabsAPTStage.vcxproj index 3d7196b8e..9fba9c6b8 100644 --- a/DeviceAdapters/ThorlabsAPTStage/ThorlabsAPTStage.vcxproj +++ b/DeviceAdapters/ThorlabsAPTStage/ThorlabsAPTStage.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)/Thorlabs/APTDLLClient_x64;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -74,7 +74,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)/Thorlabs/APTDLLClient_x64;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ThorlabsDCxxxx/ThorlabsDCxxxx.vcxproj b/DeviceAdapters/ThorlabsDCxxxx/ThorlabsDCxxxx.vcxproj index 1253ac4f3..d9b46f8c2 100644 --- a/DeviceAdapters/ThorlabsDCxxxx/ThorlabsDCxxxx.vcxproj +++ b/DeviceAdapters/ThorlabsDCxxxx/ThorlabsDCxxxx.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -78,7 +78,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ThorlabsElliptecSlider/ThorlabsElliptecSlider.vcxproj b/DeviceAdapters/ThorlabsElliptecSlider/ThorlabsElliptecSlider.vcxproj index d160f988b..18a3d60df 100644 --- a/DeviceAdapters/ThorlabsElliptecSlider/ThorlabsElliptecSlider.vcxproj +++ b/DeviceAdapters/ThorlabsElliptecSlider/ThorlabsElliptecSlider.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ThorlabsFilterWheel/ThorlabsFilterWheel.vcxproj b/DeviceAdapters/ThorlabsFilterWheel/ThorlabsFilterWheel.vcxproj index 14c1f3fb3..457789049 100644 --- a/DeviceAdapters/ThorlabsFilterWheel/ThorlabsFilterWheel.vcxproj +++ b/DeviceAdapters/ThorlabsFilterWheel/ThorlabsFilterWheel.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ThorlabsPM100x/ThorlabsPM100x.vcxproj b/DeviceAdapters/ThorlabsPM100x/ThorlabsPM100x.vcxproj index 21fd80aa8..c4478ee06 100644 --- a/DeviceAdapters/ThorlabsPM100x/ThorlabsPM100x.vcxproj +++ b/DeviceAdapters/ThorlabsPM100x/ThorlabsPM100x.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\Thorlabs\PM100\VISA\Win64\Include;C:\Program Files (x86)\IVI Foundation\VISA\WinNT\Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -77,7 +77,7 @@ $(MM_3RDPARTYPRIVATE)\Thorlabs\PM100\VISA\Win64\Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ThorlabsSC10/ThorlabsSC10.vcxproj b/DeviceAdapters/ThorlabsSC10/ThorlabsSC10.vcxproj index 345b300f3..a8cf4c0bb 100644 --- a/DeviceAdapters/ThorlabsSC10/ThorlabsSC10.vcxproj +++ b/DeviceAdapters/ThorlabsSC10/ThorlabsSC10.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -72,7 +72,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/ThorlabsUSBCamera/ThorlabsUSBCamera.vcxproj b/DeviceAdapters/ThorlabsUSBCamera/ThorlabsUSBCamera.vcxproj index 2a437e0c1..8354a8d6d 100644 --- a/DeviceAdapters/ThorlabsUSBCamera/ThorlabsUSBCamera.vcxproj +++ b/DeviceAdapters/ThorlabsUSBCamera/ThorlabsUSBCamera.vcxproj @@ -57,7 +57,7 @@ true Speed $(MM_3RDPARTYPRIVATE)\Thorlabs\DCx_SDK_4_20\Develop\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true 4290;%(DisableSpecificWarnings) @@ -79,7 +79,7 @@ true Speed $(MM_3RDPARTYPRIVATE)\Thorlabs\DCx_SDK_4_20\Develop\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Tofra/Tofra.vcxproj b/DeviceAdapters/Tofra/Tofra.vcxproj index b933dc666..d609e9d55 100644 --- a/DeviceAdapters/Tofra/Tofra.vcxproj +++ b/DeviceAdapters/Tofra/Tofra.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Toptica_iBeamSmartCW/Toptica_iBeamSmartCW.vcxproj b/DeviceAdapters/Toptica_iBeamSmartCW/Toptica_iBeamSmartCW.vcxproj index e401afd95..5306fe1b6 100644 --- a/DeviceAdapters/Toptica_iBeamSmartCW/Toptica_iBeamSmartCW.vcxproj +++ b/DeviceAdapters/Toptica_iBeamSmartCW/Toptica_iBeamSmartCW.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/TwainCamera/TwainCamera.vcxproj b/DeviceAdapters/TwainCamera/TwainCamera.vcxproj index abb929cce..7119c5f55 100644 --- a/DeviceAdapters/TwainCamera/TwainCamera.vcxproj +++ b/DeviceAdapters/TwainCamera/TwainCamera.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/TwoPhoton/TwoPhoton.vcxproj b/DeviceAdapters/TwoPhoton/TwoPhoton.vcxproj index 23b9f44aa..01a503b12 100644 --- a/DeviceAdapters/TwoPhoton/TwoPhoton.vcxproj +++ b/DeviceAdapters/TwoPhoton/TwoPhoton.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\Bitflow\SDK_6.30\Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -77,7 +77,7 @@ true Speed $(MM_3RDPARTYPRIVATE)\Bitflow\SDK_6.30\Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/USBManager/USBManager.vcxproj b/DeviceAdapters/USBManager/USBManager.vcxproj index c690af818..678ce0061 100644 --- a/DeviceAdapters/USBManager/USBManager.vcxproj +++ b/DeviceAdapters/USBManager/USBManager.vcxproj @@ -55,7 +55,7 @@ Disabled $(MM_3RDPARTYPUBLIC)\libusb-win32-bin-1.2.6.0\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -76,7 +76,7 @@ $(MM_3RDPARTYPUBLIC)\libusb-win32-bin-1.2.6.0\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/USB_Viper_QPL/USB_Viper_QPL.vcxproj b/DeviceAdapters/USB_Viper_QPL/USB_Viper_QPL.vcxproj index 9f9ebf00c..f406a7bc3 100644 --- a/DeviceAdapters/USB_Viper_QPL/USB_Viper_QPL.vcxproj +++ b/DeviceAdapters/USB_Viper_QPL/USB_Viper_QPL.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)\MeasurementComputing\UniversalLibrary\6.27\DAQ\C;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -77,7 +77,7 @@ $(MM_3RDPARTYPRIVATE)\MeasurementComputing\UniversalLibrary\6.27\DAQ\C;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/UniversalMMHubSerial/UniversalMMHubSerial.vcxproj b/DeviceAdapters/UniversalMMHubSerial/UniversalMMHubSerial.vcxproj index c4bce2bb5..03b05e887 100644 --- a/DeviceAdapters/UniversalMMHubSerial/UniversalMMHubSerial.vcxproj +++ b/DeviceAdapters/UniversalMMHubSerial/UniversalMMHubSerial.vcxproj @@ -56,7 +56,7 @@ Disabled true Speed - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -77,7 +77,7 @@ MaxSpeed true Speed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/UniversalMMHubUsb/UniversalMMHubUsb.vcxproj b/DeviceAdapters/UniversalMMHubUsb/UniversalMMHubUsb.vcxproj index 61b54da65..635ca62d8 100644 --- a/DeviceAdapters/UniversalMMHubUsb/UniversalMMHubUsb.vcxproj +++ b/DeviceAdapters/UniversalMMHubUsb/UniversalMMHubUsb.vcxproj @@ -56,7 +56,7 @@ Disabled true Speed - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -80,7 +80,7 @@ MaxSpeed true Speed - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Utilities/Utilities.vcxproj b/DeviceAdapters/Utilities/Utilities.vcxproj index 1c2217739..540094967 100644 --- a/DeviceAdapters/Utilities/Utilities.vcxproj +++ b/DeviceAdapters/Utilities/Utilities.vcxproj @@ -54,7 +54,7 @@ %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks 4290;%(DisableSpecificWarnings) @@ -70,7 +70,7 @@ false %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/VariLC/VariLC.vcxproj b/DeviceAdapters/VariLC/VariLC.vcxproj index c2a709dfd..f61885955 100644 --- a/DeviceAdapters/VariLC/VariLC.vcxproj +++ b/DeviceAdapters/VariLC/VariLC.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -72,7 +72,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/VarispecLCTF/VarispecLCTF.vcxproj b/DeviceAdapters/VarispecLCTF/VarispecLCTF.vcxproj index d3fe2c63e..ed0084cc2 100644 --- a/DeviceAdapters/VarispecLCTF/VarispecLCTF.vcxproj +++ b/DeviceAdapters/VarispecLCTF/VarispecLCTF.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +73,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Vincent/Vincent.vcxproj b/DeviceAdapters/Vincent/Vincent.vcxproj index de0ecb3c0..f80b9e2b2 100644 --- a/DeviceAdapters/Vincent/Vincent.vcxproj +++ b/DeviceAdapters/Vincent/Vincent.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -74,7 +74,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Vortran/Stradus.vcxproj b/DeviceAdapters/Vortran/Stradus.vcxproj index ed70ab997..2cc9ca196 100644 --- a/DeviceAdapters/Vortran/Stradus.vcxproj +++ b/DeviceAdapters/Vortran/Stradus.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Vortran/VersaLase.vcxproj b/DeviceAdapters/Vortran/VersaLase.vcxproj index a09b57ee9..70bc6daa8 100644 --- a/DeviceAdapters/Vortran/VersaLase.vcxproj +++ b/DeviceAdapters/Vortran/VersaLase.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/WieneckeSinske/WieneckeSinske.vcxproj b/DeviceAdapters/WieneckeSinske/WieneckeSinske.vcxproj index 690e8a751..66e0adc6f 100644 --- a/DeviceAdapters/WieneckeSinske/WieneckeSinske.vcxproj +++ b/DeviceAdapters/WieneckeSinske/WieneckeSinske.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -81,7 +81,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;4127;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/XCite120PC_Exacte/XCite120PC_Exacte.vcxproj b/DeviceAdapters/XCite120PC_Exacte/XCite120PC_Exacte.vcxproj index 9b16597ff..8b5e72ace 100644 --- a/DeviceAdapters/XCite120PC_Exacte/XCite120PC_Exacte.vcxproj +++ b/DeviceAdapters/XCite120PC_Exacte/XCite120PC_Exacte.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -72,7 +72,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/XCiteLed/XCiteLed.vcxproj b/DeviceAdapters/XCiteLed/XCiteLed.vcxproj index 620be9535..6a1802dab 100644 --- a/DeviceAdapters/XCiteLed/XCiteLed.vcxproj +++ b/DeviceAdapters/XCiteLed/XCiteLed.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks @@ -72,7 +72,7 @@ MaxSpeed true - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/XCiteXT600/XCiteXT600.vcxproj b/DeviceAdapters/XCiteXT600/XCiteXT600.vcxproj index 7d348819f..eb0888020 100644 --- a/DeviceAdapters/XCiteXT600/XCiteXT600.vcxproj +++ b/DeviceAdapters/XCiteXT600/XCiteXT600.vcxproj @@ -55,7 +55,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks @@ -72,7 +72,7 @@ MaxSpeed true - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/Xcite/Xcite.vcxproj b/DeviceAdapters/Xcite/Xcite.vcxproj index c76fc7d6e..cf2031876 100644 --- a/DeviceAdapters/Xcite/Xcite.vcxproj +++ b/DeviceAdapters/Xcite/Xcite.vcxproj @@ -54,7 +54,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -71,7 +71,7 @@ X64 - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Ximea/XIMEACamera.vcxproj b/DeviceAdapters/Ximea/XIMEACamera.vcxproj index e6ea2d047..36ce12437 100644 --- a/DeviceAdapters/Ximea/XIMEACamera.vcxproj +++ b/DeviceAdapters/Ximea/XIMEACamera.vcxproj @@ -56,7 +56,7 @@ Disabled $(MM_3RDPARTYPRIVATE)public\pugixml\pugixml-1.8\src;$(MM_3RDPARTYPRIVATE)\Ximea\API-Windows-4.21.2.0-beta\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ MaxSpeed true $(MM_3RDPARTYPRIVATE)public\pugixml\pugixml-1.8\src;$(MM_3RDPARTYPRIVATE)\Ximea\API-Windows-4.21.2.0-beta\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/YodnE600/YodnE600.vcxproj b/DeviceAdapters/YodnE600/YodnE600.vcxproj index 8275cd008..53cea4bad 100644 --- a/DeviceAdapters/YodnE600/YodnE600.vcxproj +++ b/DeviceAdapters/YodnE600/YodnE600.vcxproj @@ -53,7 +53,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks diff --git a/DeviceAdapters/Yokogawa/CSUX/CSUX.vcxproj b/DeviceAdapters/Yokogawa/CSUX/CSUX.vcxproj index 965f2b460..fc6db5e7d 100644 --- a/DeviceAdapters/Yokogawa/CSUX/CSUX.vcxproj +++ b/DeviceAdapters/Yokogawa/CSUX/CSUX.vcxproj @@ -54,7 +54,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) @@ -70,7 +70,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Yokogawa/Yokogawa.vcxproj b/DeviceAdapters/Yokogawa/Yokogawa.vcxproj index 343d73a7a..5e1a54ccf 100644 --- a/DeviceAdapters/Yokogawa/Yokogawa.vcxproj +++ b/DeviceAdapters/Yokogawa/Yokogawa.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -74,7 +74,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/Zaber/Zaber.vcxproj b/DeviceAdapters/Zaber/Zaber.vcxproj index 9fcad38a5..a25dd995a 100644 --- a/DeviceAdapters/Zaber/Zaber.vcxproj +++ b/DeviceAdapters/Zaber/Zaber.vcxproj @@ -49,7 +49,7 @@ Disabled - WIN32;MODULE_EXPORTS;_USRDLL;_WINDOWS;%(PreprocessorDefinitions) + WIN32;_USRDLL;_WINDOWS;%(PreprocessorDefinitions) @@ -58,7 +58,7 @@ MaxSpeed true true - WIN32;MODULE_EXPORTS;_USRDLL;_WINDOWS;%(PreprocessorDefinitions) + WIN32;_USRDLL;_WINDOWS;%(PreprocessorDefinitions) $(MM_3RDPARTYPUBLIC)\Zaber\zaber-motion\include;%(AdditionalIncludeDirectories) diff --git a/DeviceAdapters/ZeissAxioZoom/ZeissAxioZoom.vcxproj b/DeviceAdapters/ZeissAxioZoom/ZeissAxioZoom.vcxproj index 53dd32091..8ab4df04f 100644 --- a/DeviceAdapters/ZeissAxioZoom/ZeissAxioZoom.vcxproj +++ b/DeviceAdapters/ZeissAxioZoom/ZeissAxioZoom.vcxproj @@ -57,7 +57,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -78,7 +78,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;4127;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/ZeissCAN/ZeissCAN.vcxproj b/DeviceAdapters/ZeissCAN/ZeissCAN.vcxproj index a23cc13ce..555fd62a4 100644 --- a/DeviceAdapters/ZeissCAN/ZeissCAN.vcxproj +++ b/DeviceAdapters/ZeissCAN/ZeissCAN.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/ZeissCAN29/ZeissCAN29.vcxproj b/DeviceAdapters/ZeissCAN29/ZeissCAN29.vcxproj index 1c5dd46d0..ffc8f437e 100644 --- a/DeviceAdapters/ZeissCAN29/ZeissCAN29.vcxproj +++ b/DeviceAdapters/ZeissCAN29/ZeissCAN29.vcxproj @@ -56,7 +56,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks @@ -76,7 +76,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 4290;4127;%(DisableSpecificWarnings) diff --git a/DeviceAdapters/dc1394/dc1394.vcxproj b/DeviceAdapters/dc1394/dc1394.vcxproj index fb796b10b..6d3b61fc4 100644 --- a/DeviceAdapters/dc1394/dc1394.vcxproj +++ b/DeviceAdapters/dc1394/dc1394.vcxproj @@ -52,7 +52,7 @@ Disabled $(MM_3RDPARTYPUBLIC)\libdc1394\libdc1394-2.2.1-bin\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -70,7 +70,7 @@ $(MM_3RDPARTYPUBLIC)\libdc1394\libdc1394-2.2.1-bin\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/kdv/KDV.vcxproj b/DeviceAdapters/kdv/KDV.vcxproj index a5279d802..c446512f0 100644 --- a/DeviceAdapters/kdv/KDV.vcxproj +++ b/DeviceAdapters/kdv/KDV.vcxproj @@ -55,7 +55,7 @@ Disabled %(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) EnableFastChecks true @@ -75,7 +75,7 @@ %(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true diff --git a/DeviceAdapters/nPoint/NPointC400.vcxproj b/DeviceAdapters/nPoint/NPointC400.vcxproj index b35b31ea8..884cfedcf 100644 --- a/DeviceAdapters/nPoint/NPointC400.vcxproj +++ b/DeviceAdapters/nPoint/NPointC400.vcxproj @@ -56,7 +56,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks 4290;%(DisableSpecificWarnings) @@ -73,7 +73,7 @@ Disabled false - WIN32;NDEBUG;_WINDOWS;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) false 4290;%(DisableSpecificWarnings) From 39a90f7e7ad823e3d591abf3644cce3b7d683544 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 24 Jan 2024 11:04:56 -0600 Subject: [PATCH 137/141] Replace MODULE_EXPORTS with MMDEVICE_CLIENT_BUILD The new macro has (roughly) the opposite meaning as the (intent of) MODULE_EXPORTS: it should be defined when building MMCore and undefined when building device adapters. The source is now _capable_ of excluding the module interface functions when building MMCore (and the MMCore Meson build does so). Symbol visibility is set for GCC/Clang so that -fvisibility=hidden can be used when building device adapters (but this is not done for now). No changes to device adapters is required. --- MMCore/MMCore.vcxproj | 4 +- MMCore/Makefile.am | 2 + MMCore/meson.build | 7 ++-- MMCoreJ_wrap/Makefile.am | 3 +- MMDevice/MMDevice-SharedRuntime.vcxproj | 4 +- MMDevice/MMDevice-StaticRuntime.vcxproj | 4 +- MMDevice/MMDevice.h | 7 ++-- MMDevice/ModuleInterface.cpp | 5 ++- MMDevice/ModuleInterface.h | 37 ++++++++++--------- MMDevice/meson.build | 9 ++++- MMDevice/meson_options.txt | 3 ++ .../VisualStudio/MMDeviceAdapter.props | 1 - 12 files changed, 50 insertions(+), 36 deletions(-) diff --git a/MMCore/MMCore.vcxproj b/MMCore/MMCore.vcxproj index 413f2b563..3fe6c5a21 100644 --- a/MMCore/MMCore.vcxproj +++ b/MMCore/MMCore.vcxproj @@ -50,7 +50,7 @@ Disabled - NOMINMAX;WIN32;_DEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + MMDEVICE_CLIENT_BUILD;NOMINMAX;WIN32;_DEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) EnableFastChecks true @@ -65,7 +65,7 @@ X64 - NOMINMAX;WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + MMDEVICE_CLIENT_BUILD;NOMINMAX;WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) true diff --git a/MMCore/Makefile.am b/MMCore/Makefile.am index 013860a8a..e5d26a97d 100644 --- a/MMCore/Makefile.am +++ b/MMCore/Makefile.am @@ -1,5 +1,7 @@ AUTOMAKE_OPTIONS = foreign subdir-objects +AM_CPPFLAGS = -DMMDEVICE_CLIENT_BUILD + noinst_LTLIBRARIES = libMMCore.la libMMCore_la_LIBADD = ../MMDevice/libMMDevice.la diff --git a/MMCore/meson.build b/MMCore/meson.build index 3049fbb65..8b04fafb3 100644 --- a/MMCore/meson.build +++ b/MMCore/meson.build @@ -4,7 +4,7 @@ project( 'MMCore', 'cpp', - meson_version: '>=1.1.0', # May relax + meson_version: '>=1.2.0', default_options: [ 'cpp_std=c++14', 'warning_level=3', @@ -28,9 +28,10 @@ else endif mmdevice_proj = subproject( 'MMDevice', - # Propagate value of 'tests' option ('yield: true' in MMDeivce's 'tests' - # option did not seem to work; Meson 1.3.1). default_options: { + 'client_interface': true, + # Propagate value of 'tests' option ('yield: true' in MMDeivce's + # 'tests' option did not seem to work; Meson 1.3.1). 'tests': tests_option, }, ) diff --git a/MMCoreJ_wrap/Makefile.am b/MMCoreJ_wrap/Makefile.am index 83fc9e744..8fc832543 100644 --- a/MMCoreJ_wrap/Makefile.am +++ b/MMCoreJ_wrap/Makefile.am @@ -10,9 +10,8 @@ AUTOMAKE_OPTIONS = foreign subdir-objects # (We used to globally use -O by default, rather than -O2, on Linux, presumably # for the above reason (though the intent was never documented). But there is # nothing Linux-specific about this.) -# TODO The flag should come from configure. AM_CXXFLAGS = -fno-strict-aliasing -AM_CPPFLAGS = $(JNI_CPPFLAGS) +AM_CPPFLAGS = $(JNI_CPPFLAGS) -DMMDEVICE_CLIENT_BUILD # This ugly list of headers is necessary to trigger the rebuild of the diff --git a/MMDevice/MMDevice-SharedRuntime.vcxproj b/MMDevice/MMDevice-SharedRuntime.vcxproj index d1e1f8636..8fc7e8ad2 100644 --- a/MMDevice/MMDevice-SharedRuntime.vcxproj +++ b/MMDevice/MMDevice-SharedRuntime.vcxproj @@ -65,7 +65,7 @@ Disabled - WIN32;_DEBUG;_LIB;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) Windows @@ -77,7 +77,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) Windows diff --git a/MMDevice/MMDevice-StaticRuntime.vcxproj b/MMDevice/MMDevice-StaticRuntime.vcxproj index be67f1eed..bdb2fce9a 100644 --- a/MMDevice/MMDevice-StaticRuntime.vcxproj +++ b/MMDevice/MMDevice-StaticRuntime.vcxproj @@ -65,7 +65,7 @@ Disabled - WIN32;_DEBUG;_LIB;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) MultiThreadedDebug @@ -78,7 +78,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;MODULE_EXPORTS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) MultiThreaded diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index ae95e6ec6..f0e60538c 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -53,7 +53,10 @@ #include -#ifdef MODULE_EXPORTS +#ifdef MMDEVICE_CLIENT_BUILD +// Hide deprecation warnings when building MMCore +# define MM_DEPRECATED(prototype) prototype +#else # ifdef _MSC_VER # define MM_DEPRECATED(prototype) __declspec(deprecated) prototype # elif defined(__GNUC__) @@ -61,8 +64,6 @@ # else # define MM_DEPRECATED(prototype) prototype # endif -#else -# define MM_DEPRECATED(prototype) prototype #endif #ifdef _WIN32 diff --git a/MMDevice/ModuleInterface.cpp b/MMDevice/ModuleInterface.cpp index cbfd83b60..0a3a8a520 100644 --- a/MMDevice/ModuleInterface.cpp +++ b/MMDevice/ModuleInterface.cpp @@ -23,11 +23,12 @@ #include "ModuleInterface.h" +#ifndef MMDEVICE_CLIENT_BUILD + #include #include #include - namespace { struct DeviceInfo @@ -135,3 +136,5 @@ void RegisterDevice(const char* deviceName, MM::DeviceType deviceType, const cha g_registeredDevices.push_back(DeviceInfo(deviceName, deviceType, deviceDescription)); } + +#endif // MMDEVICE_CLIENT_BUILD diff --git a/MMDevice/ModuleInterface.h b/MMDevice/ModuleInterface.h index 4aa5f2301..ecdd760ee 100644 --- a/MMDevice/ModuleInterface.h +++ b/MMDevice/ModuleInterface.h @@ -29,17 +29,6 @@ #include "MMDevice.h" -#ifdef _WIN32 - #ifdef MODULE_EXPORTS - #define MODULE_API __declspec(dllexport) - #else - #define MODULE_API __declspec(dllimport) - #endif -#else - #define MODULE_API -#endif - - /// Module interface version. /** * The Core ensures that any loaded device adapter modules have a matching @@ -50,11 +39,20 @@ // GetModuleVersion() must never change. #define MODULE_INTERFACE_VERSION 10 - -/* - * Exported module interface - */ extern "C" { +#ifndef MMDEVICE_CLIENT_BUILD + +// Make the module interface functions visible from outside the module. +#ifdef _MSC_VER +# define MODULE_API __declspec(dllexport) +#else +# define MODULE_API __attribute__((visibility("default"))) +#endif + + /* + * Exported module interface + */ + /// Initialize the device adapter module. /** * Device adapter modules must provide an implementation of this function. @@ -103,10 +101,10 @@ extern "C" { MODULE_API bool GetDeviceName(unsigned deviceIndex, char* name, unsigned bufferLength); MODULE_API bool GetDeviceType(const char* deviceName, int* type); MODULE_API bool GetDeviceDescription(const char* deviceName, char* name, unsigned bufferLength); +#endif // MMDEVICE_CLIENT_BUILD +#ifdef MMDEVICE_CLIENT_BUILD // Function pointer types for module interface functions - // (Not for use by device adapters) -#ifndef MODULE_EXPORTS typedef void (*fnInitializeModuleData)(); typedef MM::Device* (*fnCreateDevice)(const char*); typedef void (*fnDeleteDevice)(MM::Device*); @@ -116,9 +114,10 @@ extern "C" { typedef bool (*fnGetDeviceName)(unsigned, char*, unsigned); typedef bool (*fnGetDeviceType)(const char*, int*); typedef bool (*fnGetDeviceDescription)(const char*, char*, unsigned); -#endif +#endif // MMDEVICE_CLIENT_BUILD } +#ifndef MMDEVICE_CLIENT_BUILD /* * Functions for use by the device adapter module @@ -135,3 +134,5 @@ extern "C" { * \see InitializeModuleData() */ void RegisterDevice(const char* deviceName, MM::DeviceType deviceType, const char* description); + +#endif // MMDEVICE_CLIENT_BUILD diff --git a/MMDevice/meson.build b/MMDevice/meson.build index dcddc8cb0..094d22430 100644 --- a/MMDevice/meson.build +++ b/MMDevice/meson.build @@ -11,6 +11,11 @@ project( ], ) +build_mode_args = [] +if get_option('client_interface') + build_mode_args += '-DMMDEVICE_CLIENT_BUILD' +endif + # We intentionally do NOT define NOMINMAX on Windows. MMDevice should compile # correctly with or without Windows.h's min()/max() macros. @@ -43,8 +48,7 @@ mmdevice_lib = static_library( 'MMDevice', sources: mmdevice_sources, include_directories: mmdevice_include_dir, - cpp_args: [ - '-DMODULE_EXPORTS', + cpp_args: build_mode_args + [ '-D_CRT_SECURE_NO_WARNINGS', # TODO Eliminate the need ], # MMDevice does not depend on any external library. This is a big advantage @@ -58,6 +62,7 @@ mmdevice_lib = static_library( subdir('unittest') mmdevice = declare_dependency( + compile_args: build_mode_args, include_directories: mmdevice_include_dir, link_with: mmdevice_lib, ) diff --git a/MMDevice/meson_options.txt b/MMDevice/meson_options.txt index 4f5d777ee..d10a16fb8 100644 --- a/MMDevice/meson_options.txt +++ b/MMDevice/meson_options.txt @@ -1,3 +1,6 @@ +option('client_interface', type: 'boolean', value: false, + description: 'Build for use by MMCore, as opposed to by a device adapter', +) option('tests', type: 'feature', value: 'enabled', yield: true, description: 'Build unit tests', ) diff --git a/buildscripts/VisualStudio/MMDeviceAdapter.props b/buildscripts/VisualStudio/MMDeviceAdapter.props index 7e4e52053..04ab5bc99 100644 --- a/buildscripts/VisualStudio/MMDeviceAdapter.props +++ b/buildscripts/VisualStudio/MMDeviceAdapter.props @@ -7,7 +7,6 @@ - MODULE_EXPORTS;%(PreprocessorDefinitions) $(MM_MMDEVICE_INCLUDEDIR);$(MM_BOOST_INCLUDEDIR);%(AdditionalIncludeDirectories) From 3f78883084e2afcb5ef91c8e8e9e3f5c4a2b0068 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 21:29:33 +0000 Subject: [PATCH 138/141] Update secret-device-adapters-commit to latest --- secret-device-adapters-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret-device-adapters-commit b/secret-device-adapters-commit index 8b35b92cd..068175143 100644 --- a/secret-device-adapters-commit +++ b/secret-device-adapters-commit @@ -1 +1 @@ -da3dc9f547ec408a17f4610e7e01b4b870c5c546 +e413f51e93b71d5513dc4febc950266c054832f0 From fd74a720750d83905596cb3ea58528b839399a28 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 24 Jan 2024 16:18:32 -0600 Subject: [PATCH 139/141] MMDevice/MMCore[J]: Remove unused macro defs Remove preprocessor definitions from MMDevice/MMCore/MMCoreJ_wrap .vcxproj files This is to maintain the cleanup done in #415 (specifically, 4536cc099caa4793fadacbea3dcc694de679f014) -- to prevent the vcxproj build from going out of sync with the experimental Meson build. - `_DEBUG` - the compiler defines this when using the debug runtime; redundant - `_LIB` - added by VS by default for static libs; we don't use it - `_USRDLL` - added by VS by default for DLLs; we don't use it - `_WINDOWS` - added by VS by default; we don't use it - `WIN32` - added by VS by default; we prefer `_WIN32`, defined by the compiler Also delete the single use of `_DEBUG` in `CMMCore::getVersionInfo()`. It is better not to produce a different version string in a debug build, because applications may parse this string (for example, a pymmcore test does that). All the other macros removed here are not currently used in these projects. --- MMCore/MMCore.cpp | 3 --- MMCore/MMCore.vcxproj | 4 ++-- MMCoreJ_wrap/MMCoreJ_wrap.vcxproj | 5 ++--- MMDevice/MMDevice-SharedRuntime.vcxproj | 5 ++--- MMDevice/MMDevice-StaticRuntime.vcxproj | 5 ++--- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 22c2c2ecc..4bef95471 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -362,9 +362,6 @@ std::string CMMCore::getVersionInfo() const std::ostringstream txt; std::string debug; txt << "MMCore version " << MMCore_versionMajor << "." << MMCore_versionMinor << "." << MMCore_versionPatch; - #ifdef _DEBUG - txt << " (debug)"; - #endif return txt.str(); } diff --git a/MMCore/MMCore.vcxproj b/MMCore/MMCore.vcxproj index 3fe6c5a21..ebfdcb9ba 100644 --- a/MMCore/MMCore.vcxproj +++ b/MMCore/MMCore.vcxproj @@ -50,7 +50,7 @@ Disabled - MMDEVICE_CLIENT_BUILD;NOMINMAX;WIN32;_DEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + MMDEVICE_CLIENT_BUILD;NOMINMAX;%(PreprocessorDefinitions) EnableFastChecks true @@ -65,7 +65,7 @@ X64 - MMDEVICE_CLIENT_BUILD;NOMINMAX;WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + MMDEVICE_CLIENT_BUILD;NOMINMAX;NDEBUG;%(PreprocessorDefinitions) true diff --git a/MMCoreJ_wrap/MMCoreJ_wrap.vcxproj b/MMCoreJ_wrap/MMCoreJ_wrap.vcxproj index f0128901f..54e1e1902 100644 --- a/MMCoreJ_wrap/MMCoreJ_wrap.vcxproj +++ b/MMCoreJ_wrap/MMCoreJ_wrap.vcxproj @@ -53,7 +53,6 @@ Disabled $(JAVA_HOME)\include\win32;$(JAVA_HOME)\include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;MMCOREJ_WRAP_EXPORTS;%(PreprocessorDefinitions) EnableFastChecks true @@ -73,7 +72,7 @@ $(JAVA_HOME)\include\win32;$(JAVA_HOME)\include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;MMCOREJ_WRAP_EXPORTS;%(PreprocessorDefinitions) + NDEBUG;%(PreprocessorDefinitions) true @@ -147,4 +146,4 @@ copy %swig_out_dir%\*.java %java_src_dir%\ > NUL - \ No newline at end of file + diff --git a/MMDevice/MMDevice-SharedRuntime.vcxproj b/MMDevice/MMDevice-SharedRuntime.vcxproj index 8fc7e8ad2..8df751203 100644 --- a/MMDevice/MMDevice-SharedRuntime.vcxproj +++ b/MMDevice/MMDevice-SharedRuntime.vcxproj @@ -65,7 +65,6 @@ Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) Windows @@ -77,7 +76,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + NDEBUG;%(PreprocessorDefinitions) Windows @@ -89,4 +88,4 @@ - \ No newline at end of file + diff --git a/MMDevice/MMDevice-StaticRuntime.vcxproj b/MMDevice/MMDevice-StaticRuntime.vcxproj index bdb2fce9a..b1317341a 100644 --- a/MMDevice/MMDevice-StaticRuntime.vcxproj +++ b/MMDevice/MMDevice-StaticRuntime.vcxproj @@ -65,7 +65,6 @@ Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) MultiThreadedDebug @@ -78,7 +77,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + NDEBUG;%(PreprocessorDefinitions) MultiThreaded @@ -91,4 +90,4 @@ - \ No newline at end of file + From 673241335cc4b7264c834792cccde32ffc9857db Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 24 Jan 2024 17:10:53 -0600 Subject: [PATCH 140/141] MMDevice: Deprecate functions - Those discussed in #295 - Get/SetModuleHandle(), which were deprecated in comment - Direct access to device pointers, deprecated in comment --- MMDevice/MMDevice.h | 67 ++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index f0e60538c..d6a7db5e0 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -66,6 +66,7 @@ # endif #endif +// To be removed once the deprecated Get/SetModuleHandle() is removed: #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -279,13 +280,9 @@ namespace MM { virtual void SetDelayMs(double delay) = 0; virtual bool UsesDelay() = 0; - /** - * library handle management (for use only in the client code) - */ - // TODO Get/SetModuleHandle() is no longer used; can remove at a - // convenient time. - virtual HDEVMODULE GetModuleHandle() const = 0; - virtual void SetModuleHandle(HDEVMODULE hLibraryHandle) = 0; + MM_DEPRECATED(virtual HDEVMODULE GetModuleHandle() const) = 0; + MM_DEPRECATED(virtual void SetModuleHandle(HDEVMODULE hLibraryHandle)) = 0; + virtual void SetLabel(const char* label) = 0; virtual void GetLabel(char* name) const = 0; virtual void SetModuleName(const char* moduleName) = 0; @@ -1313,44 +1310,32 @@ namespace MM { /// \deprecated Use the other forms instead. virtual int InsertMultiChannel(const Device* caller, const unsigned char* buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* md = 0) = 0; - // autofocus - // TODO This interface needs improvement: the caller pointer should be - // passed, and it should be clarified whether the use of these methods is - // to be limited to autofocus or not. - Mark T. - virtual const char* GetImage() = 0; - virtual int GetImageDimensions(int& width, int& height, int& depth) = 0; - virtual int GetFocusPosition(double& pos) = 0; - virtual int SetFocusPosition(double pos) = 0; - virtual int MoveFocus(double velocity) = 0; - virtual int SetXYPosition(double x, double y) = 0; - virtual int GetXYPosition(double& x, double& y) = 0; - virtual int MoveXYStage(double vX, double vY) = 0; - virtual int SetExposure(double expMs) = 0; - virtual int GetExposure(double& expMs) = 0; - virtual int SetConfig(const char* group, const char* name) = 0; - virtual int GetCurrentConfig(const char* group, int bufLen, char* name) = 0; - virtual int GetChannelConfig(char* channelConfigName, const unsigned int channelConfigIterator) = 0; - - // direct access to specific device types - // TODO With the exception of GetParentHub(), these should be removed in - // favor of methods providing indirect access to the required - // functionality. Eventually we should completely avoid access to raw - // pointers to devices of other device adapters (because we loose - // information on errors, because direct access ignores any - // synchronization implemented in the Core, and because it would be bad - // if device adapters stored the returned pointer). - Mark T. - virtual MM::ImageProcessor* GetImageProcessor(const MM::Device* caller) = 0; // Use not recommended - virtual MM::AutoFocus* GetAutoFocus(const MM::Device* caller) = 0; // Use not recommended + // Formerly intended for use by autofocus + MM_DEPRECATED(virtual const char* GetImage()) = 0; + MM_DEPRECATED(virtual int GetImageDimensions(int& width, int& height, int& depth)) = 0; + MM_DEPRECATED(virtual int GetFocusPosition(double& pos)) = 0; + MM_DEPRECATED(virtual int SetFocusPosition(double pos)) = 0; + MM_DEPRECATED(virtual int MoveFocus(double velocity)) = 0; + MM_DEPRECATED(virtual int SetXYPosition(double x, double y)) = 0; + MM_DEPRECATED(virtual int GetXYPosition(double& x, double& y)) = 0; + MM_DEPRECATED(virtual int MoveXYStage(double vX, double vY)) = 0; + MM_DEPRECATED(virtual int SetExposure(double expMs)) = 0; + MM_DEPRECATED(virtual int GetExposure(double& expMs)) = 0; + MM_DEPRECATED(virtual int SetConfig(const char* group, const char* name)) = 0; + MM_DEPRECATED(virtual int GetCurrentConfig(const char* group, int bufLen, char* name)) = 0; + MM_DEPRECATED(virtual int GetChannelConfig(char* channelConfigName, const unsigned int channelConfigIterator)) = 0; + + // Direct (and dangerous) access to specific device types + MM_DEPRECATED(virtual MM::ImageProcessor* GetImageProcessor(const MM::Device* caller)) = 0; + MM_DEPRECATED(virtual MM::AutoFocus* GetAutoFocus(const MM::Device* caller)) = 0; virtual MM::Hub* GetParentHub(const MM::Device* caller) const = 0; - virtual MM::State* GetStateDevice(const MM::Device* caller, const char* deviceName) = 0; // Use not recommended - virtual MM::SignalIO* GetSignalIODevice(const MM::Device* caller, const char* deviceName) = 0; // Use not recommended + // More direct (and dangerous) access to specific device types + MM_DEPRECATED(virtual MM::State* GetStateDevice(const MM::Device* caller, const char* deviceName)) = 0; + MM_DEPRECATED(virtual MM::SignalIO* GetSignalIODevice(const MM::Device* caller, const char* deviceName)) = 0; - // asynchronous error handling - // TODO We do need a framework for handling asynchronous errors, but this - // interface is poorly thought through. I'm working on a better design. - // - Mark T. + // Asynchronous error handling (never implemented) /// \deprecated Not sure what this was meant to do. MM_DEPRECATED(virtual void NextPostedError(int& /*errorCode*/, char* /*pMessage*/, int /*maxlen*/, int& /*messageLength*/)) = 0; /// \deprecated Better handling of asynchronous errors to be developed. From ef695e4627013e6deecf9cff0cc13f3afeaf8229 Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Wed, 24 Jan 2024 18:16:29 -0600 Subject: [PATCH 141/141] MMDevice: Remove need for _CRT_SECURE_NO_WARNINGS That is, remove use of strcpy(), strncpy(), and getenv(). Replace str[n]cpy() with snprintf(). Add tests in the case of CDeviceUtils::CopyLimitedString() (previous implementation had a bug where it didn't null-terminate if the source string was exactly MM::MaxStrLength - 1 chars long). The use of getenv() was dead code, so remove entirely. --- MMDevice/DeviceUtils.cpp | 37 ++++++------------------- MMDevice/DeviceUtils.h | 1 - MMDevice/MMDeviceConstants.h | 3 ++ MMDevice/ModuleInterface.cpp | 5 ++-- MMDevice/meson.build | 4 +-- MMDevice/unittest/DeviceUtils-Tests.cpp | 26 +++++++++++++++++ MMDevice/unittest/meson.build | 1 + 7 files changed, 41 insertions(+), 36 deletions(-) create mode 100644 MMDevice/unittest/DeviceUtils-Tests.cpp diff --git a/MMDevice/DeviceUtils.cpp b/MMDevice/DeviceUtils.cpp index edcec6967..5ef84315f 100644 --- a/MMDevice/DeviceUtils.cpp +++ b/MMDevice/DeviceUtils.cpp @@ -34,18 +34,16 @@ char CDeviceUtils::m_pszBuffer[MM::MaxStrLength]={""}; /** - * Copies strings with predefined size limit. + * Copies string up to MM::MaxStrLength - 1 characters, truncating if necessary + * and ensuring the result is null-terminated. + * + * Behavior is undefined unless dest points to a buffer with size at least + * MM::MaxStrLength and src points to a null-terminated string. */ -bool CDeviceUtils::CopyLimitedString(char* target, const char* source) +bool CDeviceUtils::CopyLimitedString(char* dest, const char* src) { - strncpy(target, source, MM::MaxStrLength - 1); - if ((MM::MaxStrLength - 1) < strlen(source)) - { - target[MM::MaxStrLength - 1] = 0; - return false; // string truncated - } - else - return true; + snprintf(dest, MM::MaxStrLength, "%s", src); + return strlen(src) <= MM::MaxStrLength; } /** @@ -169,22 +167,3 @@ void CDeviceUtils::NapMicros(unsigned long period) usleep(period); #endif } - - -bool CDeviceUtils::CheckEnvironment(std::string env) -{ - bool bvalue = false; - if( 0 < env.length()) - { - char *pvalue = ::getenv(env.c_str()); - if( 0 != pvalue) - { - if( 0 != *pvalue) - { - char initial = (char)tolower(*pvalue); - bvalue = ('0' != initial) && ('f' != initial) && ( 'n' != initial); - } - } - } - return bvalue; -} diff --git a/MMDevice/DeviceUtils.h b/MMDevice/DeviceUtils.h index 32eb6577c..cd8e705b6 100644 --- a/MMDevice/DeviceUtils.h +++ b/MMDevice/DeviceUtils.h @@ -37,7 +37,6 @@ class CDeviceUtils static void SleepMs(long ms); static void NapMicros(unsigned long microsecs); static std::string HexRep(std::vector ); - static bool CheckEnvironment(std::string environment); private: static char m_pszBuffer[MM::MaxStrLength]; }; diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index 23c8cbc18..956b05474 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -78,6 +78,9 @@ namespace MM { + // Maximum length copied into various char buffers. + // Code providing buffer should assume excludes null terminator. + // Code filling buffer should assume includes null terminator. const int MaxStrLength = 1024; // system-wide property names diff --git a/MMDevice/ModuleInterface.cpp b/MMDevice/ModuleInterface.cpp index 0a3a8a520..1454c3e2f 100644 --- a/MMDevice/ModuleInterface.cpp +++ b/MMDevice/ModuleInterface.cpp @@ -85,7 +85,7 @@ MODULE_API bool GetDeviceName(unsigned deviceIndex, char* name, unsigned bufLen) if (deviceName.size() >= bufLen) return false; // buffer too small, can't truncate the name - strcpy(name, deviceName.c_str()); + std::snprintf(name, bufLen, "%s", deviceName.c_str()); return true; } @@ -115,8 +115,7 @@ MODULE_API bool GetDeviceDescription(const char* deviceName, char* description, if (it == g_registeredDevices.end()) return false; - strncpy(description, it->description_.c_str(), bufLen - 1); - + std::snprintf(description, bufLen, "%s", it->description_.c_str()); return true; } diff --git a/MMDevice/meson.build b/MMDevice/meson.build index 094d22430..e29cb3522 100644 --- a/MMDevice/meson.build +++ b/MMDevice/meson.build @@ -48,9 +48,7 @@ mmdevice_lib = static_library( 'MMDevice', sources: mmdevice_sources, include_directories: mmdevice_include_dir, - cpp_args: build_mode_args + [ - '-D_CRT_SECURE_NO_WARNINGS', # TODO Eliminate the need - ], + cpp_args: build_mode_args, # MMDevice does not depend on any external library. This is a big advantage # in simplifing its usage (given hundreds of device adapters depending on # MMDevice), so think twice before adding dependencies. diff --git a/MMDevice/unittest/DeviceUtils-Tests.cpp b/MMDevice/unittest/DeviceUtils-Tests.cpp new file mode 100644 index 000000000..aab00a41c --- /dev/null +++ b/MMDevice/unittest/DeviceUtils-Tests.cpp @@ -0,0 +1,26 @@ +#include + +#include "DeviceUtils.h" + +#include +#include + +TEST_CASE("CopyLimitedString truncates", "[CopyLimitedString]") +{ + std::vector dest(MM::MaxStrLength, '.'); + const std::string src(MM::MaxStrLength + 10, '*'); + CHECK_FALSE(CDeviceUtils::CopyLimitedString(dest.data(), src.c_str())); + CHECK(dest[0] == '*'); + CHECK(dest[MM::MaxStrLength - 2] == '*'); + CHECK(dest[MM::MaxStrLength - 1] == '\0'); +} + +TEST_CASE("CopyLimitedString max untruncated len", "[CopyLimitedString]") +{ + std::vector dest(MM::MaxStrLength, '.'); + const std::string src(MM::MaxStrLength - 1, '*'); + CHECK(CDeviceUtils::CopyLimitedString(dest.data(), src.c_str())); + CHECK(dest[0] == '*'); + CHECK(dest[MM::MaxStrLength - 2] == '*'); + CHECK(dest[MM::MaxStrLength - 1] == '\0'); +} diff --git a/MMDevice/unittest/meson.build b/MMDevice/unittest/meson.build index dbca08cff..7692bc02a 100644 --- a/MMDevice/unittest/meson.build +++ b/MMDevice/unittest/meson.build @@ -10,6 +10,7 @@ catch2_with_main_dep = dependency( ) mmdevice_test_sources = files( + 'DeviceUtils-Tests.cpp', 'FloatPropertyTruncation-Tests.cpp', 'MMTime-Tests.cpp', )