/////////////////////////////////////////////////////////////////////////////// // FILE: XenethCamera.cpp // PROJECT: Micro-Manager // SUBSYSTEM: DeviceAdapters //----------------------------------------------------------------------------- // DESCRIPTION: A camera implementation that uses the Xeneth SDK to // access a Xenics camera. // // AUTHOR: Lutz Rossa // // COPYRIGHT: 2020 Helmholtz-Zentrum Berlin für Materialien und Energie GmbH // LICENSE: Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #define NOMINMAX #include "XenethCamera.h" #include "../../MMDevice/ModuleInterface.h" #include #include const char* g_szCameraName = "XenethCamera"; const char* g_szExposurePropName = "exposure-property"; const int DEVICE_CAN_NOT_SET_AFTER_INITIALIZE = 100; const int DEVICE_XENETH_INTERNAL = 101; const int DEVICE_XENETH_UNKNOWN = 102; const int DEVICE_XENETH_BUG = 103; const int DEVICE_XENETH_NOINIT = 104; const int DEVICE_XENETH_LOGICLOADFAILED = 105; const int DEVICE_XENETH_INTERFACE_ERROR = 106; const int DEVICE_XENETH_OUT_OF_RANGE = 107; const int DEVICE_XENETH_NOT_SUPPORTED = 108; const int DEVICE_XENETH_NOT_FOUND = 109; const int DEVICE_XENETH_FILTER_DONE = 110; const int DEVICE_XENETH_NO_FRAME = 111; const int DEVICE_XENETH_SAVE_ERROR = 112; const int DEVICE_XENETH_MISMATCHED = 113; const int DEVICE_XENETH_BUSY = 114; const int DEVICE_XENETH_INVALID_HANDLE = 115; const int DEVICE_XENETH_TIMEOUT = 116; const int DEVICE_XENETH_FRAMEGRABBER = 117; const int DEVICE_XENETH_NO_CONVERSION = 118; const int DEVICE_XENETH_FILTER_SKIP_FRAME = 119; const int DEVICE_XENETH_WRONG_VERSION = 120; const int DEVICE_XENETH_PACKET_ERROR = 121; const int DEVICE_XENETH_WRONG_FORMAT = 122; const int DEVICE_XENETH_WRONG_SIZE = 123; static bool g_bLogThread(false); static std::list g_aszLogList; static std::string g_szLogFile #ifdef _DEBUG ("D:\\Lutz-development-only\\mm.log") #endif ; static std::recursive_mutex g_hLogMutex; static std::thread* g_pLogThread(nullptr); static void LogThreadFunc(); static std::string LogFormat(const char* szFormat, ...); static void LogLine(const char* szFileInfo, std::string && szLine); static void LogDone(); static void LogInit(); #define STRINGIFY2(x) #x #define STRINGIFY(x) STRINGIFY2(x) #define LOG(x) do { LogInit(); std::string szLine(LogFormat x); LogLine(__FILE__ "(" STRINGIFY(__LINE__) "): ", std::move(szLine)); } while (0) // Exported MMDevice API MODULE_API void InitializeModuleData() { LOG(("InitializeModuleData()\n")); RegisterDevice(g_szCameraName, MM::CameraDevice, "Xeneth/Xenics camera"); } MODULE_API MM::Device* CreateDevice(const char* deviceName) { LOG(("CreateDevice(\"%s\")\n", deviceName)); if (!deviceName) return 0; std::string deviceName_(deviceName); if (deviceName_ == g_szCameraName) { XenethCamera* s = new XenethCamera(); LOG(("CreateDevice(\"%s\") %p\n", deviceName, s)); return s; } LOG(("CreateDevice(\"%s\") nullptr\n", deviceName)); return 0; } MODULE_API void DeleteDevice(MM::Device* pDevice) { LOG(("DeleteDevice(%p)\n", pDevice)); if (pDevice) delete pDevice; } XenethCamera::XenethCamera() : m_bInitialized(false), m_bInitializing(false), m_byPixelSize(1), m_szCameraPath("cam://default"), m_lExposurePropertyIndex(-1), m_uRoiX(0), m_uRoiY(0), m_uRoiW(0), m_uRoiH(0) { LOG(("XenethCamera::XenethCamera(%p)\n", this)); // create a property to select the camera XenethCreateProperty("device path", m_szCameraPath.c_str(), MM::String, false, &XenethCamera::OnCameraPath, true); // create a property to select the calibration file name XenethCreateProperty("calibration file", m_szCalibrationFile.c_str(), MM::String, false, &XenethCamera::OnCalibrationFile, true); // create a property to select the calibration file name XenethCreateProperty("log file", g_szLogFile.c_str(), MM::String, false, &XenethCamera::OnLogFile, true); // create a property to select the index of the exposure-time property XenethCreateProperty(g_szExposurePropName, std::to_string(m_lExposurePropertyIndex).c_str(), MM::Integer, false, &XenethCamera::OnExposureProperty, true); // register error message SetErrorText(DEVICE_CAN_NOT_SET_AFTER_INITIALIZE, "cannot set property after Initialize before Shutdown"); SetErrorText(DEVICE_XENETH_INTERNAL, "Xeneth SDK internal error (I_DIRTY)"); SetErrorText(DEVICE_XENETH_UNKNOWN, "Xeneth SDK unknown error"); SetErrorText(DEVICE_XENETH_BUG, "Xeneth SDK generic error (E_BUG)"); SetErrorText(DEVICE_XENETH_NOINIT, "Xeneth SDK not successfully initialised (E_NOINIT)"); SetErrorText(DEVICE_XENETH_LOGICLOADFAILED, "Xeneth SDK invalid logic file (E_LOGICLOADFAILED)"); SetErrorText(DEVICE_XENETH_INTERFACE_ERROR, "Xeneth SDK command interface failure (E_INTERFACE_ERROR)"); SetErrorText(DEVICE_XENETH_OUT_OF_RANGE, "Xeneth SDK provided value is incapable of being produced by the hardware (E_OUT_OF_RANGE)"); SetErrorText(DEVICE_XENETH_NOT_SUPPORTED, "Xeneth SDK functionality not supported by this camera (E_NOT_SUPPORTED)"); SetErrorText(DEVICE_XENETH_NOT_FOUND, "Xeneth SDK file/data not found (E_NOT_FOUND)"); SetErrorText(DEVICE_XENETH_FILTER_DONE, "Xeneth SDK filter has finished processing, and will be removed (E_FILTER_DONE)"); SetErrorText(DEVICE_XENETH_NO_FRAME, "Xeneth SDK a frame was requested by calling GetFrame, but none was available (E_NO_FRAME)"); SetErrorText(DEVICE_XENETH_SAVE_ERROR, "Xeneth SDK couldn't save to file (E_SAVE_ERROR)"); SetErrorText(DEVICE_XENETH_MISMATCHED, "Xeneth SDK buffer size mismatch (E_MISMATCHED)"); SetErrorText(DEVICE_XENETH_BUSY, "Xeneth SDK the API can not read a temperature because the camera is busy (E_BUSY)"); SetErrorText(DEVICE_XENETH_INVALID_HANDLE, "Xeneth SDK an unknown handle was passed to the C API (E_INVALID_HANDLE)"); SetErrorText(DEVICE_XENETH_TIMEOUT, "Xeneth SDK operation timed out (E_TIMEOUT)"); SetErrorText(DEVICE_XENETH_FRAMEGRABBER, "Xeneth SDK frame grabber error (E_FRAMEGRABBER)"); SetErrorText(DEVICE_XENETH_NO_CONVERSION, "Xeneth SDK GetFrame could not convert the image data to the requested format (E_NO_CONVERSION)"); SetErrorText(DEVICE_XENETH_FILTER_SKIP_FRAME, "Xeneth SDK filter indicates the frame should be skipped (E_FILTER_SKIP_FRAME)"); SetErrorText(DEVICE_XENETH_WRONG_VERSION, "Xeneth SDK version mismatch (E_WRONG_VERSION)"); SetErrorText(DEVICE_XENETH_PACKET_ERROR, "Xeneth SDK the requested frame cannot be provided because at least one packet has been lost (E_PACKET_ERROR)"); SetErrorText(DEVICE_XENETH_WRONG_FORMAT, "Xeneth SDK the emissivity map you tried to set should be a 16 bit grayscale png (E_WRONG_FORMAT)"); SetErrorText(DEVICE_XENETH_WRONG_SIZE, "Xeneth SDK the emissivity map you tried to set has the wrong dimensions W/H (E_WRONG_SIZE)"); InitializeDefaultErrorMessages(); } XenethCamera::~XenethCamera() { LOG(("XenethCamera::~XenethCamera(%p) m_bInitialized=%d\n", this, m_bInitialized)); if (m_bInitialized) Shutdown(); } int XenethCamera::XenethCreateProperty(const char* szName, const char* szValue, MM::PropertyType eType, bool bReadOnly, int(XenethCamera::* pFunction)(MM::PropertyBase* pProp, MM::ActionType eAct), bool bIsPreInitProperty) { CPropertyAction* pAction(nullptr); if (pFunction) { pAction = new CPropertyAction(this, pFunction); if (!pAction) return DEVICE_OUT_OF_MEMORY; } int iResult(CreateProperty(szName, szValue, eType, bReadOnly, pAction, bIsPreInitProperty)); if (iResult != DEVICE_OK && pAction) { delete pAction; pAction = nullptr; } LOG(("XenethCamera::XenethCreateProperty(%p szName=\"%s\" szValue=\"%s\" eType=%d bReadOnly=%d pFunction=%p bIsPreInitProperty=%d) iResult=%d pAction=%p\n", this, szName, szValue, eType, bReadOnly, pFunction, bIsPreInitProperty, iResult, pAction)); return iResult; } int XenethCamera::Initialize() { int iResult(DEVICE_OK); LOG(("XenethCamera::Initialize(%p) m_bInitialized=%d\n", this, m_bInitialized)); if (m_bInitialized) return DEVICE_OK; // open camera and try to load calibration file m_hCamera = XC_OpenCamera(m_szCameraPath.c_str()); if (!m_szCalibrationFile.empty()) { iResult = ConvertXenethResult(XC_LoadCalibration(m_hCamera, m_szCalibrationFile.c_str(), XLC_StartSoftwareCorrection)); if (iResult != DEVICE_OK) { LOG(("XenethCamera::Initialize(%p) iResult=%d\n", this, iResult)); XC_CloseCamera(m_hCamera); return iResult; } } // force gray scale XC_SetColourMode(m_hCamera, ColourMode_16); std::string szCameraIdentifier("unknown"); std::string szCameraSerial("unknown"); std::string szIntegrationTime; m_bInitializing = true; int iPropertyCount(XC_GetPropertyCount(m_hCamera)); m_aProperties.clear(); m_aProperties.reserve(iPropertyCount); for (int i = 0; i < iPropertyCount; ++i) { XenethProperty p; ErrCode iError; #define XC_GET(name, arg2, onerr) \ p.m_sz##name.resize(65536); \ iError = XC_GetProperty##name(m_hCamera, arg2, &p.m_sz##name[0], static_cast(p.m_sz##name.size())); \ if (iError == I_OK) { \ p.m_sz##name[p.m_sz##name.size() - 1] = '\0'; \ p.m_sz##name.resize(strlen(p.m_sz##name.c_str())); \ } else onerr; XC_GET(Name, i, continue); iError = XC_GetPropertyType(m_hCamera, p.m_szName.c_str(), &p.m_iType); if (iError != I_OK) continue; // ignore unsupported properties XC_GET(Category, p.m_szName.c_str(), p.m_szCategory.clear()); XC_GET(Unit, p.m_szName.c_str(), p.m_szUnit.clear()); XC_GET(Range, p.m_szName.c_str(), p.m_szRange.clear()); if (!(p.m_iType & XType_Base_RW)) { LOG(("XenethCamera::Initialize(%p) camera prop; name=\"%s\" type=%d cat=\"%s\" unit=\"%s\" range=\"%s\"\n", this, p.m_szName.c_str(), p.m_iType, p.m_szCategory.c_str(), p.m_szUnit.c_str(), p.m_szRange.c_str())); continue; // ignore unreadable properties } std::string szMMPropname("Xeneth "); szMMPropname.append(p.m_szName); XC_GET(Value, p.m_szName.c_str(), ); LOG(("XenethCamera::Initialize(%p) camera prop; name=\"%s\" type=%d val=\"%s\" cat=\"%s\" unit=\"%s\" range=\"%s\"\n", this, p.m_szName.c_str(), p.m_iType, p.m_szValue.c_str(), p.m_szCategory.c_str(), p.m_szUnit.c_str(), p.m_szRange.c_str())); MM::PropertyType eType(MM::String); switch (p.m_iType & XType_Base_Mask) { case XType_Base_Number: // A number (floating) eType = MM::Float; break; case XType_Base_Enum: // An enumerated type (a choice) case XType_Base_Bool: // Boolean (true/false/1/0) eType = MM::Integer; break; default: break; } XenethCreateProperty(szMMPropname.c_str(), p.m_szValue.c_str(), eType, !(p.m_iType & XType_Base_Writeable), &XenethCamera::OnCameraProperty, false); std::string::size_type iIndex; if ((p.m_iType & XType_Base_Mask) == XType_Base_Enum && !p.m_szRange.empty() && p.m_szRange.find(',') != std::string::npos) { // split enumeration "val1,val2,val3, ..." std::string szRange(p.m_szRange); long lValue(0); szRange.append(","); while (!szRange.empty()) { iIndex = szRange.find(','); if (iIndex == std::string::npos) iIndex = szRange.size(); AddAllowedValue(p.m_szName.c_str(), szRange.substr(0, iIndex).c_str(), lValue++); szRange.erase(0, iIndex + 1); } SetPropertyLimits(p.m_szName.c_str(), 0, lValue - 1); } else if ((p.m_iType & XType_Base_Mask) == XType_Base_Number && !p.m_szRange.empty() && (iIndex = p.m_szRange.find('>')) != std::string::npos) { // get range "min>max" std::string szLeft(p.m_szRange.substr(0, iIndex)); std::string szRight(p.m_szRange.substr(iIndex + 1)); char* pEnd(const_cast(szLeft.c_str())); double dMin(strtod(pEnd, &pEnd)); while (pEnd && *pEnd && std::isspace(*pEnd)) ++pEnd; if (pEnd && !*pEnd) { pEnd = const_cast(szRight.c_str()); double dMax(strtod(pEnd, &pEnd)); while (pEnd && *pEnd && std::isspace(*pEnd)) ++pEnd; if (pEnd && !*pEnd && dMin < dMax) SetPropertyLimits(p.m_szName.c_str(), dMin, dMax); } } #undef XC_GET do { std::string szName(p.m_szName); std::transform(szName.begin(), szName.end(), szName.begin(), std::tolower); for (iIndex = 0; iIndex < szName.size(); ++iIndex) if (!std::isalnum(szName[iIndex])) szName.erase(iIndex--, 1); if (!szName.compare("campid")) szCameraIdentifier = p.m_szValue; else if (!szName.compare("camser")) szCameraSerial = p.m_szValue; else if (szIntegrationTime.empty()) { // find first property which name is like to "IntegrationTime" or "Exposure-Time" if (!szName.compare("integrationtime") || !szName.compare("exposure") || !szName.compare("exposuretime")) szIntegrationTime = p.m_szName; } } while (0); m_aProperties.push_back(p); } m_bInitialized = true; m_bInitializing = false; CreateStringProperty(MM::g_Keyword_Name, "Xeneth Camera", true); // Name CreateStringProperty(MM::g_Keyword_Description, "Xeneth/Xenics Camera", true); // Description CreateStringProperty(MM::g_Keyword_CameraName, szCameraSerial.c_str(), true); // CameraName CreateStringProperty(MM::g_Keyword_CameraID, szCameraIdentifier.c_str(), true); // CameraID // binning XenethCreateProperty(MM::g_Keyword_Binning, "1", MM::Integer, true, &XenethCamera::OnBinning, false); SetPropertyLimits(MM::g_Keyword_Binning, 1, 1); m_byPixelSize = static_cast(XC_GetFrameSize(m_hCamera) / XC_GetWidth(m_hCamera) / XC_GetHeight(m_hCamera)); if (m_lExposurePropertyIndex < 0 || m_lExposurePropertyIndex >= static_cast(m_aProperties.size())) { // property for exposure time m_lExposurePropertyIndex = -1; if (m_aProperties.size() > 0) { for (long i = 0; i < static_cast(m_aProperties.size()); ++i) { if (!m_aProperties[i].m_szName.compare(szIntegrationTime)) { m_lExposurePropertyIndex = i; break; } } for (long i = 0; i < static_cast(m_aProperties.size()); ++i) AddAllowedValue(g_szExposurePropName, m_aProperties[i].m_szName.c_str(), i); SetPropertyLimits(g_szExposurePropName, 0, static_cast(m_aProperties.size() - 1)); } SetProperty(g_szExposurePropName, std::to_string(m_lExposurePropertyIndex).c_str()); } LOG(("XenethCamera::Initialize(%p) m_lExposurePropertyIndex=%d\n", this, static_cast(m_lExposurePropertyIndex))); ClearROI(); return DEVICE_OK; } int XenethCamera::Shutdown() { LOG(("XenethCamera::Shutdown(%p) m_bInitialized=%d\n", this, m_bInitialized)); if (m_bInitialized) { if (XC_IsCapturing(m_hCamera)) { int iResult(ConvertXenethResult(XC_StopCapture(m_hCamera))); LOG(("XenethCamera::Shutdown(%p) stopped camera, iResult=%d\n", this, iResult)); } XC_CloseCamera(m_hCamera); m_bInitialized = false; } return DEVICE_OK; } void XenethCamera::GetName(char * name) const { CDeviceUtils::CopyLimitedString(name, g_szCameraName); } long XenethCamera::GetImageBufferSize() const { return XC_GetFrameSize(m_hCamera); } unsigned XenethCamera::GetBitDepth() const { return XC_GetBitSize(m_hCamera); } int XenethCamera::GetBinning() const { return 1; } int XenethCamera::SetBinning(int) { return DEVICE_OK; } void XenethCamera::SetExposure(double exposure) { LOG(("XenethCamera::SetExposure(%p exposure=%g m_lExposurePropertyIndex=%d)\n", this, exposure, static_cast(m_lExposurePropertyIndex))); if (std::isfinite(exposure) && m_lExposurePropertyIndex >= 0 && m_lExposurePropertyIndex < static_cast(m_aProperties.size())) { XenethProperty* p(&m_aProperties[m_lExposurePropertyIndex]); p->m_szValue = std::to_string(exposure); switch (p->m_iType & XType_Base_Mask) { case XType_Base_Number: { double dMin(std::numeric_limits::quiet_NaN()), dMax(dMin); exposure = FromMilliseconds(exposure, p->m_szUnit.c_str()); if (XC_GetPropertyRangeF(m_hCamera, p->m_szName.c_str(), &dMin, &dMax) == I_OK && dMin < dMax) { if (exposure < dMin) exposure = dMin; else if (exposure > dMax) exposure = dMax; } XC_SetPropertyValueF(m_hCamera, p->m_szName.c_str(), exposure, nullptr); LOG(("XenethCamera::SetExposure(%p) exposure=%g\n", this, exposure)); break; } case XType_Base_Enum: { long lMin(std::numeric_limits::max()), lMax(std::numeric_limits::min()); if (XC_GetPropertyRangeL(m_hCamera, p->m_szName.c_str(), &lMin, &lMax) == I_OK && lMin < lMax) { if (exposure < lMin || exposure > lMax) break; } XC_SetPropertyValueL(m_hCamera, p->m_szName.c_str(), static_cast(exposure), nullptr); break; } default: XC_SetPropertyValue(m_hCamera, p->m_szName.c_str(), p->m_szValue.c_str(), nullptr); break; } } } double XenethCamera::GetExposure() const { if (m_lExposurePropertyIndex >= 0 && m_lExposurePropertyIndex < static_cast(m_aProperties.size())) { XenethProperty* p(const_cast(&m_aProperties[m_lExposurePropertyIndex])); switch (p->m_iType & XType_Base_Mask) { case XType_Base_Number: { double dValue(std::numeric_limits::quiet_NaN()); if (XC_GetPropertyValueF(m_hCamera, p->m_szName.c_str(), &dValue) == I_OK && std::isfinite(dValue)) { p->m_szValue = std::to_string(dValue); LOG(("XenethCamera::GetExposure(%p) GET \"%s\" %g -> \"%s\"\n", this, p->m_szName.c_str(), dValue, p->m_szValue.c_str())); } break; } case XType_Base_Enum: { long lValue(0); if (XC_GetPropertyValueL(m_hCamera, p->m_szName.c_str(), &lValue) == I_OK) { p->m_szValue = std::to_string(lValue); LOG(("XenethCamera::GetExposure(%p) GET \"%s\" %d -> \"%s\"\n", this, p->m_szName.c_str(), static_cast(lValue), p->m_szValue.c_str())); } break; } default: { std::string szValue; szValue.resize(65536); if (XC_GetPropertyValue(m_hCamera, p->m_szName.c_str(), &szValue[0], szValue.size()) == I_OK) { szValue[szValue.size() - 1] = '\0'; szValue.resize(strlen(szValue.c_str())); p->m_szValue = szValue; LOG(("XenethCamera::GetExposure(%p) GET \"%s\" \"%s\"\n", this, p->m_szName.c_str(), p->m_szValue.c_str())); } break; } } char* pEnd(const_cast(p->m_szValue.c_str())); double dValue(strtod(pEnd, &pEnd)); LOG(("XenethCamera::GetExposure(%p) value=\"%s\" unit=\"%s\" dValue=%g\n", this, p->m_szValue.c_str(), p->m_szUnit.c_str(), dValue)); if (pEnd && pEnd > p->m_szValue.c_str()) { dValue = ToMilliseconds(dValue, p->m_szUnit.c_str()); LOG(("XenethCamera::GetExposure(%p) m_lExposurePropertyIndex=%d -> %g\n", this, static_cast(m_lExposurePropertyIndex), dValue)); return dValue; } } LOG(("XenethCamera::GetExposure(%p) m_lExposurePropertyIndex=%d -> NaN\n", this, static_cast(m_lExposurePropertyIndex))); return std::numeric_limits::quiet_NaN(); } int XenethCamera::SetROI(unsigned uX, unsigned uY, unsigned uWidth, unsigned uHeight) { dword dwCamWidth (XC_GetWidth (m_hCamera)); dword dwCamHeight(XC_GetHeight(m_hCamera)); LOG(("XenethCamera::SetROI(%p x=%u y=%u w=%u h=%u) cam=%ux%u\n", this, uX, uY, uWidth, uHeight, dwCamWidth, dwCamHeight)); if (!dwCamWidth || dwCamWidth > 0x0FFFFFFUL || !dwCamHeight || dwCamHeight > 0x0FFFFFFUL) { LOG(("XenethCamera::SetROI(%p) DEVICE_NOT_CONNECTED\n", this)); return DEVICE_NOT_CONNECTED; } m_abyImage.resize(XC_GetFrameSize(m_hCamera)); if (!uX && !uY && !uWidth && !uHeight) { uWidth = dwCamWidth; uHeight = dwCamHeight; } else if ((uX + uWidth) > dwCamWidth || (uY + uHeight) > dwCamHeight) { LOG(("XenethCamera::SetROI(%p) DEVICE_INVALID_PROPERTY_VALUE\n", this)); return DEVICE_INVALID_PROPERTY_VALUE; } m_uRoiX = uX; m_uRoiY = uY; m_uRoiW = uWidth; m_uRoiH = uHeight; if (m_uRoiW == dwCamWidth && m_uRoiH == dwCamHeight) m_abyROI.clear(); else m_abyROI.resize(static_cast(m_uRoiW) * static_cast(m_uRoiH) * static_cast(m_byPixelSize)); UpdateROI(); return DEVICE_OK; } int XenethCamera::GetROI(unsigned &uX, unsigned &uY, unsigned &uWidth, unsigned &uHeight) { uX = m_uRoiX; uY = m_uRoiY; uWidth = m_uRoiW; uHeight = m_uRoiH; return DEVICE_OK; } int XenethCamera::ClearROI() { return SetROI(0, 0, 0, 0); } void XenethCamera::UpdateROI() { dword dwCamWidth(XC_GetWidth(m_hCamera)); FrameType iFrameType(XC_GetFrameType(m_hCamera)); size_t iSrc(0), iDst(0), iX(0), iY(0); union { unsigned char* u8; unsigned short* u16; unsigned int* u32; } pSrc, pDst; if (m_abyROI.empty()) return; pSrc.u8 = m_abyImage.data(); pDst.u8 = m_abyROI.data(); for (iSrc = iDst = iX = iY = 0; iY < m_uRoiH; ++iSrc) { if (iX >= m_uRoiX && iX < (m_uRoiX + m_uRoiW) && iY >= m_uRoiY && iY < (m_uRoiY + m_uRoiH)) { switch (iFrameType) { case FT_NATIVE: switch (m_byPixelSize) { case 1: iFrameType = FT_8_BPP_GRAY; goto gray8bit; case 4: iFrameType = FT_32_BPP_GRAY; goto gray32bit; default: iFrameType = FT_16_BPP_GRAY; goto gray16bit; // Xeneth/Xenics default } case FT_8_BPP_GRAY: gray8bit: pDst.u8[iDst] = pSrc.u8[iSrc]; break; case FT_16_BPP_GRAY: gray16bit: pDst.u16[iDst] = pSrc.u16[iSrc]; break; case FT_32_BPP_GRAY: gray32bit: pDst.u32[iDst] = pSrc.u32[iSrc]; break; default: return; } ++iDst; } if ((++iX) >= dwCamWidth) { iX = 0; ++iY; } } } int XenethCamera::IsExposureSequenceable(bool & isSequenceable) const { isSequenceable = false; return DEVICE_OK; } const unsigned char * XenethCamera::GetImageBuffer() { return m_abyROI.empty() ? m_abyImage.data() : m_abyROI.data(); } unsigned XenethCamera::GetNumberOfComponents() const { switch (XC_GetFrameType(m_hCamera)) { case FT_32_BPP_RGB: case FT_32_BPP_BGR: return 3; case FT_32_BPP_RGBA: case FT_32_BPP_BGRA: return 4; default: return 1; } } const unsigned int* XenethCamera::GetImageBufferAsRGB32() { return nullptr; } unsigned XenethCamera::GetImageWidth() const { return m_uRoiW; } unsigned XenethCamera::GetImageHeight() const { return m_uRoiH; } unsigned XenethCamera::GetImageBytesPerPixel() const { return m_byPixelSize; } int XenethCamera::SnapImage() { static int g_iSnapCount(0); if (g_iSnapCount < 1000) LOG(("XenethCamera::SnapImage(%p) trigger\n", this)); m_abyImage.resize(XC_GetFrameSize(m_hCamera), 0); if (!XC_IsCapturing(m_hCamera)) { int iResult(ConvertXenethResult(XC_StartCapture(m_hCamera))); LOG(("XenethCamera::SnapImage(%p) started camera, iResult=%d\n", this, iResult)); } LOG(("XenethCamera::SnapImage(%p) frame count=%u\n", this, XC_GetFrameCount(m_hCamera))); int iResult(ConvertXenethResult(XC_GetFrame(m_hCamera, FT_NATIVE, XGF_Blocking, static_cast(m_abyImage.data()), static_cast(m_abyImage.size())))); if (g_iSnapCount < 1000) { ++g_iSnapCount; LOG(("XenethCamera::SnapImage(%p) size=%u iResult=%d\n", this, m_abyImage.size(), iResult)); } if (iResult == DEVICE_OK) UpdateROI(); return iResult; } int XenethCamera::StartSequenceAcquisition(long numImages, double interval_ms, bool stopOnOverflow) { int iResult(ConvertXenethResult(XC_StartCapture(m_hCamera))); LOG(("XenethCamera::StartSequenceAcquisition(%p) started camera, iResult=%d\n", this, iResult)); if (iResult != DEVICE_OK) return iResult; return CCameraBase::StartSequenceAcquisition(numImages, interval_ms, stopOnOverflow); } int XenethCamera::StopSequenceAcquisition() { int iResult(ConvertXenethResult(XC_StopCapture(m_hCamera))); LOG(("XenethCamera::StopSequenceAcquisition(%p) stopped camera, iResult=%d\n", this, iResult)); if (iResult != DEVICE_OK) return iResult; return CCameraBase::StopSequenceAcquisition(); } void XenethCamera::OnThreadExiting() throw() { bool bCapturing(XC_IsCapturing(m_hCamera) != 0); LOG(("XenethCamera::OnThreadExiting(%p) capturing=%d\n", this, bCapturing)); if (bCapturing) { int iResult(ConvertXenethResult(XC_StopCapture(m_hCamera))); LOG(("XenethCamera::OnThreadExiting(%p) stopped camera, iResult=%d\n", this, iResult)); } CCameraBase::OnThreadExiting(); } int XenethCamera::OnCameraPath(MM::PropertyBase* pProp, MM::ActionType eAct) { switch (eAct) { case MM::BeforeGet: LOG(("XenethCamera::OnCameraPath(%p) GET \"%s\"\n", this, m_szCameraPath.c_str())); pProp->Set(m_szCameraPath.c_str()); break; case MM::AfterSet: if (m_bInitialized) { LOG(("XenethCamera::OnCameraPath(%p) SET already initialized\n", this)); return DEVICE_CAN_NOT_SET_AFTER_INITIALIZE; } pProp->Get(m_szCameraPath); LOG(("XenethCamera::OnCameraPath(%p) SET \"%s\"\n", this, m_szCameraPath.c_str())); break; default: LOG(("XenethCamera::OnCameraPath(%p) eAct=%d\n", this, eAct)); break; } return DEVICE_OK; } int XenethCamera::OnCalibrationFile(MM::PropertyBase* pProp, MM::ActionType eAct) { int iResult(DEVICE_OK); switch (eAct) { case MM::BeforeGet: LOG(("XenethCamera::OnCalibrationFile(%p) GET \"%s\"\n", this, m_szCalibrationFile.c_str())); pProp->Set(m_szCalibrationFile.c_str()); break; case MM::AfterSet: { std::string szFile; pProp->Get(szFile); if (m_bInitialized) // load calibration file only, if initialized iResult = ConvertXenethResult(XC_LoadCalibration(m_hCamera, szFile.c_str(), XLC_StartSoftwareCorrection)); if (iResult == DEVICE_OK) { m_szCalibrationFile = szFile; LOG(("XenethCamera::OnCalibrationFile(%p) SET \"%s\"\n", this, m_szCalibrationFile.c_str())); } else LOG(("XenethCamera::OnCalibrationFile(%p) SET failed %d\n", this, iResult)); break; } default: LOG(("XenethCamera::OnCalibrationFile(%p) eAct=%d\n", this, eAct)); break; } return DEVICE_OK; } int XenethCamera::OnLogFile(MM::PropertyBase* pProp, MM::ActionType eAct) { switch (eAct) { case MM::BeforeGet: LOG(("XenethCamera::OnLogFile(%p) GET \"%s\"\n", this, g_szLogFile.c_str())); pProp->Set(g_szLogFile.c_str()); break; case MM::AfterSet: pProp->Get(g_szLogFile); LOG(("XenethCamera::OnLogFile(%p) SET \"%s\"\n", this, g_szLogFile.c_str())); break; default: LOG(("XenethCamera::OnLogFile(%p) eAct=%d\n", this, eAct)); break; } return DEVICE_OK; } int XenethCamera::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) { pProp->Set(1L); return DEVICE_OK; } return DEVICE_ERR; } int XenethCamera::OnExposureProperty(MM::PropertyBase* pProp, MM::ActionType eAct) { switch (eAct) { case MM::BeforeGet: LOG(("XenethCamera::OnExposureProperty(%p) GET m_lExposurePropertyIndex=%d\n", this, static_cast(m_lExposurePropertyIndex))); pProp->Set(m_lExposurePropertyIndex); break; case MM::AfterSet: { long lValue(-1); pProp->Get(lValue); if (m_bInitialized && (lValue < 0 || lValue >= static_cast(m_aProperties.size()))) lValue = -1; m_lExposurePropertyIndex = lValue; LOG(("XenethCamera::OnExposureProperty(%p) SET m_lExposurePropertyIndex=%d\n", this, static_cast(m_lExposurePropertyIndex))); break; } default: LOG(("XenethCamera::OnExposureProperty(%p) eAct=%d\n", this, eAct)); break; } return DEVICE_OK; } int XenethCamera::OnCameraProperty(MM::PropertyBase* pProp, MM::ActionType eAct) { int iResult(DEVICE_OK); XenethProperty* pXenethProp(nullptr); std::string szPropname(pProp->GetName()); if (!szPropname.substr(0, 7).compare("Xeneth ")) { szPropname.erase(0, 7); for (std::vector::iterator it = m_aProperties.begin(); it != m_aProperties.end(); ++it) { if (!szPropname.compare(it->m_szName)) { pXenethProp = it.operator->(); break; } } } if (!pXenethProp) { LOG(("XenethCamera::OnCameraProperty(%p) invalid property \"%s\"\n", this, pProp->GetName().c_str())); return DEVICE_INVALID_PROPERTY; } switch (eAct) { case MM::BeforeGet: LOG(("XenethCamera::OnCameraProperty(%p) GET \"%s\" = \"%s\"\n", this, pXenethProp->m_szName.c_str(), pXenethProp->m_szValue.c_str())); pProp->Set(pXenethProp->m_szValue.c_str()); break; case MM::AfterSet: { std::string szValue; pProp->Get(szValue); if (!m_bInitializing) { if (!(pXenethProp->m_iType & XType_Base_Writeable)) { iResult = DEVICE_CAN_NOT_SET_PROPERTY; break; } switch (pXenethProp->m_iType & XType_Base_Mask) { case XType_Base_Number: { char* pEnd(const_cast(szValue.c_str())); if (!pEnd || !*pEnd) // no string { iResult = DEVICE_CAN_NOT_SET_PROPERTY; break; } double dValue(strtod(pEnd, &pEnd)); while (pEnd && (*pEnd == ' ' || *pEnd == '\t')) ++pEnd; if (!pEnd || *pEnd) goto try_string; iResult = ConvertXenethResult(XC_SetPropertyValueF(m_hCamera, pXenethProp->m_szName.c_str(), dValue, nullptr)); break; } case XType_Base_Enum: case XType_Base_Bool: { char* pEnd(const_cast(szValue.c_str())); if (!pEnd || !*pEnd) // no string { iResult = DEVICE_CAN_NOT_SET_PROPERTY; break; } long lValue(strtol(pEnd, &pEnd, 10)); while (pEnd && (*pEnd == ' ' || *pEnd == '\t')) ++pEnd; if (!pEnd || *pEnd) goto try_string; iResult = ConvertXenethResult(XC_SetPropertyValueL(m_hCamera, pXenethProp->m_szName.c_str(), lValue, nullptr)); break; } default: try_string: iResult = ConvertXenethResult(XC_SetPropertyValue(m_hCamera, pXenethProp->m_szName.c_str(), szValue.c_str(), nullptr)); break; } } if (iResult == DEVICE_OK) { pXenethProp->m_szValue = szValue; LOG(("XenethCamera::OnCameraProperty(%p) SET \"%s\" = \"%s\"\n", this, pXenethProp->m_szName.c_str(), pXenethProp->m_szValue.c_str())); } else LOG(("XenethCamera::OnCameraProperty(%p) SET \"%s\" failed %d\n", this, pXenethProp->m_szName.c_str(), iResult)); break; } default: LOG(("XenethCamera::OnCameraProperty(%p) eAct=%d\n", this, eAct)); break; } return DEVICE_OK; } double XenethCamera::FromMilliseconds(double dValue, const char* szTargetUnit) { double dOldValue(dValue); if (szTargetUnit[0] == 'n' && szTargetUnit[1] == 's' && szTargetUnit[2] == '\0') dValue *= 1.0E+06; else if (szTargetUnit[0] == 'm' && szTargetUnit[1] == 's' && szTargetUnit[2] == '\0') dValue *= 1.0E+00; else if (szTargetUnit[0] == 's' && szTargetUnit[1] == '\0') dValue /= 1.0E+03; else if (szTargetUnit[0] == 'm' && szTargetUnit[1] == '\0') dValue /= 60.0E+03; else if (szTargetUnit[0] == 'h' && szTargetUnit[1] == '\0') dValue /= 3600.0E+03; else /*if (szTargetUnit[0] == 'u' && szTargetUnit[1] == 's' && szTargetUnit[2] == '\0')*/ dValue *= 1.0E+03; // default: ms -> us LOG(("XenethCamera::FromMilliseconds(%g \"%s\")=%g\n", dOldValue, szTargetUnit, dValue)); return dValue; } double XenethCamera::ToMilliseconds(double dValue, const char* szSourceUnit) { double dOldValue(dValue); if (szSourceUnit[0] == 'n' && szSourceUnit[1] == 's' && szSourceUnit[2] == '\0') dValue /= 1.0E+06; else if (szSourceUnit[0] == 'm' && szSourceUnit[1] == 's' && szSourceUnit[2] == '\0') dValue /= 1.0E+00; else if (szSourceUnit[0] == 's' && szSourceUnit[1] == '\0') dValue *= 1.0E+03; else if (szSourceUnit[0] == 'm' && szSourceUnit[1] == '\0') dValue *= 60.0E+03; else if (szSourceUnit[0] == 'h' && szSourceUnit[1] == '\0') dValue *= 3600.0E+03; else /*if (szSourceUnit[0] == 'u' && szSourceUnit[1] == 's' && szSourceUnit[2] == '\0')*/ dValue /= 1.0E+03; // default: us -> ms LOG(("XenethCamera::ToMilliseconds(%g \"%s\")=%g\n", dOldValue, szSourceUnit, dValue)); return dValue; } int XenethCamera::ConvertXenethResult(ErrCode code) { switch (code) { case I_OK: return DEVICE_OK; case I_DIRTY: return DEVICE_XENETH_INTERNAL; case E_BUG: return DEVICE_XENETH_BUG; case E_NOINIT: return DEVICE_XENETH_NOINIT; case E_LOGICLOADFAILED: return DEVICE_XENETH_LOGICLOADFAILED; case E_INTERFACE_ERROR: return DEVICE_XENETH_INTERFACE_ERROR; case E_OUT_OF_RANGE: return DEVICE_XENETH_OUT_OF_RANGE; case E_NOT_SUPPORTED: return DEVICE_XENETH_NOT_SUPPORTED; case E_NOT_FOUND: return DEVICE_XENETH_NOT_FOUND; case E_FILTER_DONE: return DEVICE_XENETH_FILTER_DONE; case E_NO_FRAME: return DEVICE_XENETH_NO_FRAME; case E_SAVE_ERROR: return DEVICE_XENETH_SAVE_ERROR; case E_MISMATCHED: return DEVICE_XENETH_MISMATCHED; case E_BUSY: return DEVICE_XENETH_BUSY; case E_INVALID_HANDLE: return DEVICE_XENETH_INVALID_HANDLE; case E_TIMEOUT: return DEVICE_XENETH_TIMEOUT; case E_FRAMEGRABBER: return DEVICE_XENETH_FRAMEGRABBER; case E_NO_CONVERSION: return DEVICE_XENETH_NO_CONVERSION; case E_FILTER_SKIP_FRAME: return DEVICE_XENETH_FILTER_SKIP_FRAME; case E_WRONG_VERSION: return DEVICE_XENETH_WRONG_VERSION; case E_PACKET_ERROR: return DEVICE_XENETH_PACKET_ERROR; case E_WRONG_FORMAT: return DEVICE_XENETH_WRONG_FORMAT; case E_WRONG_SIZE: return DEVICE_XENETH_WRONG_SIZE; default: return DEVICE_XENETH_UNKNOWN; } } static void LogThreadFunc() { g_bLogThread = true; while (g_bLogThread) { std::list aszLogList; std::string szLogFile; g_hLogMutex.lock(); std::swap(aszLogList, g_aszLogList); szLogFile = g_szLogFile; g_hLogMutex.unlock(); if (!aszLogList.empty() && !szLogFile.empty()) { FILE* pFile(fopen(szLogFile.c_str(), "at")); if (pFile) { while (!aszLogList.empty()) { std::string szLine(aszLogList.front()); fwrite(szLine.c_str(), 1, szLine.length(), pFile); aszLogList.pop_front(); } fflush(pFile); fclose(pFile); } } std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } static std::string LogFormat(const char* szFormat, ...) { std::string szResult; va_list args; va_start(args, szFormat); szResult.resize(65536); vsnprintf(&szResult[0], szResult.size(), szFormat, args); szResult[szResult.size() - 1] = '\0'; szResult.resize(strlen(szResult.c_str())); return szResult; } static void LogLine(const char* szFileInfo, std::string&& szLine) { std::string szTmp; std::time_t tNow(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); std::tm now_tm; localtime_s(&now_tm, &tNow); // time szTmp.resize(32); strftime(&szTmp[0], szTmp.size(), "%Y%m%d-%H%M%S ", &now_tm); szTmp[szTmp.size() - 1] = '\0'; szTmp.resize(strlen(szTmp.c_str())); // file info if (!szFileInfo || !*szFileInfo) szFileInfo = "?"; else { // keep file name only for (const char* p(szFileInfo); *p != '\0'; ++p) { if ((p[0] == '/' || p[0] == '\\') && p[1]) szFileInfo = p + 1; } } szTmp.append(szFileInfo); // content szTmp.append(szLine); g_hLogMutex.lock(); g_aszLogList.emplace_back(szTmp); g_hLogMutex.unlock(); } static void LogDone() { std::lock_guard lk(g_hLogMutex); g_bLogThread = false; if (g_pLogThread) { g_pLogThread->join(); delete g_pLogThread; g_pLogThread = nullptr; } } static void LogInit() { std::lock_guard lk(g_hLogMutex); if (!g_pLogThread) { g_pLogThread = new std::thread(&LogThreadFunc); while (!g_bLogThread) std::this_thread::sleep_for(std::chrono::microseconds(50)); atexit(&LogDone); } }