From 5aed1d59558a1f5cf7e579a5dd61ca52aacfb243 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Thu, 2 Feb 2023 14:31:54 +0300 Subject: [PATCH] Add support of Basler cameras --- BASLER_cameras/CMakeLists.txt | 15 + BASLER_cameras/FindBASLER.cmake | 36 +++ BASLER_cameras/basler.c | 556 ++++++++++++++++++++++++++++++++ CMakeLists.txt | 31 +- FLI_cameras/flifunc.c | 6 +- HIKROBOT_cameras/mvsfunc.c | 2 + Readme.md | 36 ++- ccdfunc.c | 17 +- events.c | 4 +- 9 files changed, 677 insertions(+), 26 deletions(-) create mode 100644 BASLER_cameras/CMakeLists.txt create mode 100644 BASLER_cameras/FindBASLER.cmake create mode 100644 BASLER_cameras/basler.c diff --git a/BASLER_cameras/CMakeLists.txt b/BASLER_cameras/CMakeLists.txt new file mode 100644 index 0000000..8e02873 --- /dev/null +++ b/BASLER_cameras/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.20) +set(CCDLIB devbasler) + +SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +find_package(PkgConfig REQUIRED) +find_package(BASLER REQUIRED) +pkg_check_modules(${CCDLIB} REQUIRED usefull_macros) + +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SRC) +include_directories(${${CCDLIB}_INCLUDE_DIRS} ${BASLER_INCLUDE_DIRS} ..) +link_directories(${${CCDLIB}_LIBRARY_DIRS} ${BASLER_LIBRARY_DIRS}) + +add_library(${CCDLIB} SHARED ${SRC}) +target_link_libraries(${CCDLIB} ${${CCDLIB}_LIBRARIES} ${BASLER_LIBRARY} -fPIC) +install(TARGETS ${CCDLIB} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/BASLER_cameras/FindBASLER.cmake b/BASLER_cameras/FindBASLER.cmake new file mode 100644 index 0000000..5324401 --- /dev/null +++ b/BASLER_cameras/FindBASLER.cmake @@ -0,0 +1,36 @@ +# - Try to find libpylonc.so +# Once done this will define +# +# BASLER_FOUND - system has lib +# BASLER_INCLUDE_DIR - include directory +# BASLER_LIBRARIES - Link these to use lib + +# Copyright (c) 2021, Edward V. Emelianov +# +# Redistribution and use is allowed according to the terms of the GPLv2/GPLv3. + +include(GNUInstallDirs) + +find_path(BASLER_INCLUDE_DIR pylonc/PylonC.h + PATHS /usr/pylon/include /opt/pylon/include /usr/local/pylon/include /usr/include /usr/local/include /opt/include /opt/local/include +) +find_path(BASLER_LIBRARY_DIR libpylonc.so + PATHS /usr/pylon/lib /opt/pylon/lib /usr/local/pylon/lib /lib /lib64 /usr/lib /usr/lib64 /opt/lib /opt/lib64 /usr/local/lib /usr/local/lib64 +) +find_library(BASLER_LIBRARY NAMES pylonc + PATHS /opt/pylon/lib /usr/pylon/lib /usr/local/pylon/lib /lib /lib64 /usr/lib /usr/lib64 /opt/lib /opt/lib64 /usr/local/lib /usr/local/lib64 +) + +find_package_handle_standard_args(BASLER DEFAULT_MSG BASLER_INCLUDE_DIR BASLER_LIBRARY BASLER_LIBRARY_DIR) + +if(BASLER_FOUND) + set(BASLER_INCLUDE_DIRS ${BASLER_INCLUDE_DIR}) + set(BASLER_LIBRARIES ${BASLER_LIBRARY}) + set(BASLER_LIBRARY_DIRS ${BASLER_LIBRARY_DIR}) +# message("BASLER include dir = ${BASLER_INCLUDE_DIRS}") +# message("BASLER lib = ${BASLER_LIBRARIES}") +# message("BASLER libdir = ${BASLER_LIBRARY_DIRS}") + mark_as_advanced(BASLER_INCLUDE_DIRS BASLER_LIBRARIES BASLER_LIBRARY_DIRS) +else() + message("BASLER not found") +endif(BASLER_FOUND) diff --git a/BASLER_cameras/basler.c b/BASLER_cameras/basler.c new file mode 100644 index 0000000..3adb76a --- /dev/null +++ b/BASLER_cameras/basler.c @@ -0,0 +1,556 @@ +/* + * This file is part of the CCD_Capture project. + * Copyright 2023 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "basestructs.h" +#include "omp.h" + +extern Camera camera; + +static PYLON_DEVICE_HANDLE hDev; +static int isopened = FALSE, is16bit = FALSE; +static char camname[BUFSIZ] = {0}; +static size_t payloadsize = 0; // size of imgBuf +static unsigned char *imgBuf = NULL; +static uint32_t expostime = 0.; // current exposition time (in microseconds) +static PYLON_DEVICECALLBACK_HANDLE hCb; +static int curhbin = 1, curvbin = 1; + +#if 0 +static void *handle = NULL; +static char camname[BUFSIZ] = {0}; +//static long cam_err, tmpl; +static capture_status capStatus = CAPTURE_NO; +static int curhbin = 1, curvbin = 1; +static double starttime = 0.; // time when exposure started +static float exptime = 0.; // exposition time (in seconds) +static uint8_t *pdata = NULL; +static int pdatasz = 0; + +static struct{ + float maxgain; + float mingain; + float maxbright; + float minbright; + float minexp; + float maxexp; + int maxbin; +} extrvalues = {0}; // extremal values +#endif + +typedef struct{ + int64_t min; + int64_t max; + int64_t incr; + int64_t val; +} int64_values; + +typedef struct{ + double min; + double max; + double val; +} float_values; + +static char* describeError(GENAPIC_RESULT reserr){ + static char buf[1024]; + char* errMsg, *bptr = buf; + size_t length, l = 1023; + GenApiGetLastErrorMessage(NULL, &length); + errMsg = MALLOC(char, length); + GenApiGetLastErrorMessage(errMsg, &length); + size_t ll = snprintf(bptr, l, "%s (%d); ", errMsg, (int)reserr); + if(ll > 0){l -= ll; bptr += ll;} + FREE(errMsg); + GenApiGetLastErrorDetail(NULL, &length); + errMsg = MALLOC(char, length); + GenApiGetLastErrorDetail(errMsg, &length); + snprintf(bptr, l, "%s", errMsg); + FREE(errMsg); + return buf; +} + +#define PYLONFN(fn, ...) do{register GENAPIC_RESULT reserr; if(GENAPI_E_OK != (reserr=fn(__VA_ARGS__))){ \ + WARNX(#fn "(): %s", describeError(reserr)); return FALSE;}}while(0) + +static void disconnect(){ + FNAME(); + if(!isopened) return; + FREE(imgBuf); + PylonDeviceDeregisterRemovalCallback(hDev, hCb); + PylonDeviceClose(hDev); + PylonDestroyDevice(hDev); + PylonTerminate(); + isopened = FALSE; +} + +/** + * @brief chkNode - get node & check it for read/write + * @param phNode (io) - pointer to node + * @param nodeType - type of node + * @param wr - ==TRUE if need to check for writeable + * @return TRUE if node found & checked + */ +static int chkNode(NODE_HANDLE *phNode, const char *featureName, EGenApiNodeType nodeType, int wr){ + if(!isopened || !phNode || !featureName) return FALSE; + NODEMAP_HANDLE hNodeMap; + EGenApiNodeType nt; + _Bool bv; + PYLONFN(PylonDeviceGetNodeMap, hDev, &hNodeMap); + PYLONFN(GenApiNodeMapGetNode, hNodeMap, featureName, phNode); + if(*phNode == GENAPIC_INVALID_HANDLE) return FALSE; + PYLONFN(GenApiNodeGetType, *phNode, &nt); + if(nodeType != nt) return FALSE; + PYLONFN(GenApiNodeIsReadable, *phNode, &bv); + if(!bv) return FALSE; // not readable + if(wr){ + PYLONFN(GenApiNodeIsWritable, *phNode, &bv); + if(!bv) return FALSE; // not writeable + } + return TRUE; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + +// getters of different types of data +static int getBoolean(const char *featureName, _Bool *val){ + if(!isopened || !featureName) return FALSE; + NODE_HANDLE hNode; + if(!chkNode(&hNode, featureName, BooleanNode, FALSE)) return FALSE; + if(!val) return TRUE; + PYLONFN(GenApiBooleanGetValue, hNode, val); + //DBG("Get boolean: %s = %s", featureName, val ? "true" : "false"); + return TRUE; +} +static int getInt(char *featureName, int64_values *val){ + if(!isopened || !featureName) return FALSE; + NODE_HANDLE hNode; + if(!chkNode(&hNode, featureName, IntegerNode, FALSE)) return FALSE; + if(!val) return TRUE; + PYLONFN(GenApiIntegerGetMin, hNode, &val->min); + PYLONFN(GenApiIntegerGetMax, hNode, &val->max); + PYLONFN(GenApiIntegerGetInc, hNode, &val->incr); + PYLONFN(GenApiIntegerGetValue, hNode, &val->val); + //DBG("Get integer %s = %ld: min = %ld, max = %ld, incr = %ld", featureName, val->val, val->min, val->max, val->incr); + return TRUE; +} +static int getFloat(char *featureName, float_values *val){ + if(!isopened || !featureName) return FALSE; + NODE_HANDLE hNode; + if(!chkNode(&hNode, featureName, FloatNode, FALSE)) return FALSE; + if(!val) return TRUE; + PYLONFN(GenApiFloatGetMin, hNode, &val->min); + PYLONFN(GenApiFloatGetMax, hNode, &val->max); + PYLONFN(GenApiFloatGetValue, hNode, &val->val); + //DBG("Get float %s = %g: min = %g, max = %g", featureName, val->val, val->min, val->max); + return TRUE; +} + +// setters of different types of data +static int setBoolean(char *featureName, _Bool val){ + if(!isopened || !featureName) return FALSE; + NODE_HANDLE hNode; + if(!chkNode(&hNode, featureName, BooleanNode, TRUE)) return FALSE; + PYLONFN(GenApiBooleanSetValue, hNode, val); + return TRUE; +} +static int setInt(char *featureName, int64_t val){ + if(!isopened || !featureName) return FALSE; + NODE_HANDLE hNode; + if(!chkNode(&hNode, featureName, IntegerNode, TRUE)) return FALSE; + PYLONFN(GenApiIntegerSetValue, hNode, val); + return TRUE; +} +static int setFloat(char *featureName, float val){ + if(!isopened || !featureName) return FALSE; + NODE_HANDLE hNode; + if(!chkNode(&hNode, featureName, FloatNode, TRUE)) return FALSE; + PYLONFN(GenApiFloatSetValue, hNode, val); + return TRUE; +} +#pragma GCC diagnostic pop + +static void disableauto(){ + if(!isopened) return; + const char *features[] = {"EnumEntry_TriggerSelector_AcquisitionStart", + "EnumEntry_TriggerSelector_FrameBurstStart", + "EnumEntry_TriggerSelector_FrameStart"}; + const char *triggers[] = {"AcquisitionStart", "FrameBurstStart", "FrameStart"}; + for(int i = 0; i < 3; ++i){ + if(PylonDeviceFeatureIsAvailable(hDev, features[i])){ + PylonDeviceFeatureFromString(hDev, "TriggerSelector", triggers[i]); + PylonDeviceFeatureFromString(hDev, "TriggerMode", "Off"); + } + } + PylonDeviceFeatureFromString(hDev, "GainAuto", "Off"); + PylonDeviceFeatureFromString(hDev, "ExposureAuto", "Off"); + PylonDeviceFeatureFromString(hDev, "ExposureMode", "Timed"); + PylonDeviceFeatureFromString(hDev, "SequencerMode", "Off"); +} + +static void GENAPIC_CC removalCallbackFunction(_U_ PYLON_DEVICE_HANDLE hDevice){ + disconnect(); +} + +static int connect(){ + FNAME(); + size_t numDevices; + disconnect(); + PylonInitialize(); + PYLONFN(PylonEnumerateDevices, &numDevices); + if(!numDevices){ + WARNX("No cameras found"); + return FALSE; + } + camera.Ndevices = numDevices; + return TRUE; +} + +static int getbin(int *binh, int *binv){ + if(!PylonDeviceFeatureIsAvailable(hDev, "BinningVertical") || + !PylonDeviceFeatureIsAvailable(hDev, "BinningHorizontal")) return FALSE; + int64_values v; + if(!getInt("BinningVertical", &v)){ + DBG("Can't get Vbin"); + return FALSE; + } + curvbin = v.val; + if(!getInt("BinningHorizontal", &v)){ + DBG("Can't get Hbin"); + return FALSE; + } + curhbin = v.val; + DBG("binning: %d x %d", curhbin, curvbin); + if(binv) *binv = curvbin; + if(binh) *binh = curhbin; + return TRUE; +} + +static int getgeom(){ + FNAME(); + if(!isopened) return FALSE; + int64_values i; + if(!getInt("Width", &i)) return FALSE; + camera.field.w = i.val; + camera.array.w = i.max; + if(!getInt("Height", &i)) return FALSE; + camera.field.h = i.val; + camera.array.h = i.max; + if(!getInt("OffsetX", &i)) return FALSE; + camera.field.xoff = i.val; + camera.array.xoff = i.val; + camera.array.w -= i.val; + if(!getInt("OffsetY", &i)) return FALSE; + camera.field.yoff = i.val; + camera.array.yoff = i.val; + camera.array.h -= i.val; + camera.geometry = camera.field; + return TRUE; +} + +static int geometrylimits(frameformat *max, frameformat *step){ + FNAME(); + if(!isopened || !max || !step) return FALSE; + int64_values i; + if(!getInt("Width", &i)) return FALSE; + max->w = i.max; step->w = i.incr; + if(!getInt("Height", &i)) return FALSE; + max->h = i.max; step->h = i.incr; + if(!getInt("OffsetX", &i)) return FALSE; + max->xoff = i.max; step->xoff = i.incr; + if(!getInt("OffsetY", &i)) return FALSE; + max->yoff = i.max; step->yoff = i.incr; + return TRUE; +} + +static int setdevno(int N){ + if(N > camera.Ndevices - 1) return FALSE; + PYLONFN(PylonCreateDeviceByIndex, 0, &hDev); + isopened = TRUE; + PYLONFN(PylonDeviceOpen, hDev, PYLONC_ACCESS_MODE_CONTROL | PYLONC_ACCESS_MODE_STREAM | PYLONC_ACCESS_MODE_EXCLUSIVE); + disableauto(); + PYLONFN(PylonDeviceFeatureFromString, hDev, "CameraOperationMode", "LongExposure"); + PYLONFN(PylonDeviceFeatureFromString, hDev, "UserSetSelector", "HighGain"); // set high gain selector + PYLONFN(PylonDeviceFeatureFromString, hDev, "AcquisitionMode", "SingleFrame"); + PYLONFN(PylonDeviceExecuteCommandFeature, hDev, "UserSetLoad"); // load high gain mode + if(PylonDeviceFeatureIsReadable(hDev, "DeviceModelName")){ + size_t l = BUFSIZ-1; + PYLONFN(PylonDeviceFeatureToString, hDev, "DeviceModelName", camname, &l); + DBG("Using camera %s\n", camname); + }else strcpy(camname, "Unknown camera"); + if(!getbin(NULL, NULL)) WARNX("Can't get current binning"); + if(!getgeom()) WARNX("Can't get current frame format"); + + PylonDeviceRegisterRemovalCallback(hDev, removalCallbackFunction, &hCb); + PYLON_STREAMGRABBER_HANDLE hGrabber; + PYLONFN(PylonDeviceGetStreamGrabber, hDev, 0, &hGrabber); + PYLONFN(PylonStreamGrabberOpen, hGrabber); + PYLONFN(PylonStreamGrabberGetPayloadSize, hDev, hGrabber, &payloadsize); + DBG("payload sz %zd", payloadsize); + PylonStreamGrabberClose(hGrabber); + FREE(imgBuf); + imgBuf = MALLOC(unsigned char, payloadsize); + return TRUE; +} + +static int setbitdepth(int i){ + if(i == 0){ // 8 bit + if(!PylonDeviceFeatureIsAvailable( hDev, "EnumEntry_PixelFormat_Mono8" )) return FALSE; + PYLONFN(PylonDeviceFeatureFromString, hDev, "PixelFormat", "Mono8"); + is16bit = FALSE; + DBG("8 bit"); + }else{ // 16 bit + if(!PylonDeviceFeatureIsAvailable( hDev, "EnumEntry_PixelFormat_Mono16" )) return FALSE; + PYLONFN(PylonDeviceFeatureFromString, hDev, "PixelFormat", "Mono16"); + is16bit = TRUE; + DBG("16 bit"); + } + PYLON_STREAMGRABBER_HANDLE hGrabber; + PYLONFN(PylonDeviceGetStreamGrabber, hDev, 0, &hGrabber); + PYLONFN(PylonStreamGrabberOpen, hGrabber); + PYLONFN(PylonStreamGrabberGetPayloadSize, hDev, hGrabber, &payloadsize); + DBG("payload sz %zd", payloadsize); + PylonStreamGrabberClose(hGrabber); + FREE(imgBuf); + imgBuf = MALLOC(unsigned char, payloadsize); + return TRUE; +} + +// stub function: the capture process is blocking +static int pollcapt(capture_status *st, float *remain){ + if(st) *st = CAPTURE_READY; + if(remain) *remain = 0.f; + return TRUE; +} + +static int capture(IMG *ima){ + FNAME(); + if(!ima || !ima->data || !imgBuf || !isopened) return FALSE; + static int toohot = FALSE; + float_values f; + if(!getFloat("DeviceTemperature", &f)) WARNX("Can't get temperature"); + else{ + DBG("Temperature: %.1f", f.val); + if(f.val > 80.){ + WARNX("Device too hot"); + toohot = TRUE; + }else if(toohot && f.val < 75.){ + DBG("Device temperature is normal"); + toohot = FALSE; + } + } + PylonGrabResult_t grabResult; + _Bool bufferReady; + GENAPIC_RESULT res = PylonDeviceGrabSingleFrame(hDev, 0, imgBuf, payloadsize, + &grabResult, &bufferReady, 500 + (uint32_t)expostime); + if(res != GENAPI_E_OK || !bufferReady){ + WARNX("res != GENAPI_E_OK || !bufferReady"); + return FALSE; + } + if(grabResult.Status != Grabbed){ + WARNX("grabResult.Status != Grabbed"); + return FALSE; + } + int width = grabResult.SizeX, height = grabResult.SizeY, stride = grabResult.SizeX + grabResult.PaddingX; + if(is16bit){ + int s2 = stride<<1, w2 = width<<1; + OMP_FOR() + for(int y = 0; y < height; ++y){ + uint16_t *Out = &ima->data[y*width]; + const uint8_t *In = &imgBuf[y*s2]; + memcpy(Out, In, w2); + } + }else{ + OMP_FOR() + for(int y = 0; y < height; ++y){ + uint16_t *Out = &ima->data[y*width]; + const uint8_t *In = &imgBuf[y*stride]; + for(int x = 0; x < width; ++x){ + *Out++ = *In++; + } + } + } + return TRUE; +} + +// Basler have no "brightness" parameter +static int setbrightness(_U_ float b){ + FNAME(); + return FALSE; +} + +static int setexp(float e){ + FNAME(); + if(!isopened) return FALSE; + e *= 1e6f; + if(!setFloat("ExposureTime", e)){ + LOGWARN("Can't set expose time %g", e); + WARNX("Can't set expose time %g", e); + return FALSE; + } + float_values f; + if(!getFloat("ExposureTime", &f)) return FALSE; + expostime = (uint32_t)(f.val); + DBG("EXPOSURE time: need %f, real %f", e, f.val); + return TRUE; +} + +static int setgain(float e){ + FNAME(); + if(!isopened) return FALSE; + if(!setFloat("Gain", e)){ + LOGWARN("Can't set gain %g", e); + WARNX("Can't set gain %g", e); + return FALSE; + } + DBG("GAIN -> %f", e); + return TRUE; +} + +static int changeformat(frameformat *fmt){ + FNAME(); + if(!isopened) return FALSE; + if(!getbin(NULL, NULL)){curhbin = 1; curvbin = 1;} + fmt->h /= curvbin; fmt->yoff /= curvbin; + fmt->w /= curhbin; fmt->xoff /= curhbin; + DBG("set geom %dx%d (off: %dx%d)", fmt->w, fmt->h, fmt->xoff, fmt->yoff); + setInt("Width", fmt->w); + setInt("Height", fmt->h); + setInt("OffsetX", fmt->xoff); + setInt("OffsetY", fmt->yoff); + int64_values i; + if(getInt("Width", &i)) camera.geometry.w = fmt->w = i.val * curhbin; + if(getInt("Height", &i)) camera.geometry.h = fmt->h = i.val * curvbin; + if(getInt("OffsetX", &i)) camera.geometry.xoff = fmt->xoff = i.val * curhbin; + if(getInt("OffsetY", &i)) camera.geometry.yoff = fmt->yoff = i.val * curvbin; + return TRUE; +} + +static int getgain(float *g){ + FNAME(); + float_values v; + if(!getFloat("Gain", &v)) return FALSE; + if(g) *g = (float)v.val; + return TRUE; +} + +static int gainmax(float *g){ + FNAME(); + float_values v; + if(!getFloat("Gain", &v)) return FALSE; + if(g) *g = (float)v.max; + return TRUE; +} + +static int modelname(char *buf, int bufsz){ + strncpy(buf, camname, bufsz); + return TRUE; +} + +static int setbin(int binh, int binv){ + if(!PylonDeviceFeatureIsAvailable(hDev, "BinningVertical") || + !PylonDeviceFeatureIsAvailable(hDev, "BinningHorizontal")){ + DBG("No Vbin / Hbin"); + return FALSE; + } + if(setInt("BinningVertical", (int64_t)binv) && + setInt("BinningHorizontal", (int64_t)binh)) return TRUE; + DBG("Can't set v or h"); + return FALSE; +} + +static int gett(float *t){ + if(!t) return FALSE; + float_values fv; + if(!getFloat("DeviceTemperature", &fv)) return FALSE; + *t = fv.val; + return TRUE; +} + +static int setfanspd(_U_ fan_speed s){ + return FALSE; +} +static int shutter(_U_ shutter_op cmd){ + return FALSE; +} + +static int ffalse(_U_ float f){ return FALSE; } +static int fpfalse(_U_ float *f){ return FALSE; } +static int ifalse(_U_ int i){ return FALSE; } +static int vtrue(){ return TRUE; } +static int ipfalse(_U_ int *i){ return FALSE; } +static void vstub(){ return ;} + +#if 0 +// exported object +camera Basler = { + .disconnect = disconnect, + .connect = connect, + .capture = capture, + .setbrightness = setbrightness, + .setexp = setexp, + .setgain = setgain, + .setgeometry = changeformat, + .getgeomlimits = geometrylimits, + .getmaxgain = gainmax, +}; +#endif + +/* + * Global objects: camera, focuser and wheel + */ +Camera camera = { + .check = connect, + .close = disconnect, + .pollcapture = pollcapt, + .capture = capture, + .cancel = vstub, + .startexposition = vtrue, + // setters: + .setDevNo = setdevno, + .setbrightness = setbrightness, + .setexp = setexp, + .setgain = setgain, + .setT = ffalse, + .setbin = setbin, + .setnflushes = ifalse, + .shuttercmd = shutter, + .confio = ifalse, + .setio = ifalse, + .setframetype = ifalse, // set DARK or NORMAL: no shutter -> no darks + .setbitdepth = setbitdepth, + .setfastspeed = ifalse, + .setgeometry = changeformat, + .setfanspeed = setfanspd, + // getters: + .getbrightness = fpfalse, + .getModelName = modelname, + .getgain = getgain, + .getmaxgain = gainmax, + .getgeomlimits = geometrylimits, + .getTcold = fpfalse, + .getThot = fpfalse, + .getTbody = gett, + .getbin = getbin, + .getio = ipfalse, +}; diff --git a/CMakeLists.txt b/CMakeLists.txt index fe02e8c..97ae53f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.20) set(PROJ ccd_capture) set(MINOR_VERSION "0") -set(MID_VERSION "0") +set(MID_VERSION "1") set(MAJOR_VERSION "1") set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") @@ -9,6 +9,14 @@ project(${PROJ} VERSION ${VERSION} LANGUAGES C) message("VER: ${VERSION}") +# list of options +option(DEBUG "Compile in debug mode" OFF) +option(IMAGEVIEW "Build with imageview module" OFF) +option(ZWO "Add support of ZWO cameras" OFF) +option(FLI "Add support of FLI cameras" OFF) +option(BASLER "Add support of BASLER cameras" OFF) +option(HIKROBOT "Add support of HIKROBOT cameras" OFF) + # default flags set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -std=gnu99") @@ -36,14 +44,14 @@ set(CMAKE_COLOR_MAKEFILE ON) set(SOURCES main.c cmdlnopts.c ccdfunc.c socket.c server.c client.c) # cmake -DDEBUG=yes -> debugging -if(DEFINED DEBUG AND DEBUG STREQUAL "yes") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -ggdb -fno-builtin-strlen -Werror") +if(DEBUG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g3 -ggdb -fno-builtin-strlen -Werror") add_definitions(-DEBUG) set(CMAKE_BUILD_TYPE DEBUG) set(CMAKE_VERBOSE_MAKEFILE "ON") else() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -march=native -fdata-sections -ffunction-sections") - SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--gc-sections") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -march=native -fdata-sections -ffunction-sections -flto") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -flto") set(CMAKE_BUILD_TYPE RELEASE) endif() @@ -59,7 +67,7 @@ if(OPENMP_FOUND) add_definitions(-DOMP_FOUND) endif() -if(DEFINED IMAGEVIEW AND IMAGEVIEW STREQUAL "yes") +if(IMAGEVIEW) list(APPEND SOURCES events.c imageview.c) find_package(OpenGL REQUIRED) find_package(GLUT REQUIRED) @@ -72,17 +80,18 @@ endif() add_subdirectory(Dummy_cameras) # additional modules with CCD/CMOS support -if(DEFINED ZWO AND ZWO STREQUAL "yes") +if(ZWO) add_subdirectory(ZWO_cameras) endif() - -if(DEFINED FLI AND FLI STREQUAL "yes") +if(FLI) add_subdirectory(FLI_cameras) endif() - -if(DEFINED HIKROBOT AND HIKROBOT STREQUAL "yes") +if(HIKROBOT) add_subdirectory(HIKROBOT_cameras) endif() +if(BASLER) + add_subdirectory(BASLER_cameras) +endif() # directory should contain dir locale/ru for gettext translations diff --git a/FLI_cameras/flifunc.c b/FLI_cameras/flifunc.c index 21dc1c2..ccdb9ca 100644 --- a/FLI_cameras/flifunc.c +++ b/FLI_cameras/flifunc.c @@ -663,7 +663,7 @@ static int fli_fpfalse(_U_ float *f){ return FALSE; } /* * Global objects: camera, focuser and wheel */ -Camera camera = { +__attribute__ ((visibility("default"))) Camera camera = { .check = fli_findCCD, .close = fli_closecam, .pollcapture = fli_pollcapt, @@ -699,7 +699,7 @@ Camera camera = { .getio = fli_getio, }; -Focuser focuser = { +__attribute__ ((visibility("default"))) Focuser focuser = { .check = fli_findFocuser, .setDevNo = fli_setActiceFocuser, .close = fli_closefocuser, @@ -712,7 +712,7 @@ Focuser focuser = { .setAbsPos = fli_fgoto, }; -Wheel wheel = { +__attribute__ ((visibility("default"))) Wheel wheel = { .check = fli_findWheel, .setDevNo = fli_setActiceWheel, .close = fli_closewheel, diff --git a/HIKROBOT_cameras/mvsfunc.c b/HIKROBOT_cameras/mvsfunc.c index 3fe8381..1f6d63d 100644 --- a/HIKROBOT_cameras/mvsfunc.c +++ b/HIKROBOT_cameras/mvsfunc.c @@ -23,6 +23,7 @@ #include #include "basestructs.h" +#include "omp.h" #ifndef FLT_EPSILON #define FLT_EPSILON 1.19209290E-07F @@ -467,6 +468,7 @@ static int cam_capt(IMG *ima){ DBG("TRANSFORM 8 bit to 16"); bytes /= 2; uint8_t *ptr = (uint8_t*) pdata; + OMP_FOR() for(int i = 0; i < bytes; ++i){ ima->data[i] = (uint16_t) *ptr++; } diff --git a/Readme.md b/Readme.md index 5dd3767..989353f 100644 --- a/Readme.md +++ b/Readme.md @@ -1,7 +1,7 @@ CCD/CMOS imaging server ======================= -Supports FLI cameras/focusers/wheels and ZWO cameras. +Supports FLI cameras/focusers/wheels and cameras: ZWO, Basler, HikRobot. Allows to run as standalone application or imaging server/client. To restart server (e.g. if hardware was off) kill it with SIGUSR1 @@ -10,11 +10,12 @@ To restart server (e.g. if hardware was off) kill it with SIGUSR1 cmake options: -- `-DDEBUG=yes` -- make with a lot debugging info -- `-DIMAGEVIEW=yes -- compile with image viewer support (only for standalone) (OpenGL!!!) -- `-DZWO=yes` -- compile ZWO support plugin -- `-DFLI=yes` -- compile FLI support plugin - +- `-DDEBUG=ON` - make with a lot debugging info +- `-DIMAGEVIEW=ON` - compile with image viewer support (only for standalone) (OpenGL!!!) +- `-DBASLER=ON` - compile Basler support plugin +- `-DFLI=ON` - compile FLI support plugin +- `-DHIKROBOT=ON` - compile HikRobot support plugin +- `-DZWO=ON` - compile ZWO support plugin ``` @@ -79,3 +80,26 @@ Usage: ccd_capture [args] [output file prefix] --wheeldevno=arg filter wheel device number (if many: 0, 1, 2 etc) ``` +## Image viewer +In image view mode you can display menu by clicking of right mouse key or use shortcuts: + +- '0' - restore zoom; +- 'c' - capture new image in pause mode (works only with `-n` flag); +- 'e' - switch on/off histogram equalization; +- 'l' - flip image right-left; +- 'p' - pause or resume capturing (works only with `-n` flag); +- 'u' - flip image up-down; +- 'Z' - zoom+; +- 'z' - zoom-; +- 'Ctrl+r' - roll histogram conversion function (LOG, SQRT, POW, LINEAR); +- 'Ctrl+s' - save displayed image (works only if you pointed output file name or prefix); +- 'Ctrl+q' - exit. + +Mouse functions: + +- Left button - center selected point. +- Middle button - move image. +- Right button - show menu. +- Wheel up - scroll up, or scroll left (with Shift), or zoom+ (with Ctrl). +- Wheel down - scroll down, or scroll right (with Shift), or zoom- (with Ctrl). + diff --git a/ccdfunc.c b/ccdfunc.c index 0b443f2..b7b8cc0 100644 --- a/ccdfunc.c +++ b/ccdfunc.c @@ -674,6 +674,9 @@ void ccds(){ snprintf(buf, BUFSIZ, "(%d, %d)(%d, %d)", camera->field.xoff, camera->field.yoff, camera->field.xoff + camera->field.w, camera->field.yoff + camera->field.h); verbose(2, _("Field of view: %s"), buf); + snprintf(buf, BUFSIZ, "(%d, %d)(%d, %d)", camera->geometry.xoff, camera->geometry.yoff, + camera->geometry.xoff + camera->geometry.w, camera->geometry.yoff + camera->geometry.h); + verbose(2, _("Current format: %s"), buf); if(!isnan(GP->temperature)){ if(!camera->setT((float)GP->temperature)) WARNX(_("Can't set T to %g degC"), GP->temperature); @@ -724,16 +727,18 @@ void ccds(){ camera->cancel(); if(GP->hbin < 1) GP->hbin = 1; if(GP->vbin < 1) GP->vbin = 1; - if(!camera->setbin(GP->hbin, GP->vbin)) + if(!camera->setbin(GP->hbin, GP->vbin)){ WARNX(_("Can't set binning %dx%d"), GP->hbin, GP->vbin); + camera->getbin(&GP->hbin, &GP->vbin); + } if(GP->X0 < 0) GP->X0 = x0; // default values else if(GP->X0 > x1-1) GP->X0 = x1-1; if(GP->Y0 < 0) GP->Y0 = y0; else if(GP->Y0 > y1-1) GP->Y0 = y1-1; if(GP->X1 < GP->X0+1 || GP->X1 > x1) GP->X1 = x1; if(GP->Y1 < GP->Y0+1 || GP->Y1 > y1) GP->Y1 = y1; + DBG("x1/x0: %d/%d", GP->X1, GP->X0); frameformat fmt = {.w = GP->X1 - GP->X0, .h = GP->Y1 - GP->Y0, .xoff = GP->X0, .yoff = GP->Y0}; - int raw_width = fmt.w / GP->hbin, raw_height = fmt.h / GP->vbin; if(!camera->setgeometry(&fmt)) WARNX(_("Can't set given geometry")); verbose(3, "Geometry: off=%d/%d, wh=%d/%d", fmt.xoff, fmt.yoff, fmt.w, fmt.h); @@ -757,7 +762,7 @@ void ccds(){ if(!camera->getbin(&GP->hbin, &GP->vbin)) // GET binning should be AFTER setgeometry! WARNX(_("Can't get current binning")); verbose(2, "Binning: %d x %d", GP->hbin, GP->vbin); - + int raw_width = fmt.w / GP->hbin, raw_height = fmt.h / GP->vbin; uint16_t *img = MALLOC(uint16_t, raw_width * raw_height); DBG("\n\nAllocated image 2x%dx%d=%d", raw_width, raw_height, 2 * raw_width * raw_height); @@ -793,8 +798,12 @@ void ccds(){ break; } calculate_stat(&ima); - saveFITS(&ima, NULL); #ifdef IMAGEVIEW + if(!GP->showimage){ // don't save all FITS files in imagev view mode +#endif + saveFITS(&ima, NULL); +#ifdef IMAGEVIEW + } if(GP->showimage){ // display image if((mainwin = getWin())){ DBG("change image"); diff --git a/events.c b/events.c index 27b275c..4dc8746 100644 --- a/events.c +++ b/events.c @@ -84,7 +84,7 @@ static void processKeybrd(unsigned char key, int mod, _U_ int x, _U_ int y){ * Process keyboard */ void keyPressed(unsigned char key, int x, int y){ - int mod = glutGetModifiers(); + int mod = glutGetModifiers() & 7; //mod: GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL, GLUT_ACTIVE_ALT; result is their sum DBG("Key pressed. mod=%d, keycode=%d (%c), point=(%d,%d)\n", mod, key, key, x,y); processKeybrd(key, mod, x, y); @@ -101,7 +101,7 @@ static int movingwin = 0; // ==1 when user moves image by middle button void mousePressed(int key, int state, int x, int y){ // key: GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, GLUT_RIGHT_BUTTON // state: GLUT_UP, GLUT_DOWN - int mod = glutGetModifiers(); + int mod = glutGetModifiers() & 7; windowData *win = getWin(); if(!win) return; if(state == GLUT_DOWN){