From 45da3cd481a041bbbe7de1571f38c0a1a580bf96 Mon Sep 17 00:00:00 2001 From: Varvara Chiliaeva Date: Tue, 18 Feb 2025 15:47:02 +0100 Subject: [PATCH 1/5] create a module for each equipment --- spas/cam_ximea.py | 1094 ++++++++++++++++++++++++++++++++++++++++++ spas/dmd.py | 572 ++++++++++++++++++++++ spas/spectrograph.py | 12 + 3 files changed, 1678 insertions(+) create mode 100644 spas/cam_ximea.py create mode 100644 spas/dmd.py create mode 100644 spas/spectrograph.py diff --git a/spas/cam_ximea.py b/spas/cam_ximea.py new file mode 100644 index 0000000..c2334bc --- /dev/null +++ b/spas/cam_ximea.py @@ -0,0 +1,1094 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jan 28 10:10:13 2025 + +@author: chiliaeva + +init +piloting +disconnect +""" + +import json +from ximea import xiapi +from dataclasses import dataclass, InitVar, field +from typing import Optional, Union, List, Tuple, Optional +from dataclasses_json import dataclass_json +import numpy as np + + + +''' +EXAMPLE : + +cam = xiapi.Camera() + +cam.open_device_by_SN('41305651') # open by serial number + +print('Opening first camera...') +cam.open_device() + + +# Settings +cam.set_exposure(10000) + + +#create instance of Image to store image data and metadata +img = xiapi.Image() + + +#start data acquisition +print('Starting data acquisition...') +cam.start_acquisition() + + + + +for i in range(10): + # get data and pass them from camera to img + cam.get_image(img) + + # get raw data from camera + #for Python2.x function returns string + #for Python3.x function returns bytes + data_raw = img.get_image_data_raw() + + #transform data to list + data = list(data_raw) + + #print image data and metadata + print('Image number: ' + str(i)) + print('Image width (pixels): ' + str(img.width)) + print('Image height (pixels): ' + str(img.height)) + print('First 10 pixels: ' + str(data[:10])) + print('\n') + + + +#stop data acquisition +print('Stopping acquisition...') +cam.stop_acquisition() + +#stop communication +cam.close_device() + +print('Done.') +''' + +######################################################################################################################################################### +# Ximea version +########################################################################################################################################################## + + +def _init_CAM(): + """ + Initialize and connect to the Ximea camera. + + Returns: + CAM: a structure containing the parameters of the IDS camera + """ + + camPar = CAM(hCam = ueye.HIDS(0), + sInfo = ueye.SENSORINFO(), + cInfo = ueye.CAMINFO(), + nBitsPerPixel = ueye.INT(8), + m_nColorMode = ueye.INT(), + bytes_per_pixel = int( ueye.INT(8)/ 8), + rectAOI = ueye.IS_RECT(), + pcImageMemory = ueye.c_mem_p(), + MemID = ueye.int(), + pitch = ueye.INT(), + fps = float(), + gain = int(), + gainBoost = str(), + gamma = float(), + exposureTime = float(), + blackLevel = int(), + camActivated = bool(), + pixelClock = ueye.uint(), + bandwidth = float(), + Memory = bool(), + Exit = int(), + vidFormat = str(), + gate_period = int(), + trigger_mode = str(), + avi = ueye.int(), + punFileID = ueye.c_uint(), + timeout = int(), + time_array = [], + int_time_spect = float(), # unused ? + black_pattern_num = int(), # unused ? + insert_patterns = bool(), # unused ? + acq_mode = str(), # unused ? + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +############################################################################################################################################################## +# IDS version of the program +############################################################################################################################################################## + +@dataclass_json +@dataclass +class CAM: + """Class containing IDS camera configurations. + + Further information: https://en.ids-imaging.com/manuals/ids-software-suite/ueye-manual/4.95/en/c_programmierung.html. + + Attributes: + hCam (ueye.c_uint): Handle of the camera. + sInfo (ueye.SENSORINFO):sensor information : [SensorID [c_ushort] = 566; + strSensorName [c_char_Array_32] = b'UI388xCP-M'; + nColorMode [c_char] = b'\x01'; + nMaxWidth [c_uint] = 3088; + nMaxHeight [c_uint] = 2076; + bMasterGain [c_int] = 1; + bRGain [c_int] = 0; + bGGain [c_int] = 0; + bBGain [c_int] = 0; + bGlobShutter [c_int] = 0; + wPixelSize [c_ushort] = 240; + nUpperLeftBayerPixel [c_char] = b'\x00'; + Reserved]. + cInfo (ueye.BOARDINFO):Camera information: [SerNo [c_char_Array_12] = b'4103219888'; + ID [c_char_Array_20] = b'IDS GmbH'; + Version [c_char_Array_10] = b''; + Date [c_char_Array_12] = b'30.11.2017'; + Select [c_ubyte] = 1; + Type [c_ubyte] = 100; + Reserved [c_char_Array_8] = b'';] + nBitsPerPixel (ueye.c_int): number of bits per pixel (8 for monochrome, 24 for color). + m_nColorMode (ueye.c_int): color mode : Y8/RGB16/RGB24/REG32. + bytes_per_pixel (int): bytes_per_pixel = int(nBitsPerPixel / 8). + rectAOI (ueye.IS_RECT()): rectangle of the Area Of Interest (AOI): s32X [c_int] = 0; + s32Y [c_int] = 0; + s32Width [c_int] = 3088; + s32Height [c_int] = 2076; + pcImageMemory (ueye.c_mem_p()): memory allocation. + MemID (ueye.int()): memory identifier. + pitch (ueye.INT()): ???. + fps (float): set frame per second. + gain (int): Set gain between [0 - 100]. + gainBoost (str): Activate gain boosting ("ON") or deactivate ("OFF"). + gamma (float): Set Gamma between [1 - 2.5] to change the image contrast + exposureTime (float): Set the exposure time between [0.032 - 56.221] + blackLevel (int): Set the black level between [0 - 255] to set an offset in the image. It is adviced to put 5 for noise measurement + camActivated (bool) : need to to know if the camera is ready to acquire (1: yes, 0: No) + pixelClock (int) : the pixel clock, three values possible : [118, 237, 474] (MHz) + bandwidth (float) the bandwidth (in MByte/s) is an approximate value which is calculated based on the pixel clock + Memory (bool) : a boolean to know if the memory inside the camera is busy [1] or free [0] + Exit (int) : if Exit = 2 => excute is_ExitCamera function (disables the hCam camera handle and releases the memory) | if Exit = 0 => allow to init cam, after that, Exit = 1 + vidFormat (str) : save video in the format avi or bin (for binary) + gate_period (int) : a second TTL is sent by the DMD to trigg the camera, and based on the fisrt TTL to trigg the spectrometer. camera trigger period = gate_period*(spectrometer trigger period) + trigger_mode (str) : hard or soft + avi (ueye.int) : A pointer that returns the instance ID which is needed for calling the other uEye AVI functions + punFileID (ueye.c_int) : a pointer in which the instance ID is returned. This ID is needed for calling other functions. + timeout (int) : a time which stop the camera that waiting for a TTL + time_array (List[float]) : the time array saved after each frame received on the camera + int_time_spect (float) : is egal to the integration time of the spectrometer, it is need to know this value because of the rolling shutter of the monochrome IDS camera + black_pattern_num (int) : is number inside the image name of the black pattern (for the hyperspectral arm, or white pattern for the camera arm) to be inserted betweem the Hadamard patterns + insert_patterns (int) : 0 => no insertion / 1=> insert white patterns for the camera + acq_mode (str) : mode of the acquisition => 'video' or 'snapshot' mode + """ + if dll_pyueye_installed: + hCam: Optional[ueye.c_uint] = None + sInfo: Optional[ueye.SENSORINFO] = None + cInfo: Optional[ueye.BOARDINFO] = None + nBitsPerPixel: Optional[ueye.c_int] = None + m_nColorMode: Optional[ueye.c_int] = None + bytes_per_pixel: Optional[int] = None + rectAOI: Optional[ueye.IS_RECT] = None + pcImageMemory: Optional[ueye.c_mem_p] = None + MemID: Optional[ueye.c_int] = None + pitch: Optional[ueye.c_int] = None + fps: Optional[float] = None + gain: Optional[int] = None + gainBoost: Optional[str] = None + gamma: Optional[float] = None + exposureTime: Optional[float] = None + blackLevel: Optional[int] = None + camActivated : Optional[bool] = None + pixelClock : Optional[int] = None + bandwidth : Optional[float] = None + Memory : Optional[bool] = None + Exit : Optional[int] = None + vidFormat : Optional[str] = None + gate_period : Optional[int] = None + trigger_mode : Optional[str] = None + avi : Optional[ueye.int] = None + punFileID : Optional[ueye.c_int] = None + timeout : Optional[int] = None + time_array : Optional[Union[List[float], str]] = field(default=None, repr=False) + int_time_spect : Optional[float] = None + black_pattern_num : Optional[int] = None + insert_patterns : Optional[int] = None + acq_mode : Optional[str] = None + + class_description: str = 'IDS camera configuration' + + def undo_readable_class_CAM(self) -> None: + """Changes the time_array attribute from `str` to `List` of `int`.""" + + def to_float(str_arr): + arr = [] + for s in str_arr: + try: + num = float(s) + arr.append(num) + except ValueError: + pass + return arr + + if self.time_array: + self.time_array = ( + self.time_array.strip('[').strip(']').split(', ')) + self.time_array = to_float(self.time_array) + self.time_array = np.asarray(self.time_array) + + @staticmethod + def readable_class_CAM(cam_params_dict: dict) -> dict: + # pass + """Turns list of time_array into a string. + convert the c_type structure (sInfo, cInfo and rectAOI) into a nested dict + change the bytes type item into str + change the c_types item into their value + """ + + readable_cam_dict = {} + readable_cam_dict_temp = cam_params_dict#camPar.to_dict()# + inc = 0 + for item in readable_cam_dict_temp: + stri = str(type(readable_cam_dict_temp[item])) + # print('----- item : ' + item) + if item == 'sInfo' or item == 'cInfo' or item == 'rectAOI': + readable_cam_dict[item] = dict() + try: + for sub_item in readable_cam_dict_temp[item]._fields_: + new_item = item + '-' + sub_item[0] + try: + att = getattr(readable_cam_dict_temp[item], sub_item[0]).value + except: + att = getattr(readable_cam_dict_temp[item], sub_item[0]) + + if type(att) == bytes: + att = str(att) + + readable_cam_dict[item][sub_item[0]] = att + except: + try: + for sub_item in readable_cam_dict_temp[item]: + # print('----- sub_item : ' + sub_item) + new_item = item + '-' + sub_item + att = readable_cam_dict_temp[item][sub_item] + + if type(att) == bytes: + att = str(att) + + readable_cam_dict[item][sub_item] = att + except: + print('warning, impossible to read the subitem of readable_cam_dict_temp[item]') + + elif stri.find('pyueye') >=0: + try: + readable_cam_dict[item] = readable_cam_dict_temp[item].value + except: + readable_cam_dict[item] = readable_cam_dict_temp[item] + elif item == 'time_array': + readable_cam_dict[item] = str(readable_cam_dict_temp[item]) + else: + readable_cam_dict[item] = readable_cam_dict_temp[item] + + return readable_cam_dict + + +##################################################################################################################### + + + + +def _init_CAM(): + """ + Initialize and connect to the IDS camera. + + Returns: + CAM: a structure containing the parameters of the IDS camera + """ + camPar = CAM(hCam = ueye.HIDS(0), + sInfo = ueye.SENSORINFO(), + cInfo = ueye.CAMINFO(), + nBitsPerPixel = ueye.INT(8), + m_nColorMode = ueye.INT(), + bytes_per_pixel = int( ueye.INT(8)/ 8), + rectAOI = ueye.IS_RECT(), + pcImageMemory = ueye.c_mem_p(), + MemID = ueye.int(), + pitch = ueye.INT(), + fps = float(), + gain = int(), + gainBoost = str(), + gamma = float(), + exposureTime = float(), + blackLevel = int(), + camActivated = bool(), + pixelClock = ueye.uint(), + bandwidth = float(), + Memory = bool(), + Exit = int(), + vidFormat = str(), + gate_period = int(), + trigger_mode = str(), + avi = ueye.int(), + punFileID = ueye.c_uint(), + timeout = int(), + time_array = [], + int_time_spect = float(), # unused ? + black_pattern_num = int(), # unused ? + insert_patterns = bool(), # unused ? + acq_mode = str(), # unused ? + ) + + # # Camera Initialization --- + # print("START Initialization of the IDS camera") + ### Starts the driver and establishes the connection to the camera + nRet = ueye.is_InitCamera(camPar.hCam, None) + if nRet != ueye.IS_SUCCESS: + print("is_InitCamera ERROR") + + ### Reads out the data hard-coded in the non-volatile camera memory and writes it to the data structure that cInfo points to + nRet = ueye.is_GetCameraInfo(camPar.hCam, camPar.cInfo) + if nRet != ueye.IS_SUCCESS: + print("is_GetCameraInfo ERROR") + + ### You can query additional information about the sensor type used in the camera + nRet = ueye.is_GetSensorInfo(camPar.hCam, camPar.sInfo) + if nRet != ueye.IS_SUCCESS: + print("is_GetSensorInfo ERROR") + + ### set camera parameters to default values + nRet = ueye.is_ResetToDefault(camPar.hCam) + if nRet != ueye.IS_SUCCESS: + print("is_ResetToDefault ERROR") + + ### Set display mode to DIB + nRet = ueye.is_SetDisplayMode(camPar.hCam, ueye.IS_SET_DM_DIB) + + ### Set the right color mode + if int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_BAYER: + # setup the color depth to the current windows setting + ueye.is_GetColorDepth(camPar.hCam, camPar.nBitsPerPixel, camPar.m_nColorMode) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + # print("IS_COLORMODE_BAYER: ", ) + # print("\tm_nColorMode: \t\t", m_nColorMode) + # print("\tnBitsPerPixel: \t\t", nBitsPerPixel) + # print("\tbytes_per_pixel: \t\t", bytes_per_pixel) + # print() + + elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_CBYCRY: + # for color camera models use RGB32 mode + camPar.m_nColorMode = ueye.IS_CM_BGRA8_PACKED + camPar.nBitsPerPixel = ueye.INT(32) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + # print("IS_COLORMODE_CBYCRY: ", ) + # print("\tm_nColorMode: \t\t", m_nColorMode) + # print("\tnBitsPerPixel: \t\t", nBitsPerPixel) + # print("\tbytes_per_pixel: \t\t", bytes_per_pixel) + # print() + + elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_MONOCHROME: + # for color camera models use RGB32 mode + camPar.m_nColorMode = ueye.IS_CM_MONO8 + camPar.nBitsPerPixel = ueye.INT(8) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + # print("IS_COLORMODE_MONOCHROME: ", ) + # print("\tm_nColorMode: \t\t", m_nColorMode) + # print("\tnBitsPerPixel: \t\t", nBitsPerPixel) + # print("\tbytes_per_pixel: \t\t", bytes_per_pixel) + # print() + + else: + # for monochrome camera models use Y8 mode + camPar.m_nColorMode = ueye.IS_CM_MONO8 + camPar.nBitsPerPixel = ueye.INT(8) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + # print("else") + + ### Get the AOI (Area Of Interest) + sizeofrectAOI = ueye.c_uint(4*4) + nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_GET_AOI, camPar.rectAOI, sizeofrectAOI) + if nRet != ueye.IS_SUCCESS: + print("AOI getting ERROR") + + camPar.camActivated = 0 + + # Get current pixel clock + getpixelclock = ueye.UINT(0) + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, + ueye.sizeof(getpixelclock)) + + camPar.pixelClock = getpixelclock + # print('pixel clock = ' + str(getpixelclock) + ' MHz') + + # get the bandwidth (in MByte/s) + camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) + # print('Bandwidth = ' + str(camPar.bandwidth) + ' MB/s') + + + # width = rectAOI.s32Width + # height = rectAOI.s32Height + + # Prints out some information about the camera and the sensor + # print("Camera model:\t\t", sInfo.strSensorName.decode('utf-8')) + # print("Camera serial no.:\t", cInfo.SerNo.decode('utf-8')) + # print("Maximum image width:\t", width) + # print("Maximum image height:\t", height) + # print() + + # self.hCam = hCam + # self.sInfo = sInfo + # self.cInfo = cInfo + # self.nBitsPerPixel = nBitsPerPixel + # self.m_nColorMode = m_nColorMode + # self.bytes_per_pixel = bytes_per_pixel + # self.rectAOI = rectAOI + + camPar.Exit = 1 + + print('IDS camera connected') + + return camPar + + + + + +def captureVid(camPar): + """ + Allocate memory and begin video capture of the IDS camera + + Args: + camPar : a structure containing the parameters of the IDS camera. + + Returns: + camPar : a structure containing the parameters of the IDS camera. + """ + camPar = stopCapt_DeallocMem_ExitCam(camPar) + + if camPar.Exit == 0: + camPar = _init_CAM() + camPar.Exit = 1 + + + ### Set the AOI + sizeofrectAOI = ueye.c_uint(4*4) + nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_SET_AOI, camPar.rectAOI, sizeofrectAOI) + if nRet != ueye.IS_SUCCESS: + print("AOI setting ERROR") + + width = camPar.rectAOI.s32Width + height = camPar.rectAOI.s32Height + + ### Allocates an image memory for an image having its dimensions defined by width and height and its color depth defined by nBitsPerPixel + nRet = ueye.is_AllocImageMem(camPar.hCam, width, height, camPar.nBitsPerPixel, camPar.pcImageMemory, camPar.MemID) + if nRet != ueye.IS_SUCCESS: + print("is_AllocImageMem ERROR") + else: + # Makes the specified image memory the active memory + camPar.Memory = 1 + nRet = ueye.is_SetImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet != ueye.IS_SUCCESS: + print("is_SetImageMem ERROR") + else: + # Set the desired color mode + nRet = ueye.is_SetColorMode(camPar.hCam, camPar.m_nColorMode) + + + ### Activates the camera's live video mode (free run mode) + nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) + if nRet != ueye.IS_SUCCESS: + print("is_CaptureVideo ERROR") + + + ### Enables the queue mode for existing image memory sequences + nRet = ueye.is_InquireImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID, width, height, camPar.nBitsPerPixel, camPar.pitch) + if nRet != ueye.IS_SUCCESS: + print("is_InquireImageMem ERROR") + + camPar.camActivated = 1 + + return camPar + + + + +def setup_cam(camPar, pixelClock, fps, Gain, gain_boost, nGamma, ExposureTime, black_level): + """ + Set and read the camera parameters + + Args: + pixelClock = [118, 237 or 474] (MHz) + fps: fps boundary => [1 - No Value] sup limit depend of image size (216 fps for 768x544 pixels for example) + Gain: Gain boundary => [0 100] + gain_boost: 'ON' set "ON" to activate gain boost, "OFF" to deactivate + nGamma: Gamma boundary => [1 - 2.2] + ExposureTime: Exposure time (ms) boundarye => [0.032 - 56.221] + black_level: Black Level boundary => [0 255] + + returns: + CAM: a structure containing the parameters of the IDS camera + """ + # It is necessary to execute twice this code to take account the parameter modification + for i in range(2): + ############################### Set Pixel Clock ############################### + ### Get range of pixel clock, result : range = [118 474] MHz (Inc = 0) + getpixelclock = ueye.UINT(0) + newpixelclock = ueye.UINT(0) + newpixelclock.value = pixelClock + PixelClockRange = (ueye.int * 3)() + + # Get pixel clock range + nRet = ueye.is_PixelClock(camPar.hCam, ueye.IS_PIXELCLOCK_CMD_GET_RANGE, PixelClockRange, ueye.sizeof(PixelClockRange)) + if nRet == ueye.IS_SUCCESS: + nPixelClockMin = PixelClockRange[0] + nPixelClockMax = PixelClockRange[1] + nPixelClockInc = PixelClockRange[2] + + # Set pixel clock + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_SET, newpixelclock, + ueye.sizeof(newpixelclock)) + # Get current pixel clock + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, + ueye.sizeof(getpixelclock)) + + camPar.pixelClock = getpixelclock.value + if i == 1: + print(' pixel clock = ' + str(getpixelclock) + ' MHz') + if getpixelclock == 118: + if i == 1: + print('Pixel clcok blocked to 118 MHz, it is necessary to unplug the camera if not desired') + # get the bandwidth (in MByte/s) + camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) + if i == 1: + print(' Bandwidth = ' + str(camPar.bandwidth) + ' MB/s') + ############################### Set FrameRate ################################# + ### Read current FrameRate + dblFPS_init = ueye.c_double() + nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_init) + if nRet != ueye.IS_SUCCESS: + print("FrameRate getting ERROR") + else: + dblFPS_eff = dblFPS_init + if i == 1: + print(' current FPS = '+str(round(dblFPS_init.value*100)/100) + ' fps') + if fps < 1: + fps = 1 + if i == 1: + print('FPS exceed lower limit >= 1') + + dblFPS = ueye.c_double(fps) + if (dblFPS.value < dblFPS_init.value-0.01) | (dblFPS.value > dblFPS_init.value+0.01): + newFPS = ueye.c_double() + nRet = ueye.is_SetFrameRate(camPar.hCam, dblFPS, newFPS) + time.sleep(1) + if nRet != ueye.IS_SUCCESS: + print("FrameRate setting ERROR") + else: + if i == 1: + print(' new FPS = '+str(round(newFPS.value*100)/100) + ' fps') + ### Read again the effective FPS / depend of the image size, 17.7 fps is not possible with the entire image size (ie 2076x3088) + dblFPS_eff = ueye.c_double() + nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_eff) + if nRet != ueye.IS_SUCCESS: + print("FrameRate getting ERROR") + else: + if i == 1: + print(' effective FPS = '+str(round(dblFPS_eff.value*100)/100) + ' fps') + ############################### Set GAIN ###################################### + #### Maximum gain is depending of the sensor. Convertion gain code to gain to limit values from 0 to 100 + # gain_code = gain * slope + b + gain_max_code = 1450 + gain_min_code = 100 + gain_max = 100 + gain_min = 0 + slope = (gain_max_code-gain_min_code)/(gain_max-gain_min) + b = gain_min_code + #### Read gain setting + current_gain_code = ueye.c_int() + current_gain_code = ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_GET_MASTER_GAIN_FACTOR, current_gain_code) + current_gain = round((current_gain_code-b)/slope) + + if i == 1: + print(' current GAIN = '+str(current_gain)) + gain_eff = current_gain + + ### Set new gain value + gain = ueye.c_int(Gain) + if gain.value != current_gain: + if gain.value < 0: + gain = ueye.c_int(0) + if i == 1: + print('Gain exceed lower limit >= 0') + elif gain.value > 100: + gain = ueye.c_int(100) + if i == 1: + print('Gain exceed upper limit <= 100') + gain_code = ueye.c_int(round(slope*gain.value+b)) + + ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_SET_MASTER_GAIN_FACTOR, gain_code) + new_gain = round((gain_code-b)/slope) + + if i == 1: + print(' new GAIN = '+str(new_gain)) + gain_eff = new_gain + ############################### Set GAIN Boost ################################ + ### Read current state of the gain boost + current_gain_boost_bool = ueye.is_SetGainBoost(camPar.hCam, ueye.IS_GET_GAINBOOST) + if nRet != ueye.IS_SUCCESS: + print("Gain boost ERROR") + if current_gain_boost_bool == 0: + current_gain_boost = 'OFF' + elif current_gain_boost_bool == 1: + current_gain_boost = 'ON' + + if i == 1: + print('current Gain boost mode = ' + current_gain_boost) + + ### Set the state of the gain boost + if gain_boost != current_gain_boost: + if gain_boost == 'OFF': + nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_OFF) + print(' new Gain Boost : OFF') + + elif gain_boost == 'ON': + nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_ON) + print(' new Gain Boost : ON') + + if nRet != ueye.IS_SUCCESS: + print("Gain boost setting ERROR") + ############################### Set Gamma ##################################### + ### Check boundary of Gamma + if nGamma > 2.2: + nGamma = 2.2 + if i == 1: + print('Gamma exceed upper limit <= 2.2') + elif nGamma < 1: + nGamma = 1 + if i == 1: + print('Gamma exceed lower limit >= 1') + ### Read current Gamma + c_nGamma_init = ueye.c_void_p() + sizeOfnGamma = ueye.c_uint(4) + nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_GET, c_nGamma_init, sizeOfnGamma) + if nRet != ueye.IS_SUCCESS: + print("Gamma getting ERROR") + else: + if i == 1: + print(' current Gamma = ' + str(c_nGamma_init.value/100)) + ### Set Gamma + c_nGamma = ueye.c_void_p(round(nGamma*100)) # need to multiply by 100 [100 - 220] + if c_nGamma_init.value != c_nGamma.value: + nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_SET, c_nGamma, sizeOfnGamma) + if nRet != ueye.IS_SUCCESS: + print("Gamma setting ERROR") + else: + if i == 1: + print(' new Gamma = '+str(c_nGamma.value/100)) + ############################### Set Exposure time ############################# + ### Read current Exposure Time + getExposure = ueye.c_double() + sizeOfpParam = ueye.c_uint(8) + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE, getExposure, sizeOfpParam) + if nRet == ueye.IS_SUCCESS: + getExposure.value = round(getExposure.value*1000)/1000 + + if i == 1: + print(' current Exposure Time = ' + str(getExposure.value) + ' ms') + ### Get minimum Exposure Time + minExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MIN, minExposure, sizeOfpParam) + ### Get maximum Exposure Time + maxExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MAX, maxExposure, sizeOfpParam) + ### Get increment Exposure Time + incExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_INC, incExposure, sizeOfpParam) + ### Set new Exposure Time + setExposure = ueye.c_double(ExposureTime) + if setExposure.value > maxExposure.value: + setExposure.value = maxExposure.value + if i == 1: + print('Exposure Time exceed upper limit <= ' + str(maxExposure.value)) + elif setExposure.value < minExposure.value: + setExposure.value = minExposure.value + if i == 1: + print('Exposure Time exceed lower limit >= ' + str(minExposure.value)) + + if (setExposure.value < getExposure.value-incExposure.value/2) | (setExposure.value > getExposure.value+incExposure.value/2): + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_SET_EXPOSURE, setExposure, sizeOfpParam) + if nRet != ueye.IS_SUCCESS: + print("Exposure Time ERROR") + else: + if i == 1: + print(' new Exposure Time = ' + str(round(setExposure.value*1000)/1000) + ' ms') + ############################### Set Black Level ############################### + current_black_level_c = ueye.c_uint() + sizeOfBlack_level = ueye.c_uint(4) + ### Read current Black Level + nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_GET_OFFSET, current_black_level_c, sizeOfBlack_level) + if nRet != ueye.IS_SUCCESS: + print("Black Level getting ERROR") + else: + if i == 1: + print(' current Black Level = ' + str(current_black_level_c.value)) + + ### Set Black Level + if black_level > 255: + black_level = 255 + if i == 1: + print('Black Level exceed upper limit <= 255') + if black_level < 0: + black_level = 0 + if i == 1: + print('Black Level exceed lower limit >= 0') + + black_level_c = ueye.c_uint(black_level) + if black_level != current_black_level_c.value : + nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_SET_OFFSET, black_level_c, sizeOfBlack_level) + if nRet != ueye.IS_SUCCESS: + print("Black Level setting ERROR") + else: + if i == 1: + print(' new Black Level = ' + str(black_level_c.value)) + + + camPar.fps = round(dblFPS_eff.value*100)/100 + camPar.gain = gain_eff + camPar.gainBoost = gain_boost + camPar.gamma = c_nGamma.value/100 + camPar.exposureTime = round(setExposure.value*1000)/1000 + camPar.blackLevel = black_level_c.value + + return camPar + + + + + +def snapshot(camPar, pathIDSsnapshot, pathIDSsnapshot_overview): + """ + Snapshot of the IDS camera + + Args: + CAM: a structure containing the parameters of the IDS camera + """ + array = ueye.get_data(camPar.pcImageMemory, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel, camPar.pitch, copy=False) + + # ...reshape it in an numpy array... + frame = np.reshape(array,(camPar.rectAOI.s32Height.value, camPar.rectAOI.s32Width.value))#, camPar.bytes_per_pixel)) + + with pathIDSsnapshot.open('wb') as f: #('ab') as f: #(pathname, mode='w', encoding='utf-8') as f: #('ab') as f: + np.save(f,frame) + + maxi = np.amax(frame) + if maxi == 0: + maxi = 1 + im = Image.fromarray(frame*math.floor(255/maxi)) + im.save(pathIDSsnapshot_overview) + + maxi = np.amax(frame) + # print() + # print('frame max = ' + str(maxi)) + # print('frame min = ' + str(np.amin(frame))) + if maxi >= 255: + print('Saturation detected') + + plt.figure + plt.imshow(frame)#, cmap='gray', vmin=mini, vmax=maxi) + plt.colorbar(); + plt.show() + + + + + +def check_ueye(func, *args, exp=0, raise_exc=True, txt=None): + ret = func(*args) + if not txt: + txt = "{}: Expected {} but ret={}!".format(str(func), exp, ret) + if ret != exp: + if raise_exc: + raise RuntimeError(txt) + else: + logging.critical(txt) + + +def stopCapt_DeallocMem(camPar): + # Stop capture and deallocate camera memory if need to change AOI + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + print('video stop successful') + else: + print('problem to stop the video') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + print('deallocate memory successful') + else: + print('Problem to deallocate memory of the camera') + + return camPar + +def stopCapt_DeallocMem_ExitCam(camPar): + # Stop capture and deallocate camera memory if need to change AOI + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + print('video stop successful') + else: + print('problem to stop the video') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + print('deallocate memory successful') + else: + print('Problem to deallocate memory of the camera') + + if camPar.Exit == 2: + nRet = ueye.is_ExitCamera(camPar.hCam) + if nRet == ueye.IS_SUCCESS: + camPar.Exit = 0 + print('Camera disconnected') + else: + print('Problem to disconnect camera, need to restart spyder') + + return camPar + +class ImageBuffer: + pcImageMemory = None + MemID = None + width = None + height = None + nbitsPerPixel = None + +def imageQueue(camPar): + # Create Imagequeue --------------------------------------------------------- + # - allocate 3 ore more buffers depending on the framerate + # - initialize Imagequeue + # --------------------------------------------------------- + sleep(1) # is required (delay of 1s was not optimized!!) + buffers = [] + for y in range(10): + buffers.append(ImageBuffer()) + + for x in range(len(buffers)): + buffers[x].nbitsPerPixel = camPar.nBitsPerPixel # RAW8 + buffers[x].height = camPar.rectAOI.s32Height # sensorinfo.nMaxHeight + buffers[x].width = camPar.rectAOI.s32Width # sensorinfo.nMaxWidth + buffers[x].MemID = ueye.int(0) + buffers[x].pcImageMemory = ueye.c_mem_p() + check_ueye(ueye.is_AllocImageMem, camPar.hCam, buffers[x].width, buffers[x].height, buffers[x].nbitsPerPixel, + buffers[x].pcImageMemory, buffers[x].MemID) + check_ueye(ueye.is_AddToSequence, camPar.hCam, buffers[x].pcImageMemory, buffers[x].MemID) + + check_ueye(ueye.is_InitImageQueue, camPar.hCam, ueye.c_int(0)) + if camPar.trigger_mode == 'soft': + check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_SOFTWARE) + elif camPar.trigger_mode == 'hard': + check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_LO_HI) + +def prepareCam(camPar, metadata): + cam_path = metadata.output_directory + '\\' + metadata.experiment_name + '_video.' + camPar.vidFormat + strFileName = ueye.c_char_p(cam_path.encode('utf-8')) + + if camPar.vidFormat == 'avi': + # print('Video format : AVI') + camPar.avi = ueye.int() + nRet = ueye_tools.isavi_InitAVI(camPar.avi, camPar.hCam) + # print("isavi_InitAVI") + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_InitAVI ERROR") + + nRet = ueye_tools.isavi_SetImageSize(camPar.avi, camPar.m_nColorMode, camPar.rectAOI.s32Width , camPar.rectAOI.s32Height, 0, 0, 0) + nRet = ueye_tools.isavi_SetImageQuality(camPar.avi, 100) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_SetImageQuality ERROR") + + nRet = ueye_tools.isavi_OpenAVI(camPar.avi, strFileName) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_OpenAVI ERROR") + print('Error code = ' + str(nRet)) + print('Certainly, it is a problem with the file name, Avoid special character like "µ" or try to redcue its size') + + nRet = ueye_tools.isavi_SetFrameRate(camPar.avi, camPar.fps) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_SetFrameRate ERROR") + nRet = ueye_tools.isavi_StartAVI(camPar.avi) + # print("isavi_StartAVI") + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_StartAVI ERROR") + + + elif camPar.vidFormat == 'bin': + camPar.punFileID = ueye.c_uint() + nRet = ueye_tools.israw_InitFile(camPar.punFileID, ueye_tools.IS_FILE_ACCESS_MODE_WRITE) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("INIT RAW FILE ERROR") + + nRet = ueye_tools.israw_SetImageInfo(camPar.punFileID, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("SET IMAGE INFO ERROR") + + if nRet == ueye.IS_SUCCESS: + # print('initFile ok') + # print('SetImageInfo ok') + nRet = ueye_tools.israw_OpenFile(camPar.punFileID, strFileName) + # if nRet == ueye.IS_SUCCESS: + # # print('OpenFile success') + + # nShutterMode = ueye.c_uint(ueye.IS_DEVICE_FEATURE_CAP_SHUTTER_MODE_ROLLING_GLOBAL_START) + # nRet = ueye.is_DeviceFeature(camPar.hCam, ueye.IS_DEVICE_FEATURE_CMD_SET_SHUTTER_MODE, nShutterMode, + # ueye.sizeof(nShutterMode)) + # print('shutter mode = ' + str(nShutterMode.value) + ' / enable : ' + str(nRet)) + + # # Read the global flash params + # flashParams = ueye.IO_FLASH_PARAMS() + # nRet = ueye.is_IO(camPar.hCam, ueye.IS_IO_CMD_FLASH_GET_GLOBAL_PARAMS, flashParams, ueye.sizeof(flashParams)) + # if (nRet == ueye.IS_SUCCESS): + # nDelay = flashParams.s32Delay + # print('nDelay = ' + str(nDelay.value)) + # nDuration = flashParams.u32Duration + # print('nDuration = ' + str(nDuration.value)) + + # flashParams.s32Delay.value = 0 + # flashParams.u32Duration.value = 40 + # # Apply the global flash params and set the flash params to these values + # nRet = ueye.is_IO(camPar.hCam, ueye.IS_IO_CMD_FLASH_SET_PARAMS, flashParams, ueye.sizeof(flashParams)) + + + # nRet = ueye.is_IO(camPar.hCam, ueye.IS_IO_CMD_FLASH_GET_PARAMS, flashParams, ueye.sizeof(flashParams)) + # if (nRet == ueye.IS_SUCCESS): + # nDelay = flashParams.s32Delay + # print('nDelay = ' + str(nDelay.value)) + # nDuration = flashParams.u32Duration + # print('nDuration = ' + str(nDuration.value)) + + # --------------------------------------------------------- + # Activates the camera's live video mode (free run mode) + # --------------------------------------------------------- + nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) + # nRet = ueye.is_FreezeVideo(camPar.hCam, ueye.IS_DONT_WAIT) + if nRet != ueye.IS_SUCCESS: + print("is_CaptureVideo ERROR") + else: + camPar.camActivated = 1 + + return camPar + + +def runCam_thread(camPar, start_chrono): + imageinfo = ueye.UEYEIMAGEINFO() + current_buffer = ueye.c_mem_p() + current_id = ueye.int() + # inc = 0 + entier_old = 0 + # time.sleep(0.01) + while True: + nret = ueye.is_WaitForNextImage(camPar.hCam, camPar.timeout, current_buffer, current_id) + if nret == ueye.IS_SUCCESS: + check_ueye(ueye.is_GetImageInfo, camPar.hCam, current_id, imageinfo, ueye.sizeof(imageinfo)) + start_time = time.time() + counter = start_time - start_chrono + camPar.time_array.append(counter) + if camPar.vidFormat == 'avi': + nRet = ueye_tools.isavi_AddFrame(camPar.avi, current_buffer) + elif camPar.vidFormat == 'bin': + nRet = ueye_tools.israw_AddFrame(camPar.punFileID, current_buffer, imageinfo.u64TimestampDevice) + + check_ueye(ueye.is_UnlockSeqBuf, camPar.hCam, current_id, current_buffer) + else: + # nRet = ueye.is_FreeImageMem (camPar.hCam, current_buffer, current_id) + # if nRet != ueye.IS_SUCCESS: + # print('ERROR to free the memory') + # print(nRet) + print('Thread finished') + break + # else: + # print('thread cam stop correctly') + # break + +def stopCam(camPar): + if camPar.vidFormat == 'avi': + ueye_tools.isavi_StopAVI(camPar.hCam) + ueye_tools.isavi_CloseAVI(camPar.hCam) + ueye_tools.isavi_ExitAVI(camPar.hCam) + elif camPar.vidFormat == 'bin': + ueye_tools.israw_CloseFile(camPar.punFileID) + ueye_tools.israw_ExitFile(camPar.punFileID) + camPar.punFileID = ueye.c_uint() + + # camPar = stopCapt_DeallocMem(camPar) + + return camPar + + + + + + + + + + + + + + + + + + + + + + diff --git a/spas/dmd.py b/spas/dmd.py new file mode 100644 index 0000000..4d7bfc8 --- /dev/null +++ b/spas/dmd.py @@ -0,0 +1,572 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jan 28 10:10:42 2025 + +@author: chiliaeva + +init +piloting +disconnect +""" + + +from time import sleep, perf_counter_ns +import numpy as np +import cv2 +import matplotlib.pyplot as plt +from pathlib import Path +from typing import NamedTuple, Tuple, List, Optional +from spas.metadata_SPC2D import DMDParameters + +from tqdm import tqdm + + +##### DLL for the DMD +try: + from ALP4 import ALP4, ALP_FIRSTFRAME, ALP_LASTFRAME, ALP_BITNUM + from ALP4 import ALP_AVAIL_MEMORY, ALP_DEV_DYN_SYNCH_OUT1_GATE, tAlpDynSynchOutGate + # print('ALP4 is ok in Acquisition file') +except: + class ALP4: + pass + + + + +def _init_DMD() -> Tuple[ALP4, int]: + """Initialize a DMD and clean its allocated memory from a previous use. + + Returns: + Tuple[ALP4, int]: Tuple containing initialized DMD object and DMD + initial available memory. + """ + + # Initializing DMD + + dll_path = Path(__file__).parent.parent.joinpath('lib/alpV42').__str__() + DMD = ALP4(version='4.2',libDir=dll_path) + # dll_path = Path(__file__).parent.parent.joinpath('lib/alpV43').__str__() + # DMD = ALP4(version='4.3',libDir=dll_path) + DMD.Initialize(DeviceNum=None) + + #print(f'DMD initial available memory: {DMD.DevInquire(ALP_AVAIL_MEMORY)}') + print('DMD connected') + + return DMD, DMD.DevInquire(ALP_AVAIL_MEMORY) + + +def _setup_DMD(DMD: ALP4, + add_illumination_time: int, + initial_memory: int + ) -> DMDParameters: + """Create DMD metadata. + + Creates basic DMD metadata, but leaves most of its fields empty to be set + later. Sets up the initial free memory present in the DMD. + This function's name is used to create cohesion between spectrometer and DMD + related functions. + + Args: + DMD (ALP4): + Connected DMD object. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + initial_memory (int): + Initial memory available in DMD after initialization. + + Returns: + DMDParameters: + DMD metadata object. + """ + + return DMDParameters( + add_illumination_time_us=add_illumination_time, + initial_memory=initial_memory, + DMD=DMD) + + +def _sequence_limits(DMD: ALP4, pattern_compression: int, + sequence_lenght: int, + pos_neg: bool = True) -> int: + """Set sequence limits based on a sequence already uploaded to DMD. + + Args: + DMD (ALP4): + Connected DMD object. + pattern_compression (int): + Percentage of total available patterns to be present in an + acquisition sequence. + sequence_lenght (int): + Amount of patterns present in DMD memory. + pos_neg (bool): + Boolean indicating if sequence is formed by positive and negative + patterns. Default is True. + + Returns: + frames (int): + Amount of patterns to be used from a sequence based on the pattern + compression. + """ + + # Choosing beggining of the sequence + # DMD.SeqControl(ALP_BITNUM, 1) + DMD.SeqControl(ALP_FIRSTFRAME, 0) + + # Choosing the end of the sequence + if (round(pattern_compression * sequence_lenght) % 2 == 0) or not (pos_neg): + frames = round(pattern_compression * sequence_lenght) + else: + frames = round(pattern_compression * sequence_lenght) + 1 + + DMD.SeqControl(ALP_LASTFRAME, frames - 1) + + return frames + + +def _update_sequence(DMD: ALP4, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + pattern_source: str, + pattern_prefix: str, + pattern_order: List[int], + bitplanes: int = 1): + """Send new complete pattern sequence to DMD. + + Args: + DMD (ALP4): + Connected DMD object. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + pattern_source (str): + Pattern source folder. + pattern_preffix (str): + Prefix used in pattern naming. + pattern_order (List[int]): + List of the pattern indices in a certain order for upload to DMD. + bitplanes (int, optional): + Pattern bitplanes. Defaults to 1. + """ + + path_base = Path(pattern_source) + + seqId = DMD.SeqAlloc(nbImg=len(pattern_order), + bitDepth=bitplanes) + + zoom = acquisition_params.zoom + x_offset = acquisition_params.xw_offset + y_offset = acquisition_params.yh_offset + Np = acquisition_params.pattern_dimension_x + + dmd_height = DMD_params.display_height + dmd_width = DMD_params.display_width + len_im = int(dmd_height / zoom) + + # print(f'Pattern order size: {len(pattern_order)}') + t = perf_counter_ns() + + # for adaptative patterns into a ROI + apply_mask = False + mask_index = acquisition_params.mask_index + + if mask_index != []: + apply_mask = True + Npx = acquisition_params.pattern_dimension_x + Npy = acquisition_params.pattern_dimension_y + mask_element_nbr = len(mask_index) + x_mask_coord = acquisition_params.x_mask_coord + y_mask_coord = acquisition_params.y_mask_coord + x_mask_length = x_mask_coord[1] - x_mask_coord[0] + y_mask_length = y_mask_coord[1] - y_mask_coord[0] + + first_pass = True + for index,pattern_name in enumerate(tqdm(pattern_order, unit=' patterns', total=len(pattern_order))): + # read numpy patterns + path = path_base.joinpath(f'{pattern_prefix}_{pattern_name}.npy') + im = np.load(path) + + patterns = np.zeros((dmd_height, dmd_width), dtype=np.uint8) + + if apply_mask == True: # for adaptative patterns into a ROI + pat_mask_all = np.zeros(y_mask_length*x_mask_length) # initialize a vector of lenght = size of the cropped mask + pat_mask_all[mask_index] = im[:mask_element_nbr] #pat_re_vec[:mask_element_nbr] # put the pattern into the vector + pat_mask_all_mat = np.reshape(pat_mask_all, [y_mask_length, x_mask_length]) # reshape the vector into a matrix of the 2d cropped mask + # resize the matrix to the DMD size + pat_mask_all_mat_DMD = cv2.resize(pat_mask_all_mat, (int(dmd_height*x_mask_length/(Npx*zoom)), int(dmd_height*y_mask_length/(Npy*zoom))), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + first_pass = False + len_im3 = pat_mask_all_mat_DMD.shape + + patterns[y_offset:y_offset+len_im3[0], x_offset:x_offset+len_im3[1]] = pat_mask_all_mat_DMD + else: # send the entire square pattern without the mask + im_mat = np.reshape(im, [Np,Np]) + im_HD = cv2.resize(im_mat, (int(dmd_height/zoom), int(dmd_height/zoom)), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + len_im = im_HD.shape + first_pass = False + + patterns[y_offset:y_offset+len_im[0], x_offset:x_offset+len_im[1]] = im_HD + + if pattern_name == 151: + plt.figure() + # plt.imshow(pat_c_re) + # plt.imshow(pat_mask_all_mat) + # plt.imshow(pat_mask_all_mat_DMD) + plt.imshow(patterns) + plt.colorbar() + plt.title('pattern n°' + str(pattern_name)) + + patterns = patterns.ravel() + + DMD.SeqPut( + imgData=patterns.copy(), + PicOffset=index, + PicLoad=1) + + print(f'\nTime for sending all patterns: ' + f'{(perf_counter_ns() - t)/1e+9} s') + + +def _update_sequence(DMD: ALP4, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + pattern_source: str, + pattern_prefix: str, + pattern_order: List[int], + bitplanes: int = 1): + """Send new complete pattern sequence to DMD. + + Args: + DMD (ALP4): + Connected DMD object. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + pattern_source (str): + Pattern source folder. + pattern_preffix (str): + Prefix used in pattern naming. + pattern_order (List[int]): + List of the pattern indices in a certain order for upload to DMD. + bitplanes (int, optional): + Pattern bitplanes. Defaults to 1. + """ + + path_base = Path(pattern_source) + + seqId = DMD.SeqAlloc(nbImg=len(pattern_order), + bitDepth=bitplanes) + + zoom = acquisition_params.zoom + x_offset = acquisition_params.xw_offset + y_offset = acquisition_params.yh_offset + Np = acquisition_params.pattern_dimension_x + + dmd_height = DMD_params.display_height + dmd_width = DMD_params.display_width + len_im = int(dmd_height / zoom) + + # print(f'Pattern order size: {len(pattern_order)}') + t = perf_counter_ns() + + # for adaptative patterns into a ROI + apply_mask = False + mask_index = acquisition_params.mask_index + + if mask_index != []: + apply_mask = True + Npx = acquisition_params.pattern_dimension_x + Npy = acquisition_params.pattern_dimension_y + mask_element_nbr = len(mask_index) + x_mask_coord = acquisition_params.x_mask_coord + y_mask_coord = acquisition_params.y_mask_coord + x_mask_length = x_mask_coord[1] - x_mask_coord[0] + y_mask_length = y_mask_coord[1] - y_mask_coord[0] + + first_pass = True + for index,pattern_name in enumerate(tqdm(pattern_order, unit=' patterns', total=len(pattern_order))): + # read numpy patterns + path = path_base.joinpath(f'{pattern_prefix}_{pattern_name}.npy') + im = np.load(path) + + patterns = np.zeros((dmd_height, dmd_width), dtype=np.uint8) + + if apply_mask == True: # for adaptative patterns into a ROI + pat_mask_all = np.zeros(y_mask_length*x_mask_length) # initialize a vector of lenght = size of the cropped mask + pat_mask_all[mask_index] = im[:mask_element_nbr] #pat_re_vec[:mask_element_nbr] # put the pattern into the vector + pat_mask_all_mat = np.reshape(pat_mask_all, [y_mask_length, x_mask_length]) # reshape the vector into a matrix of the 2d cropped mask + # resize the matrix to the DMD size + pat_mask_all_mat_DMD = cv2.resize(pat_mask_all_mat, (int(dmd_height*x_mask_length/(Npx*zoom)), int(dmd_height*y_mask_length/(Npy*zoom))), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + first_pass = False + len_im3 = pat_mask_all_mat_DMD.shape + + patterns[y_offset:y_offset+len_im3[0], x_offset:x_offset+len_im3[1]] = pat_mask_all_mat_DMD + else: # send the entire square pattern without the mask + im_mat = np.reshape(im, [Np,Np]) + im_HD = cv2.resize(im_mat, (int(dmd_height/zoom), int(dmd_height/zoom)), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + len_im = im_HD.shape + first_pass = False + + patterns[y_offset:y_offset+len_im[0], x_offset:x_offset+len_im[1]] = im_HD + + if pattern_name == 151: + plt.figure() + # plt.imshow(pat_c_re) + # plt.imshow(pat_mask_all_mat) + # plt.imshow(pat_mask_all_mat_DMD) + plt.imshow(patterns) + plt.colorbar() + plt.title('pattern n°' + str(pattern_name)) + + patterns = patterns.ravel() + + DMD.SeqPut( + imgData=patterns.copy(), + PicOffset=index, + PicLoad=1) + + print(f'\nTime for sending all patterns: ' + f'{(perf_counter_ns() - t)/1e+9} s') + + +def _setup_patterns_2arms(DMD: ALP4, metadata: MetaData, DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, camPar: CAM, + cov_path: str = None) -> None: + """Read and send patterns to DMD. + + Reads patterns from a file and sends a percentage of them to the DMD, + considering positve and negative Hadamard patterns, which should be even in + number. + Prints time taken to read all patterns and send the requested ones + to DMD. + Updates available memory in DMD metadata object (DMD_params). + + Args: + DMD (ALP4): + Connected DMD object. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + """ + + file = np.load(Path(metadata.pattern_order_source)) + pattern_order = file['pattern_order'] + pattern_order = pattern_order.astype('int32') + + # copy the black pattern image (png) to the number = -1 + # black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.png' ) + black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.npy' ) + + if black_pattern_dest_path.is_file() == False: + # black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + + # str(camPar.black_pattern_num) + '.png' ) + black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + + str(camPar.black_pattern_num) + '.npy' ) + shutil.copyfile(black_pattern_orig_path, black_pattern_dest_path) + + + # add white patterns for the camera + if camPar.insert_patterns == 1: + inc = 0 + while True: + try: + pattern_order[inc] # except error from the end of array to stop the loop + if (inc % camPar.gate_period) == 0:#16) == 0: + pattern_order = np.insert(pattern_order, inc, -1) # double white pattern is required if integration time is shorter than 3.85 ms + if camPar.int_time_spect < 3.85: + pattern_order = np.insert(pattern_order, inc+1, -1) + if camPar.int_time_spect < 1.65: + pattern_order = np.insert(pattern_order, inc+2, -1) + if camPar.int_time_spect < 1: + pattern_order = np.insert(pattern_order, inc+1, -1) + if camPar.int_time_spect <= 0.6: + pattern_order = np.insert(pattern_order, inc+1, -1) + inc = inc + 1 + except: + # print('while loop finished') + break + + # if camPar.int_time_spect < 1.75: # add one pattern at the beginning of the sequence when the integration time of the spectrometer is shorter than 1.75 ms + # print('no interleaving') + # #pattern_order = np.insert(pattern_order, 0, -1) + # # pattern_order = np.insert(pattern_order, 0, -1) + if (len(pattern_order)%2) != 0: # Add one pattern at the end of the sequence if the pattern number is even + pattern_order = np.insert(pattern_order, len(pattern_order), -1) + print('pattern order is odd => a black image is automaticly insert, need to be deleted in the case for tuning the spectrometer') + + pos_neg = file['pos_neg'] + + bitplanes = 1 + DMD_params.bitplanes = bitplanes + + if (DMD_params.initial_memory - DMD.DevInquire(ALP_AVAIL_MEMORY) == + len(pattern_order)): + print('Reusing patterns from previous acquisition') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + else: + if (DMD.Seqs): + DMD.FreeSeq() + + _update_sequence(DMD, DMD_params, acquisition_params, metadata.pattern_source, metadata.pattern_prefix, + pattern_order, bitplanes) + print(f'DMD available memory after sequence allocation: ' + f'{DMD.DevInquire(ALP_AVAIL_MEMORY)}') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + acquisition_params.patterns = ( + pattern_order[0:acquisition_params.pattern_amount]) + + acquisition_params.patterns_wp = acquisition_params.patterns + + # Confirm memory allocated in DMD + DMD_params.update_memory(DMD.DevInquire(ALP_AVAIL_MEMORY)) + + +def _setup_timings(DMD: ALP4, DMD_params: DMDParameters, picture_time: int, + illumination_time: int, synch_pulse_delay: int, + synch_pulse_width: int, trigger_in_delay: int, + add_illumination_time: int) -> None: + """Setup pattern sequence timings in DMD. + + Send previously user-defined plus calculated timings to DMD. + Updates DMD metadata with sequence and timing related data. + This function has no default values for timings and lets the burden of + setting them to the setup function. + + Args: + DMD (ALP4): + Connected DMD object. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + picture_time (int): + Time between the start of two consecutive pictures (i.e. this + parameter defines the image display rate). Units in microseconds. + illumination_time (int): + Duration of the display of one pattern in a DMD sequence. + Units in microseconds. + synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). + synch_pulse_width (int): + Duration of DMD's frame synch output pulse. Units in microseconds. + trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + """ + + DMD.SetTiming(illuminationTime=illumination_time, + pictureTime=picture_time, + synchDelay=synch_pulse_delay, + synchPulseWidth=synch_pulse_width, + triggerInDelay=trigger_in_delay) + + DMD_params.update_sequence_parameters(add_illumination_time, DMD=DMD) + + +def change_patterns(DMD: ALP4, + acquisition_params: AcquisitionParameters, + zoom: int = 1, + xw_offset: int = 0, + yh_offset: int = 0, + force_change: bool = False + ): + """ + Delete patterns in the memory of the DMD in the case where the zoom or (x,y) offset change + + DMD (ALP4): + Connected DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + zoom (int): + digital zoom. Deafult is x1. + xw_offset (int): + offset int he width direction of the patterns for zoom > 1. + Default is 0. + yh_offset (int): + offset int he height direction of the patterns for zoom > 1. + Default is 0. + force_change (bool): + to force the changement of the pattern sequence. Default is False. + """ + + if acquisition_params.zoom != zoom or acquisition_params.xw_offset != xw_offset or acquisition_params.yh_offset != yh_offset or force_change == True: + if (DMD.Seqs): + DMD.FreeSeq() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spas/spectrograph.py b/spas/spectrograph.py new file mode 100644 index 0000000..3f5f2e6 --- /dev/null +++ b/spas/spectrograph.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jan 28 10:08:56 2025 + +@author: chiliaeva + +init +piloting +disconnect +""" + +# def init_spectrograph : \ No newline at end of file From 38c3eeafef4cdf931832686c1033ac5e6de18dfd Mon Sep 17 00:00:00 2001 From: Varvara Chiliaeva Date: Tue, 18 Feb 2025 15:51:13 +0100 Subject: [PATCH 2/5] copy acquisition, metadata and reconstruction --- spas/acquisition_SP1D.py | 2878 ++++++++++++++++++++++++++++++++++ spas/acquisition_SPC2D.py | 2878 ++++++++++++++++++++++++++++++++++ spas/metadata_SPC1D.py | 1118 +++++++++++++ spas/metadata_SPC2D.py | 1118 +++++++++++++ spas/reconstruction_SPC1D.py | 82 + spas/reconstruction_SPC2D.py | 82 + 6 files changed, 8156 insertions(+) create mode 100644 spas/acquisition_SP1D.py create mode 100644 spas/acquisition_SPC2D.py create mode 100644 spas/metadata_SPC1D.py create mode 100644 spas/metadata_SPC2D.py create mode 100644 spas/reconstruction_SPC1D.py create mode 100644 spas/reconstruction_SPC2D.py diff --git a/spas/acquisition_SP1D.py b/spas/acquisition_SP1D.py new file mode 100644 index 0000000..1c39899 --- /dev/null +++ b/spas/acquisition_SP1D.py @@ -0,0 +1,2878 @@ +# -*- coding: utf-8 -*- +__author__ = 'Guilherme Beneti Martins' + +"""Acquisition utility functions. + + Acquisition module is a generic module that call function in different setup (SPC2D_1arm, SPC2D_2arms, SCP1D and SPIM) + +""" + +import warnings +from time import sleep, perf_counter_ns +from typing import NamedTuple, Tuple, List, Optional +from collections import namedtuple +from pathlib import Path +from multiprocessing import Process, Queue +import shutil +import math + +import numpy as np +from PIL import Image +##### DLL for the DMD +try: + from ALP4 import ALP4, ALP_FIRSTFRAME, ALP_LASTFRAME + from ALP4 import ALP_AVAIL_MEMORY, ALP_DEV_DYN_SYNCH_OUT1_GATE, tAlpDynSynchOutGate + # print('ALP4 is ok in Acquisition file') +except: + class ALP4: + pass +##### DLL for the spectrometer Avantes +try: + from msl.equipment import EquipmentRecord, ConnectionRecord, Backend + from msl.equipment.resources.avantes import MeasureCallback, Avantes +except: + pass + +from tqdm import tqdm +from spas.metadata import DMDParameters, MetaData, AcquisitionParameters +from spas.metadata import SpectrometerParameters, save_metadata, CAM, save_metadata_2arms +from spas.reconstruction_nn import reconstruct_process, plot_recon, ReconstructionParameters + +# DLL for the IDS CAMERA +try: + from pyueye import ueye, ueye_tools +except: + print('ueye DLL not installed') + +from matplotlib import pyplot as plt +from IPython import get_ipython +import ctypes as ct +import logging +import time +import threading + + +def _init_spectrometer() -> Avantes: + """Initialize and connect to an Avantes Spectrometer. + + Returns: + Avantes: Avantes spectrometer. + """ + + dll_path = Path(__file__).parent.parent.joinpath( + 'lib/avaspec3/avaspecx64.dll') + + record = EquipmentRecord( + manufacturer='Avantes', + model='AvaSpec-UCLS2048BCL-EVO-RS', # update for your device + serial='2011126U1', # update for your device + connection=ConnectionRecord( + address=f'SDK::{dll_path}', + backend=Backend.MSL)) + + # Initialize Avantes SDK and establish the connection to the spectrometer + ava = record.connect() + print('Spectrometer connected') + + return ava + + +def _init_DMD(dmd_lib_version: str = '4.2') -> Tuple[ALP4, int]: + """Initialize a DMD and clean its allocated memory from a previous use. + + Args: + dmd_lib_version [str]: the version of the DMD library + + Returns: + Tuple[ALP4, int]: Tuple containing initialized DMD object and DMD + initial available memory. + """ + + # Initializing DMD + stop_init = False + if dmd_lib_version == '4.1': + print('dmd lib version = ' + dmd_lib_version + ' not installed, please, install it at the location : "openspyrit/spas/alpV41"') + stop_init = True + elif dmd_lib_version == '4.2': + dll_path = Path(__file__).parent.parent.joinpath('lib/alpV42').__str__() + DMD = ALP4(version='4.2',libDir=dll_path) + elif dmd_lib_version == '4.3': + dll_path = Path(__file__).parent.parent.joinpath('lib/alpV43').__str__() + DMD = ALP4(version='4.3',libDir=dll_path) + else: + print('unknown version of dmd library') + stop_init = True + + if stop_init == False: + DMD.Initialize(DeviceNum=None) + + #print(f'DMD initial available memory: {DMD.DevInquire(ALP_AVAIL_MEMORY)}') + print('DMD connected') + + return DMD, DMD.DevInquire(ALP_AVAIL_MEMORY) + else: + print('DMD initialisation aborted') + + +def init(dmd_lib_version: str = '4.2') -> Tuple[Avantes, ALP4, int]: + """Call functions to initialize spectrometer and DMD. + + Args: + dmd_lib_version [str]: the version of the DMD library + + Returns: + Tuple[Avantes, ALP4, int]: Tuple containing equipments and DMD initial + available memory: + Avantes: + Connected spectrometer object. + ALP4: + Connected DMD object. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + """ + + DMD, DMD_initial_memory = _init_DMD(dmd_lib_version) + return _init_spectrometer(), DMD, DMD_initial_memory + + +def init_2arms(dmd_lib_version: str = '4.2') -> Tuple[Avantes, ALP4, int]: + """Call functions to initialize spectrometer and DMD. + + Args: + dmd_lib_version [str]: the version of the DMD library + + Returns: + Tuple[Avantes, ALP4, int]: Tuple containing equipments and DMD initial + available memory: + Avantes: + Connected spectrometer object. + ALP4: + Connected DMD object. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + """ + + DMD, DMD_initial_memory = _init_DMD(dmd_lib_version) + camPar = _init_CAM() + return _init_spectrometer(), DMD, DMD_initial_memory, camPar + + +def _calculate_timings(integration_time: float = 1, + integration_delay: int = 0, + add_illumination_time: int = 300, + synch_pulse_delay: int = 0, + dark_phase_time: int = 44, + ) -> Tuple[int, int, int]: + """Calculate spectrometer and DMD dependant timings. + + Args: + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. + Default is 1 ms. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + Default is 0 us. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". Default is 365 us. + synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). Default is + 0 us. + dark_phase_time (int): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. Default is 44 us. + + Returns: + [Tuple]: DMD timings which depend on spectrometer's parameters. + synch_pulse_width: Duration of DMD's frame synch output pulse. Units + in microseconds. + illumination_time: Duration of the display of one pattern in a DMD + sequence. Units in microseconds. + picture_time: Time between the start of two consecutive pictures + (i.e. this parameter defines the image display rate). Units in + microseconds. + """ + + illumination_time = (integration_delay/1000 + integration_time*1000 + + add_illumination_time) + picture_time = illumination_time + dark_phase_time + synch_pulse_width = round(illumination_time/2 + synch_pulse_delay) + illumination_time = round(illumination_time) + picture_time = round(picture_time) + + return synch_pulse_width, illumination_time, picture_time + + +def _setup_spectrometer(ava: Avantes, + integration_time: float, + integration_delay: int, + start_pixel: int, + stop_pixel: int, + ) -> Tuple[SpectrometerParameters, List[float]]: + """Sets configurations in the spectrometer. + + Set all necessary configurations in the spectrometer preparing it for a + measurement. Creates SpectrometerData containing its metadata. Gets the + correct wavelengths depending on the selected pixels to be used. + + Args: + ava (Avantes): + Avantes spectrometer. + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + start_pixel (int): + Initial pixel data received from spectrometer. + stop_pixel (int, optional): + Last pixel data received from spectrometer. If None, then its value + will be determined from the amount of available pixels in the + spectrometer. + Returns: + Tuple[SpectrometerParameters, List[float]]: Metadata and wavelengths. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata. + wavelengths (List): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + """ + + spectrometer_detector = ava.SensType( + ava.get_parameter().m_Detector.m_SensorType).name + + # Get the number of pixels that the spectrometer has + initial_available_pixels = ava.get_num_pixels() + + # print(f'\nThe spectrometer has {initial_available_pixels} pixels') + + # Enable the 16-bit AD converter for High-Resolution + ava.use_high_res_adc(True) + + # Creating configuration block + measconfig = ava.MeasConfigType() + + measconfig.m_StartPixel = start_pixel + + if stop_pixel is None: + measconfig.m_StopPixel = initial_available_pixels - 1 + else: + measconfig.m_StopPixel = stop_pixel + + measconfig.m_IntegrationTime = integration_time + measconfig.m_IntegrationDelay = integration_delay + measconfig.m_NrAverages = 1 + + dark_correction = ava.DarkCorrectionType() + dark_correction.m_Enable = 0 + dark_correction.m_ForgetPercentage = 100 + measconfig.m_CorDynDark = dark_correction + + smoothing = ava.SmoothingType() + smoothing.m_SmoothPix = 0 + smoothing.m_SmoothModel = 0 + measconfig.m_Smoothing = smoothing + + measconfig.m_SaturationDetection = 1 + + trigger = ava.TriggerType() + trigger.m_Mode = 2 + trigger.m_Source = 0 + trigger.m_SourceType = 0 + measconfig.m_Trigger = trigger + + control_settings = ava.ControlSettingsType() + control_settings.m_StrobeControl = 0 + control_settings.m_LaserDelay = 0 + control_settings.m_LaserWidth = 0 + control_settings.LaserWaveLength = 0.00 + control_settings.m_StoreToRam = 0 + measconfig.m_Control = control_settings + + ava.prepare_measure(measconfig) + + spectrometer_params = SpectrometerParameters( + high_resolution=True, + initial_available_pixels=initial_available_pixels, + detector=spectrometer_detector, + configs=measconfig, + version_info=ava.get_version_info()) + + # Get the wavelength corresponding to each pixel + wavelengths = ava.get_lambda()[ + spectrometer_params.start_pixel:spectrometer_params.stop_pixel+1] + + return spectrometer_params, np.asarray(wavelengths) + + +def _setup_DMD(DMD: ALP4, + add_illumination_time: int, + initial_memory: int + ) -> DMDParameters: + """Create DMD metadata. + + Creates basic DMD metadata, but leaves most of its fields empty to be set + later. Sets up the initial free memory present in the DMD. + This function's name is used to create cohesion between spectrometer and DMD + related functions. + + Args: + DMD (ALP4): + Connected DMD object. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + initial_memory (int): + Initial memory available in DMD after initialization. + + Returns: + DMDParameters: + DMD metadata object. + """ + + return DMDParameters( + add_illumination_time_us=add_illumination_time, + initial_memory=initial_memory, + DMD=DMD) + + +def _sequence_limits(DMD: ALP4, + pattern_compression: int, + sequence_lenght: int, + pos_neg: bool = True) -> int: + """Set sequence limits based on a sequence already uploaded to DMD. + + Args: + DMD (ALP4): + Connected DMD object. + pattern_compression (int): + Percentage of total available patterns to be present in an + acquisition sequence. + sequence_lenght (int): + Amount of patterns present in DMD memory. + pos_neg (bool): + Boolean indicating if sequence is formed by positive and negative + patterns. Default is True. + + Returns: + frames (int): + Amount of patterns to be used from a sequence based on the pattern + compression. + """ + + # Choosing beggining of the sequence + # DMD.SeqControl(ALP_BITNUM, 1) + DMD.SeqControl(ALP_FIRSTFRAME, 0) + + # Choosing the end of the sequence + if (round(pattern_compression * sequence_lenght) % 2 == 0) or not (pos_neg): + frames = round(pattern_compression * sequence_lenght) + else: + frames = round(pattern_compression * sequence_lenght) + 1 + + DMD.SeqControl(ALP_LASTFRAME, frames - 1) + + return frames + + +def _update_sequence(DMD: ALP4, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + pattern_source: str, + pattern_prefix: str, + pattern_order: List[int], + bitplanes: int = 1): + """Send new complete pattern sequence to DMD. + + Args: + DMD (ALP4): + Connected DMD object. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + pattern_source (str): + Pattern source folder. + pattern_preffix (str): + Prefix used in pattern naming. + pattern_order (List[int]): + List of the pattern indices in a certain order for upload to DMD. + bitplanes (int, optional): + Pattern bitplanes. Defaults to 1. + """ + + import cv2 + + path_base = Path(pattern_source) + + seqId = DMD.SeqAlloc(nbImg=len(pattern_order), + bitDepth=bitplanes) + + zoom = acquisition_params.zoom + x_offset = acquisition_params.xw_offset + y_offset = acquisition_params.yh_offset + Np = acquisition_params.pattern_dimension_x + + dmd_height = DMD_params.display_height + dmd_width = DMD_params.display_width + len_im = int(dmd_height / zoom) + + t = perf_counter_ns() + + # for adaptative patterns into a ROI + apply_mask = False + mask_index = acquisition_params.mask_index + + if len(mask_index) > 0: + apply_mask = True + Npx = acquisition_params.pattern_dimension_x + Npy = acquisition_params.pattern_dimension_y + mask_element_nbr = len(mask_index) + x_mask_coord = acquisition_params.x_mask_coord + y_mask_coord = acquisition_params.y_mask_coord + x_mask_length = x_mask_coord[1] - x_mask_coord[0] + y_mask_length = y_mask_coord[1] - y_mask_coord[0] + + first_pass = True + for index,pattern_name in enumerate(tqdm(pattern_order, unit=' patterns', total=len(pattern_order))): + # read numpy patterns + path = path_base.joinpath(f'{pattern_prefix}_{pattern_name}.npy') + im = np.load(path) + + patterns = np.zeros((dmd_height, dmd_width), dtype=np.uint8) + + if apply_mask == True: # for adaptative patterns into a ROI + pat_mask_all = np.zeros(y_mask_length*x_mask_length) # initialize a vector of lenght = size of the cropped mask + pat_mask_all[mask_index] = im[:mask_element_nbr] #pat_re_vec[:mask_element_nbr] # put the pattern into the vector + pat_mask_all_mat = np.reshape(pat_mask_all, [y_mask_length, x_mask_length]) # reshape the vector into a matrix of the 2d cropped mask + # resize the matrix to the DMD size + pat_mask_all_mat_DMD = cv2.resize(pat_mask_all_mat, (int(dmd_height*x_mask_length/(Npx*zoom)), int(dmd_height*y_mask_length/(Npy*zoom))), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + first_pass = False + len_im3 = pat_mask_all_mat_DMD.shape + + patterns[y_offset:y_offset+len_im3[0], x_offset:x_offset+len_im3[1]] = pat_mask_all_mat_DMD + else: # send the entire square pattern without the mask + im_mat = np.reshape(im, [Np,Np]) + im_HD = cv2.resize(im_mat, (int(dmd_height/zoom), int(dmd_height/zoom)), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + len_im = im_HD.shape + first_pass = False + + patterns[y_offset:y_offset+len_im[0], x_offset:x_offset+len_im[1]] = im_HD + + # if pattern_name == 800: + # plt.figure() + # # plt.imshow(pat_c_re) + # # plt.imshow(pat_mask_all_mat) + # # plt.imshow(pat_mask_all_mat_DMD) + # plt.imshow(np.rot90(patterns,2)) + # plt.colorbar() + # plt.title('pattern n°' + str(pattern_name)) + + patterns = patterns.ravel() + + DMD.SeqPut( + imgData=patterns.copy(), + PicOffset=index, + PicLoad=1) + + print(f'\nTime for sending all patterns: ' + f'{(perf_counter_ns() - t)/1e+9} s') + + +def _setup_patterns(DMD: ALP4, + metadata: MetaData, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + cov_path: str = None, + pattern_to_display: str = 'white', + loop: bool = False) -> None: + """Read and send patterns to DMD. + + Reads patterns from a file and sends a percentage of them to the DMD, + considering positve and negative Hadamard patterns, which should be even in + number. + Prints time taken to read all patterns and send the requested ones + to DMD. + Updates available memory in DMD metadata object (DMD_params). + + Args: + DMD (ALP4): + Connected DMD object. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + loop (bool): + is to projet in loop, one or few patterns continously (see AlpProjStartCont + in the doc for more detail). Default is False + """ + + file = np.load(Path(metadata.pattern_order_source)) + pattern_order = file['pattern_order'] + pos_neg = file['pos_neg'] + + if loop == True: + pos_neg = False + if pattern_to_display == 'white': + pattern_order = np.array(pattern_order[0:1], dtype=np.int16) + elif pattern_to_display == 'black': + pattern_order = np.array(pattern_order[1:2], dtype=np.int16) + elif pattern_to_display == 'gray': + index = int(np.where(pattern_order == 1953)[0]) + print(index) + pattern_order = np.array(pattern_order[index:index+1], dtype=np.int16) + + bitplanes = 1 + DMD_params.bitplanes = bitplanes + + if (DMD_params.initial_memory - DMD.DevInquire(ALP_AVAIL_MEMORY) == + len(pattern_order)) and loop == False: + print('Reusing patterns from previous acquisition') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + else: + if (DMD.Seqs): + DMD.FreeSeq() + + _update_sequence(DMD, DMD_params, acquisition_params, metadata.pattern_source, metadata.pattern_prefix, + pattern_order, bitplanes) + print(f'DMD available memory after sequence allocation: ' + f'{DMD.DevInquire(ALP_AVAIL_MEMORY)}') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + acquisition_params.patterns = ( + pattern_order[0:acquisition_params.pattern_amount]) + + # Confirm memory allocated in DMD + DMD_params.update_memory(DMD.DevInquire(ALP_AVAIL_MEMORY)) + + +def _setup_patterns_2arms(DMD: ALP4, + metadata: MetaData, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + camPar: CAM, + cov_path: str = None) -> None: + """Read and send patterns to DMD. + + Reads patterns from a file and sends a percentage of them to the DMD, + considering positve and negative Hadamard patterns, which should be even in + number. + Prints time taken to read all patterns and send the requested ones + to DMD. + Updates available memory in DMD metadata object (DMD_params). + + Args: + DMD (ALP4): + Connected DMD object. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + cov_path (str): + Path to the covariance matrix used for reconstruction. + It must be a .npy (numpy) or .pt (pytorch) file. It is converted to + a torch tensor for reconstruction. + + """ + + file = np.load(Path(metadata.pattern_order_source)) + pattern_order = file['pattern_order'] + pattern_order = pattern_order.astype('int32') + + # copy the black pattern image (png) to the number = -1 + # black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.png' ) + black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.npy' ) + + if black_pattern_dest_path.is_file() == False: + # black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + + # str(camPar.black_pattern_num) + '.png' ) + black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + + str(camPar.black_pattern_num) + '.npy' ) + shutil.copyfile(black_pattern_orig_path, black_pattern_dest_path) + + + # add white patterns for the camera + if camPar.insert_patterns == 1: + inc = 0 + while True: + try: + pattern_order[inc] # except error from the end of array to stop the loop + if (inc % camPar.gate_period) == 0:#16) == 0: + pattern_order = np.insert(pattern_order, inc, -1) # double white pattern is required if integration time is shorter than 3.85 ms + if camPar.int_time_spect < 3.85: + pattern_order = np.insert(pattern_order, inc+1, -1) + if camPar.int_time_spect < 1.65: + pattern_order = np.insert(pattern_order, inc+2, -1) + if camPar.int_time_spect < 1: + pattern_order = np.insert(pattern_order, inc+1, -1) + if camPar.int_time_spect <= 0.6: + pattern_order = np.insert(pattern_order, inc+1, -1) + inc = inc + 1 + except: + # print('while loop finished') + break + + # if camPar.int_time_spect < 1.75: # add one pattern at the beginning of the sequence when the integration time of the spectrometer is shorter than 1.75 ms + # print('no interleaving') + # #pattern_order = np.insert(pattern_order, 0, -1) + # # pattern_order = np.insert(pattern_order, 0, -1) + if (len(pattern_order)%2) != 0: # Add one pattern at the end of the sequence if the pattern number is even + pattern_order = np.insert(pattern_order, len(pattern_order), -1) + print('pattern order is odd => a black image is automaticly insert, need to be deleted in the case for tuning the spectrometer') + + pos_neg = file['pos_neg'] + + bitplanes = 1 + DMD_params.bitplanes = bitplanes + + if (DMD_params.initial_memory - DMD.DevInquire(ALP_AVAIL_MEMORY) == + len(pattern_order)): + print('Reusing patterns from previous acquisition') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + else: + if (DMD.Seqs): + DMD.FreeSeq() + + _update_sequence(DMD, DMD_params, acquisition_params, metadata.pattern_source, metadata.pattern_prefix, + pattern_order, bitplanes) + print(f'DMD available memory after sequence allocation: ' + f'{DMD.DevInquire(ALP_AVAIL_MEMORY)}') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + acquisition_params.patterns = ( + pattern_order[0:acquisition_params.pattern_amount]) + + acquisition_params.patterns_wp = acquisition_params.patterns + + # Confirm memory allocated in DMD + DMD_params.update_memory(DMD.DevInquire(ALP_AVAIL_MEMORY)) + +def _setup_timings(DMD: ALP4, + DMD_params: DMDParameters, + picture_time: int, + illumination_time: int, + synch_pulse_delay: int, + synch_pulse_width: int, + trigger_in_delay: int, + add_illumination_time: int) -> None: + """Setup pattern sequence timings in DMD. + + Send previously user-defined plus calculated timings to DMD. + Updates DMD metadata with sequence and timing related data. + This function has no default values for timings and lets the burden of + setting them to the setup function. + + Args: + DMD (ALP4): + Connected DMD object. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + picture_time (int): + Time between the start of two consecutive pictures (i.e. this + parameter defines the image display rate). Units in microseconds. + illumination_time (int): + Duration of the display of one pattern in a DMD sequence. + Units in microseconds. + synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). + synch_pulse_width (int): + Duration of DMD's frame synch output pulse. Units in microseconds. + trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + """ + + DMD.SetTiming(illuminationTime=illumination_time, + pictureTime=picture_time, + synchDelay=synch_pulse_delay, + synchPulseWidth=synch_pulse_width, + triggerInDelay=trigger_in_delay) + + DMD_params.update_sequence_parameters(add_illumination_time, DMD=DMD) + + +def setup(spectrometer: Avantes, + DMD: ALP4, + DMD_initial_memory: int, + metadata: MetaData, + acquisition_params: AcquisitionParameters, + start_pixel: int = 0, + stop_pixel: Optional[int] = None, + integration_time: float = 1, + integration_delay: int = 0, + DMD_output_synch_pulse_delay: int = 0, + add_illumination_time: int = 356, + dark_phase_time: int = 44, + DMD_trigger_in_delay: int = 0, + pattern_to_display: str = 'white', + loop: bool = False + ) -> Tuple[SpectrometerParameters, DMDParameters]: + """Setup everything needed to start an acquisition. + + Sets all parameters for DMD, spectrometer, DMD patterns and DMD timings. + Must be called before every acquisition. + + Args: + spectrometer (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y. + start_pixel (int): + Initial pixel data received from spectrometer. Default is 0. + stop_pixel (int, optional): + Last pixel data received from spectrometer. Default is None if it + should be determined from the amount of available pixels in the + spectrometer. + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. Default + is 1 ms. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + Default is 0 us. + DMD_output_synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). Default is + 0 us. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". Default is 365 us. + dark_phase_time (int): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. Default is 44 us. + DMD_trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). Default is 0 us. + pattern_to_display (string): + display one pattern on the DMD to tune the spectrometer. Default is white + pattern + loop (bool): + is to projet in loop, one or few patterns continuously (see AlpProjStartCont + in the doc for more detail). Default is False + Raises: + ValueError: Sum of dark phase and additional illumination time is lower + than 400 us. + + Returns: + Tuple[SpectrometerParameters, DMDParameters, List]: Tuple containing DMD + and spectrometer relate metadata, as well as wavelengths. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + """ + + if loop == False: + path = Path(metadata.output_directory) + if not path.exists(): + path.mkdir() + + if dark_phase_time + add_illumination_time < 350: + raise ValueError(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' Must be greater than 350 µs.') + + elif dark_phase_time + add_illumination_time < 400: + warnings.warn(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' It is recomended to choose at least 400 µs.') + + synch_pulse_width, illumination_time, picture_time = _calculate_timings( + integration_time, + integration_delay, + add_illumination_time, + DMD_output_synch_pulse_delay, + dark_phase_time) + + spectrometer_params, wavelenghts = _setup_spectrometer( + spectrometer, + integration_time, + integration_delay, + start_pixel, + stop_pixel) + + acquisition_params.wavelengths = np.asarray(wavelenghts, dtype=np.float64) + + DMD_params = _setup_DMD(DMD, add_illumination_time, DMD_initial_memory) + + _setup_patterns(DMD=DMD, metadata=metadata, DMD_params=DMD_params, + acquisition_params=acquisition_params, loop=loop, + pattern_to_display=pattern_to_display) + + _setup_timings(DMD, DMD_params, picture_time, illumination_time, + DMD_output_synch_pulse_delay, synch_pulse_width, + DMD_trigger_in_delay, add_illumination_time) + + return spectrometer_params, DMD_params + + +def change_patterns(DMD: ALP4, + acquisition_params: AcquisitionParameters, + zoom: int = 1, + xw_offset: int = 0, + yh_offset: int = 0, + force_change: bool = False + ): + """ + Delete patterns in the memory of the DMD in the case where the zoom or (x,y) offset change + + DMD (ALP4): + Connected DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + zoom (int): + digital zoom. Deafult is x1. + xw_offset (int): + offset int he width direction of the patterns for zoom > 1. + Default is 0. + yh_offset (int): + offset int he height direction of the patterns for zoom > 1. + Default is 0. + force_change (bool): + to force the changement of the pattern sequence. Default is False. + """ + + if acquisition_params.zoom != zoom or acquisition_params.xw_offset != xw_offset or acquisition_params.yh_offset != yh_offset or force_change == True: + if (DMD.Seqs): + DMD.FreeSeq() + + +def setup_2arms(spectrometer: Avantes, + DMD: ALP4, + camPar: CAM, + DMD_initial_memory: int, + metadata: MetaData, + acquisition_params: AcquisitionParameters, + start_pixel: int = 0, + stop_pixel: Optional[int] = None, + integration_time: float = 1, + integration_delay: int = 0, + DMD_output_synch_pulse_delay: int = 0, + add_illumination_time: int = 356, + dark_phase_time: int = 44, + DMD_trigger_in_delay: int = 0 + ) -> Tuple[SpectrometerParameters, DMDParameters]: + """Setup everything needed to start an acquisition. + + Sets all parameters for DMD, spectrometer, DMD patterns and DMD timings. + Must be called before every acquisition. + + Args: + spectrometer (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + start_pixel (int): + Initial pixel data received from spectrometer. Default is 0. + stop_pixel (int, optional): + Last pixel data received from spectrometer. Default is None if it + should be determined from the amount of available pixels in the + spectrometer. + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. Default + is 1 ms. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + Default is 0 us. + DMD_output_synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). Default is + 0 us. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". Default is 365 us. + dark_phase_time (int): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. Default is 44 us. + DMD_trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). Default is 0 us. + + Raises: + ValueError: Sum of dark phase and additional illumination time is lower + than 400 us. + + Returns: + Tuple[SpectrometerParameters, DMDParameters, List]: Tuple containing DMD + and spectrometer relate metadata, as well as wavelengths. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + """ + + path = Path(metadata.output_directory) + if not path.exists(): + path.mkdir() + + if dark_phase_time + add_illumination_time < 350: + raise ValueError(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' Must be greater than 350 µs.') + + elif dark_phase_time + add_illumination_time < 400: + warnings.warn(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' It is recomended to choose at least 400 µs.') + + synch_pulse_width, illumination_time, picture_time = _calculate_timings( + integration_time, + integration_delay, + add_illumination_time, + DMD_output_synch_pulse_delay, + dark_phase_time) + + spectrometer_params, wavelenghts = _setup_spectrometer( + spectrometer, + integration_time, + integration_delay, + start_pixel, + stop_pixel) + + if camPar.gate_period > 16: + gate_period = 16 + print('Warning, gate period is ' + str(camPar.gate_period) + ' > than the max: 16.') + print('Try to increase the FPS of the camera, or the integration time of the spectrometer.') + print('Check the Pixel clock which must be = 474 MHz') + print('Otherwise some frames will be lost.') + elif camPar.gate_period <1: + print('Warning, gate period is ' + str(camPar.gate_period) + ' < than the min: 1.') + gate_period = 1 + else: + gate_period = camPar.gate_period + + camPar.gate_period = gate_period + Gate = tAlpDynSynchOutGate() + Gate.byref[0] = ct.c_ubyte(gate_period) # Period [1 to 16] (it is a multiple of the trig period which go to the spectro) + Gate.byref[1] = ct.c_ubyte(1) # Polarity => 0: active pulse is low, 1: high + Gate.byref[2] = ct.c_ubyte(1) # Gate1 ok to send TTL + Gate.byref[3] = ct.c_ubyte(0) # Gate2 do not send TTL + Gate.byref[4] = ct.c_ubyte(0) # Gate3 do not send TTL + DMD.DevControlEx(ALP_DEV_DYN_SYNCH_OUT1_GATE, Gate) + camPar.gate_period = gate_period + camPar.int_time_spect = integration_time + + acquisition_params.wavelengths = np.asarray(wavelenghts, dtype=np.float64) + + DMD_params = _setup_DMD(DMD, add_illumination_time, DMD_initial_memory) + + _setup_patterns_2arms(DMD=DMD, metadata=metadata, DMD_params=DMD_params, + acquisition_params=acquisition_params, camPar=camPar) + + _setup_timings(DMD, DMD_params, picture_time, illumination_time, + DMD_output_synch_pulse_delay, synch_pulse_width, + DMD_trigger_in_delay, add_illumination_time) + + return spectrometer_params, DMD_params, camPar + + +def _calculate_elapsed_time(start_measurement_time: int, + measurement_time: np.ndarray, + timestamps: List[int], + ) -> Tuple[np.ndarray, np.ndarray]: + """Calculate acquisition timings. + + Calculates elapsed time between each callback measurement taking into + account the moment when the DMD started running a sequence. + Calculates elapsed time between each spectrum acquired by the spectrometer + based on the spectrometer's internal clock. + + Args: + start_measurement_time (int): + Time in nanoseconds when DMD is set to start running a sequence. + measurement_time (np.ndarray): + 1D array with `int` type timings in nanoseconds when each callbacks + starts. + timestamps (List[int]): + 1D array with measurement timestamps from spectrometer. + Timestamps count ticks for the last pixel of the spectrum was + received by the spectrometer microcontroller. Ticks are in 10 + microsecond units since the spectrometer started. + + Returns: + Tuple[np.ndarray, np.ndarray]: Tuple with measurement timings. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + """ + + measurement_time = np.concatenate( + (start_measurement_time,measurement_time),axis=None) + + measurement_time = np.diff(measurement_time)/1e+6 # In ms + timestamps = np.diff(timestamps)/100 # In ms + + return measurement_time, timestamps + + +def _save_acquisition(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + acquisition_parameters: AcquisitionParameters, + spectral_data: np.ndarray) -> None: + """Save all acquisition data and metadata. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + acquisition_parameters (AcquisitionParameters): + Acquisition related metadata object. + spectral_data (ndarray): + 1D array with `float` type spectrometer measurements. Array size + depends on start and stop pixels previously set to the spectrometer. + """ + + # Saving collected data and timings + path = Path(metadata.output_directory) + path = path / f'{metadata.experiment_name}_spectraldata.npz' + np.savez_compressed(path, spectral_data=spectral_data) + + # 'save_metadata' function is commented because the 'save_metadata_2arms' function is executed after the 'acquire' function in the "main_seq_2arms.py" prog + # # Saving metadata + # save_metadata(metadata, + # DMD_params, + # spectrometer_params, + # acquisition_parameters) + +def _save_acquisition_2arms(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + camPar: CAM, + acquisition_parameters: AcquisitionParameters, + spectral_data: np.ndarray) -> None: + """Save all acquisition data and metadata. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + camPar (CAM): + Metadata object of the IDS monochrome camera + acquisition_parameters (AcquisitionParameters): + Acquisition related metadata object. + spectral_data (ndarray): + 1D array with `float` type spectrometer measurements. Array size + depends on start and stop pixels previously set to the spectrometer. + """ + + # Saving collected data and timings + path = Path(metadata.output_directory) + path = path / f'{metadata.experiment_name}_spectraldata.npz' + np.savez_compressed(path, spectral_data=spectral_data) + + # Saving metadata + save_metadata_2arms(metadata, + DMD_params, + spectrometer_params, + camPar, + acquisition_parameters) + + +def _acquire_raw(ava: Avantes, + DMD: ALP4, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + loop: bool = False + ) -> NamedTuple: + """Raw data acquisition. + + Setups a callback function to receive messages from spectrometer whenever a + measurement is ready to be read. Reads a measurement via a callback. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + loop (bool): + if True, projet continuously the pattern, see the AlpProjStartCont function + if False, projet one time the seq of the patterns, see the AlpProjStart function (Default) + + Returns: + NamedTuple: NamedTuple containig spectral data and measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + spectrum_index (int): + Index of the last acquired spectrum. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + start_measurement_time (float): + Time when acquisition started. + saturation_detected (bool): + Boolean incating if saturation was detected during acquisition. + """ + def register_callback(measurement_time, timestamps, + spectral_data, ava): + + def measurement_callback(handle, info): # If we want to reconstruct during callback; can use it in here. Add function as parameter. + nonlocal spectrum_index + nonlocal saturation_detected + + measurement_time[spectrum_index] = perf_counter_ns() + + if info.contents.value >= 0: + timestamp,spectrum = ava.get_data() + spectral_data[spectrum_index,:] = ( + np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + if np.any(ava.get_saturated_pixels() > 0): + saturation_detected = True + + timestamps[spectrum_index] = np.ctypeslib.as_array(timestamp) + + else: # Set values to zero if an error occured + spectral_data[spectrum_index,:] = 0 + timestamps[spectrum_index] = 0 + + spectrum_index += 1 + + return measurement_callback + + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + + measurement_time = np.zeros((acquisition_params.pattern_amount)) + timestamps = np.zeros((acquisition_params.pattern_amount),dtype=np.uint32) + spectral_data = np.zeros( + (acquisition_params.pattern_amount,pixel_amount),dtype=np.float64) + + # Boolean to indicate if saturation was detected during acquisition + saturation_detected = False + + spectrum_index = 0 # Accessed as nonlocal variable inside the callback + + if loop == False: + #spectro.register_callback(-2,acquisition_params.pattern_amount,pixel_amount) + callback = register_callback(measurement_time, timestamps, + spectral_data, ava) + measurement_callback = MeasureCallback(callback) + ava.measure_callback(-2, measurement_callback) + else: + ava.measure(-1) + + + DMD.Run(loop=loop) # if loop=False : Run the whole sequence only once, if loop=True : Run continuously one pattern + start_measurement_time = perf_counter_ns() + + if loop == False: + while(True): + if(spectrum_index >= acquisition_params.pattern_amount) and loop == False: + break + elif((perf_counter_ns() - start_measurement_time) / 1e+6 > + (2 * acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+3)) and loop == False: + print('Stopping measurement. One of the equipments may be blocked ' + 'or disconnected.') + break + else: + sleep(acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+6 / 10) + DMD.Halt() + else: + sleep(0.1) + + timestamp, spectrum = ava.get_data() + spectral_data_1 = (np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + get_ipython().run_line_magic('matplotlib', 'qt') + plt.ion() # create GUI + figure, ax = plt.subplots(figsize=(10, 8)) + line1, = ax.plot(acquisition_params.wavelengths, spectral_data_1) + + plt.title("Tune the Spectrometer", fontsize=20) + plt.xlabel("Lambda (nm)") + plt.ylabel("counts") + plt.xticks(fontsize=14) + plt.yticks(fontsize=14) + plt.grid() + printed = False + while(True): + try: + timestamp, spectrum = ava.get_data() + spectral_data_1 = (np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + line1.set_xdata(acquisition_params.wavelengths) + line1.set_ydata(spectral_data_1) # updating data values + + figure.canvas.draw() # drawing updated values + figure.canvas.flush_events() # flush prior plot + + if not printed: + print('Press "Ctrl + c" to exit') + if np.amax(spectral_data_1) >= 65535: + print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') + printed = True + + except KeyboardInterrupt: + if (DMD.Seqs): + DMD.Halt() + DMD.FreeSeq() + plt.close() + get_ipython().run_line_magic('matplotlib', 'inline') + break + + ava.stop_measure() + + AcquisitionResult = namedtuple('AcquisitionResult', [ + 'spectral_data', + 'spectrum_index', + 'timestamps', + 'measurement_time', + 'start_measurement_time', + 'saturation_detected']) + + return AcquisitionResult(spectral_data, + spectrum_index, + timestamps, + measurement_time, + start_measurement_time, + saturation_detected) + + +def acquire(ava: Avantes, + DMD: ALP4, + metadata: MetaData, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + repetitions: int = 1, + verbose: bool = False, + reconstruct: bool = False, + reconstruction_params: ReconstructionParameters = None + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Perform a complete acquisition. + + Performs single or multiple acquisitions using the same setup configurations + previously chosen. + Finnaly saves all acqusition related data and metadata. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + wavelengths (List[float]): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + repetitions (int): + Number of times the acquisition will be repeated with the same + configurations. Default is 1, a single acquisition. + verbose (bool): + Chooses if data concerning each acquisition should be printed to + user. If False, only overall data regarding all repetitions is + printed. Default is False. + reconstruct (bool): + If True, will perform reconstruction alongside acquisition using + multiprocessing. + reconstruction_params (ReconstructionParameters): + Object containing parameters of the neural network to be loaded for + reconstruction. + + Returns: + Tuple[ndarray, ndarray, ndarray]: Tuple containig spectral data and + measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + """ + + loop = False # if true, is to projet continuously a unique pattern to tune the spectrometer + + if reconstruct == True: + print('Creating reconstruction processes') + + # Creating a Queue for sending spectral data to reconstruction process + queue_to_recon = Queue() + + # Creating a Queue for sending reconstructed images to plot + queue_reconstructed = Queue() + + sleep_time = (acquisition_params.pattern_amount * + DMD_params.picture_time_us/1e+6) + + # Creating reconstruction process + recon_process = Process(target=reconstruct_process, + args=(reconstruction_params.model, + reconstruction_params.device, + queue_to_recon, + queue_reconstructed, + reconstruction_params.batches, + reconstruction_params.noise, + sleep_time)) + + # Creating plot process + plot_process = Process(target=plot_recon, + args=(queue_reconstructed, sleep_time)) + + # Starting processes + recon_process.start() + plot_process.start() + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + measurement_time = np.zeros( + (acquisition_params.pattern_amount * repetitions)) + timestamps = np.zeros( + ((acquisition_params.pattern_amount - 1) * repetitions), + dtype=np.float64) + spectral_data = np.zeros( + (acquisition_params.pattern_amount * repetitions,pixel_amount), + dtype=np.float64) + + acquisition_params.acquired_spectra = 0 + print() + + for repetition in range(repetitions): + if verbose: + print(f"Acquisition {repetition}") + + AcquisitionResults = _acquire_raw(ava, DMD, spectrometer_params, + DMD_params, acquisition_params, loop) + + (data, spectrum_index, timestamp, time, + start_measurement_time, saturation_detected) = AcquisitionResults + + print('Acquisition number : ' + str(repetition) + ' finished') + + if reconstruct == True: + queue_to_recon.put(data.T) + print('Data sent') + + time, timestamp = _calculate_elapsed_time( + start_measurement_time, time, timestamp) + + begin = repetition * acquisition_params.pattern_amount + end = (repetition + 1) * acquisition_params.pattern_amount + spectral_data[begin:end] = data + measurement_time[begin:end] = time + + begin = repetition * (acquisition_params.pattern_amount - 1) + end = (repetition + 1) * (acquisition_params.pattern_amount - 1) + timestamps[begin:end] = timestamp + + acquisition_params.acquired_spectra += spectrum_index + + acquisition_params.saturation_detected = saturation_detected + + if saturation_detected is True: + print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') + # Print data for each repetition + if (verbose): + print('Spectra acquired: {}'.format(spectrum_index)) + print('Mean callback acquisition time: {} ms'.format( + np.mean(time))) + print('Total callback acquisition time: {} s'.format( + np.sum(time)/1000)) + print('Mean spectrometer acquisition time: {} ms'.format( + np.mean(timestamp))) + print('Total spectrometer acquisition time: {} s'.format( + np.sum(timestamp)/1000)) + + # Print shape of acquisition matrix for one repetition + print(f'Partial acquisition matrix dimensions:' + f'{data.shape}') + print() + + acquisition_params.update_timings(timestamps, measurement_time) + # Real time between each spectrum acquisition by the spectrometer + print('Complete acquisition done') + print('Spectra acquired: {}'.format(acquisition_params.acquired_spectra)) + print('Total acquisition time: {0:.2f} s'.format(acquisition_params.total_spectrometer_acquisition_time_s)) + + _save_acquisition(metadata, DMD_params, spectrometer_params, + acquisition_params, spectral_data) + + # Joining processes and closing queues + if reconstruct == True: + queue_to_recon.put('kill') # Sends a message to stop reconstruction + recon_process.join() + queue_to_recon.close() + plot_process.join() + queue_reconstructed.close() + + maxi = np.amax(spectral_data[0,:]) + print('------------------------------------------------') + print('maximum in the spectrum = ' + str(maxi)) + print('------------------------------------------------') + if maxi >= 65535: + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + print('!!!!! warning, spectrum saturation !!!!!!!!') + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + + return spectral_data + +def _acquire_raw_2arms(ava: Avantes, + DMD: ALP4, + camPar: CAM, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + metadata, + repetition, + repetitions + ) -> NamedTuple: + """Raw data acquisition. + + Setups a callback function to receive messages from spectrometer whenever a + measurement is ready to be read. Reads a measurement via a callback. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + + Returns: + NamedTuple: NamedTuple containig spectral data and measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + spectrum_index (int): + Index of the last acquired spectrum. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + start_measurement_time (float): + Time when acquisition started. + saturation_detected (bool): + Boolean incating if saturation was detected during acquisition. + """ + # def for spectrometer acquisition + def register_callback(measurement_time, timestamps, + spectral_data, ava): + + def measurement_callback(handle, info): # If we want to reconstruct during callback; can use it in here. Add function as parameter. + nonlocal spectrum_index + nonlocal saturation_detected + + measurement_time[spectrum_index] = perf_counter_ns() + + if info.contents.value >= 0: + timestamp,spectrum = ava.get_data() + spectral_data[spectrum_index,:] = ( + np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + if np.any(ava.get_saturated_pixels() > 0): + saturation_detected = True + + timestamps[spectrum_index] = np.ctypeslib.as_array(timestamp) + + else: # Set values to zero if an error occured + spectral_data[spectrum_index,:] = 0 + timestamps[spectrum_index] = 0 + + spectrum_index += 1 + + return measurement_callback + + # def for camera acquisition + if repetition == 0: + camPar = stopCapt_DeallocMem(camPar) + camPar.trigger_mode = 'hard'#'soft'# + imageQueue(camPar) + camPar = prepareCam(camPar, metadata) + camPar.timeout = 1000 # time out in ms for the "is_WaitForNextImage" function + start_chrono = time.time() + x = threading.Thread(target = runCam_thread, args=(camPar, start_chrono)) + x.start() + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + + measurement_time = np.zeros((acquisition_params.pattern_amount)) + timestamps = np.zeros((acquisition_params.pattern_amount),dtype=np.uint32) + spectral_data = np.zeros( + (acquisition_params.pattern_amount,pixel_amount),dtype=np.float64) + + # Boolean to indicate if saturation was detected during acquisition + saturation_detected = False + + spectrum_index = 0 # Accessed as nonlocal variable inside the callback + + #spectro.register_callback(-2,acquisition_params.pattern_amount,pixel_amount) + callback = register_callback(measurement_time, timestamps, + spectral_data, ava) + measurement_callback = MeasureCallback(callback) + ava.measure_callback(-2, measurement_callback) + + # time.sleep(0.5) + # Run the whole sequence only once + DMD.Run(loop=False) + start_measurement_time = perf_counter_ns() + #sleep(13) + + while(True): + if(spectrum_index >= acquisition_params.pattern_amount): + break + elif((perf_counter_ns() - start_measurement_time) / 1e+6 > + (2 * acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+3)): + print('Stopping measurement. One of the equipments may be blocked ' + 'or disconnected.') + break + else: + time.sleep(acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+6 / 10) + + ava.stop_measure() + DMD.Halt() + camPar.Exit = 2 + if repetition == repetitions-1: + camPar = stopCam(camPar) + #Yprint('MAIN :// camPar.camActivated = ' + str(camPar.camActivated)) + AcquisitionResult = namedtuple('AcquisitionResult', [ + 'spectral_data', + 'spectrum_index', + 'timestamps', + 'measurement_time', + 'start_measurement_time', + 'saturation_detected']) + + return AcquisitionResult(spectral_data, + spectrum_index, + timestamps, + measurement_time, + start_measurement_time, + saturation_detected) + + +def acquire_2arms(ava: Avantes, + DMD: ALP4, + camPar: CAM, + metadata: MetaData, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + repetitions: int = 1, + verbose: bool = False, + reconstruct: bool = False, + reconstruction_params: ReconstructionParameters = None + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Perform a complete acquisition. + + Performs single or multiple acquisitions using the same setup configurations + previously chosen. + Finnaly saves all acqusition related data and metadata. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + wavelengths (List[float]): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + repetitions (int): + Number of times the acquisition will be repeated with the same + configurations. Default is 1, a single acquisition. + verbose (bool): + Chooses if data concerning each acquisition should be printed to + user. If False, only overall data regarding all repetitions is + printed. Default is False. + reconstruct (bool): + If True, will perform reconstruction alongside acquisition using + multiprocessing. + reconstruction_params (ReconstructionParameters): + Object containing parameters of the neural network to be loaded for + reconstruction. + + Returns: + Tuple[ndarray, ndarray, ndarray]: Tuple containig spectral data and + measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + """ + + if reconstruct == True: + print('Creating reconstruction processes') + + # Creating a Queue for sending spectral data to reconstruction process + queue_to_recon = Queue() + + # Creating a Queue for sending reconstructed images to plot + queue_reconstructed = Queue() + + sleep_time = (acquisition_params.pattern_amount * + DMD_params.picture_time_us/1e+6) + + # Creating reconstruction process + recon_process = Process(target=reconstruct_process, + args=(reconstruction_params.model, + reconstruction_params.device, + queue_to_recon, + queue_reconstructed, + reconstruction_params.batches, + reconstruction_params.noise, + sleep_time)) + + # Creating plot process + plot_process = Process(target=plot_recon, + args=(queue_reconstructed, sleep_time)) + + # Starting processes + recon_process.start() + plot_process.start() + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + measurement_time = np.zeros( + (acquisition_params.pattern_amount * repetitions)) + timestamps = np.zeros( + ((acquisition_params.pattern_amount - 1) * repetitions), + dtype=np.float64) + spectral_data = np.zeros( + (acquisition_params.pattern_amount * repetitions,pixel_amount), + dtype=np.float64) + + acquisition_params.acquired_spectra = 0 + print() + + for repetition in range(repetitions): + if verbose: + print(f"Acquisition {repetition}") + + AcquisitionResults = _acquire_raw_2arms(ava, DMD, camPar, spectrometer_params, + DMD_params, acquisition_params, metadata, repetition, repetitions) + + (data, spectrum_index, timestamp, time, + start_measurement_time, saturation_detected) = AcquisitionResults + + print('Acquisition number : ' + str(repetition) + ' finished') + + if reconstruct == True: + queue_to_recon.put(data.T) + print('Data sent') + + time, timestamp = _calculate_elapsed_time( + start_measurement_time, time, timestamp) + + begin = repetition * acquisition_params.pattern_amount + end = (repetition + 1) * acquisition_params.pattern_amount + spectral_data[begin:end] = data + measurement_time[begin:end] = time + + begin = repetition * (acquisition_params.pattern_amount - 1) + end = (repetition + 1) * (acquisition_params.pattern_amount - 1) + timestamps[begin:end] = timestamp + + acquisition_params.acquired_spectra += spectrum_index + + acquisition_params.saturation_detected = saturation_detected + + if saturation_detected is True: + print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') + # Print data for each repetition + if (verbose): + print('Spectra acquired: {}'.format(spectrum_index)) + print('Mean callback acquisition time: {} ms'.format( + np.mean(time))) + print('Total callback acquisition time: {} s'.format( + np.sum(time)/1000)) + print('Mean spectrometer acquisition time: {} ms'.format( + np.mean(timestamp))) + print('Total spectrometer acquisition time: {} s'.format( + np.sum(timestamp)/1000)) + + # Print shape of acquisition matrix for one repetition + print(f'Partial acquisition matrix dimensions:' + f'{data.shape}') + print() + + acquisition_params.update_timings(timestamps, measurement_time) + # Real time between each spectrum acquisition by the spectrometer + print('Complete acquisition done') + print('Spectra acquired: {}'.format(acquisition_params.acquired_spectra)) + print('Total acquisition time: {0:.2f} s'.format(acquisition_params.total_spectrometer_acquisition_time_s)) + + # delete acquisition with black pattern (white for the camera) + if camPar.insert_patterns == 1: + black_pattern_index = np.where(acquisition_params.patterns_wp == -1) + # print('index of white patterns :') + # print(black_pattern_index[0:38]) + if acquisition_params.patterns_wp.shape == acquisition_params.patterns.shape: + acquisition_params.patterns = np.delete(acquisition_params.patterns, black_pattern_index) + spectral_data = np.delete(spectral_data, black_pattern_index, axis = 0) + acquisition_params.timestamps = np.delete(acquisition_params.timestamps, black_pattern_index[1:]) + acquisition_params.measurement_time = np.delete(acquisition_params.measurement_time, black_pattern_index) + acquisition_params.acquired_spectra = len(acquisition_params.patterns) + + _save_acquisition_2arms(metadata, DMD_params, spectrometer_params, camPar, + acquisition_params, spectral_data) + + # Joining processes and closing queues + if reconstruct == True: + queue_to_recon.put('kill') # Sends a message to stop reconstruction + recon_process.join() + queue_to_recon.close() + plot_process.join() + queue_reconstructed.close() + + maxi = np.amax(spectral_data[0,:]) + print('------------------------------------------------') + print('maximum in the spectrum = ' + str(maxi)) + print('------------------------------------------------') + if maxi >= 65535: + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + print('!!!!! warning, spectrum saturation !!!!!!!!') + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + + return spectral_data + + +def setup_tuneSpectro(spectrometer, + DMD, + DMD_initial_memory, + pattern_to_display, + ti : float = 1, + zoom : int = 1, + xw_offset: int = 128, + yh_offset: int = 0, + mask_index : np.array = []): + """ Setup the hadrware to tune the spectrometer in live. The goal is to find + the integration time of the spectrometer, noise is around 700 counts, + saturation is equal to 2**16=65535 + + Args: + spectrometer (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y. + pattern_to_display (string): + display one pattern on the DMD to tune the spectrometer. Default is + white pattern + ti (float): + The integration time of the spectrometer during one scan in miliseconds. + Default is 1 ms. + zoom (int): + digital zoom on the DMD. Default is 1 + xw_offset (int): + offset of the pattern in the DMD for zoom > 1 in the width (x) direction + yh_offset (int): + offset of the pattern in the DMD for zoom > 1 in the heihgt (y) direction + mask_index (Union[np.ndarray, str], optional): + Array of `int` type corresponding to the index of the mask vector where + the value is egal to 1 + + return: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + """ + + data_folder_name = 'Tune' + data_name = 'test' + # all_path = func_path(data_folder_name, data_name) + + scan_mode = 'Walsh' + Np = 16 + source = '' + object_name = '' + + metadata = MetaData( + output_directory = '',#all_path.subfolder_path, + pattern_order_source = 'C:/openspyrit/spas/stats/pattern_order_' + scan_mode + '_' + str(Np) + 'x' + str(Np) + '.npz', + pattern_source = 'C:/openspyrit/spas/Patterns/' + scan_mode + '_' + str(Np) + 'x' + str(Np), + pattern_prefix = scan_mode + '_' + str(Np) + 'x' + str(Np), + experiment_name = data_name, + light_source = source, + object = object_name, + filter = '', + description = '' + ) + + acquisition_parameters = AcquisitionParameters( + pattern_compression = 1, + pattern_dimension_x = 16, + pattern_dimension_y = 16, + zoom = zoom, + xw_offset = xw_offset, + yh_offset = yh_offset, + mask_index = [] ) + + acquisition_parameters.pattern_amount = 1 + + spectrometer_params, DMD_params = setup( + spectrometer = spectrometer, + DMD = DMD, + DMD_initial_memory = DMD_initial_memory, + metadata = metadata, + acquisition_params = acquisition_parameters, + pattern_to_display = pattern_to_display, + integration_time = ti, + loop = True ) + + return metadata, spectrometer_params, DMD_params, acquisition_parameters + + +def displaySpectro(ava: Avantes, + DMD: ALP4, + metadata: MetaData, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + reconstruction_params: ReconstructionParameters = None + ): + """Perform a continousely acquisition on the spectrometer for optical tuning. + + Send a pattern on the DMD to project light on the spectrometer. The goal is + to have a look on the amplitude of the spectrum to tune the illumination to + avoid saturation (sat >= 65535) and noisy signal (amp <= 650). + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + wavelengths (List[float]): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + reconstruction_params (ReconstructionParameters): + Object containing parameters of the neural network to be loaded for + reconstruction. + """ + + loop = True # is to project continuously a unique pattern to tune the spectrometer + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + + spectral_data = np.zeros( + (acquisition_params.pattern_amount,pixel_amount), + dtype=np.float64) + + acquisition_params.acquired_spectra = 0 + + AcquisitionResults = _acquire_raw(ava, DMD, spectrometer_params, + DMD_params, acquisition_params, loop) + + (data, spectrum_index, timestamp, time, + start_measurement_time, saturation_detected) = AcquisitionResults + + time, timestamp = _calculate_elapsed_time( + start_measurement_time, time, timestamp) + + begin = acquisition_params.pattern_amount + end = 2 * acquisition_params.pattern_amount + spectral_data[begin:end] = data + + acquisition_params.acquired_spectra += spectrum_index + + acquisition_params.saturation_detected = saturation_detected + + +def check_ueye(func, *args, exp=0, raise_exc=True, txt=None): + """Check for bad input value + + Args: + ---------- + func : TYPE + the ueye function. + *args : TYPE + the input value. + exp : TYPE, optional + DESCRIPTION. The default is 0. + raise_exc : TYPE, optional + DESCRIPTION. The default is True. + txt : TYPE, optional + DESCRIPTION. The default is None. + + Raises + ------ + RuntimeError + DESCRIPTION. + + Returns + ------- + None. + """ + + ret = func(*args) + if not txt: + txt = "{}: Expected {} but ret={}!".format(str(func), exp, ret) + if ret != exp: + if raise_exc: + raise RuntimeError(txt) + else: + logging.critical(txt) + + +def stopCapt_DeallocMem(camPar): + """Stop capture and deallocate camera memory if need to change AOI + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns: + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + print('video stop successful') + else: + print('problem to stop the video') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + print('deallocate memory successful') + else: + print('Problem to deallocate memory of the camera') + + return camPar + + +def stopCapt_DeallocMem_ExitCam(camPar): + """Stop capture, deallocate camera memory if need to change AOI and disconnect the camera + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns: + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + print('video stop successful') + else: + print('problem to stop the video') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + print('deallocate memory successful') + else: + print('Problem to deallocate memory of the camera') + + if camPar.Exit == 2: + nRet = ueye.is_ExitCamera(camPar.hCam) + if nRet == ueye.IS_SUCCESS: + camPar.Exit = 0 + print('Camera disconnected') + else: + print('Problem to disconnect camera, need to restart spyder') + + return camPar + + +class ImageBuffer: + """A class to allocate buffer in the camera memory + """ + + pcImageMemory = None + MemID = None + width = None + height = None + nbitsPerPixel = None + + +def imageQueue(camPar): + """Create Imagequeue / Allocate 3 ore more buffers depending on the framerate / Initialize Image queue + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns: + ------- + None. + + """ + + sleep(1) # is required (delay of 1s was not optimized!!) + buffers = [] + for y in range(10): + buffers.append(ImageBuffer()) + + for x in range(len(buffers)): + buffers[x].nbitsPerPixel = camPar.nBitsPerPixel # RAW8 + buffers[x].height = camPar.rectAOI.s32Height # sensorinfo.nMaxHeight + buffers[x].width = camPar.rectAOI.s32Width # sensorinfo.nMaxWidth + buffers[x].MemID = ueye.int(0) + buffers[x].pcImageMemory = ueye.c_mem_p() + check_ueye(ueye.is_AllocImageMem, camPar.hCam, buffers[x].width, buffers[x].height, buffers[x].nbitsPerPixel, + buffers[x].pcImageMemory, buffers[x].MemID) + check_ueye(ueye.is_AddToSequence, camPar.hCam, buffers[x].pcImageMemory, buffers[x].MemID) + + check_ueye(ueye.is_InitImageQueue, camPar.hCam, ueye.c_int(0)) + if camPar.trigger_mode == 'soft': + check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_SOFTWARE) + elif camPar.trigger_mode == 'hard': + check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_LO_HI) + + +def prepareCam(camPar, metadata): + """Prepare the IDS monochrome camera before acquisition + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + + Returns: + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + """ + cam_path = metadata.output_directory + '\\' + metadata.experiment_name + '_video.' + camPar.vidFormat + strFileName = ueye.c_char_p(cam_path.encode('utf-8')) + + if camPar.vidFormat == 'avi': + # print('Video format : AVI') + camPar.avi = ueye.int() + nRet = ueye_tools.isavi_InitAVI(camPar.avi, camPar.hCam) + # print("isavi_InitAVI") + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_InitAVI ERROR") + + nRet = ueye_tools.isavi_SetImageSize(camPar.avi, camPar.m_nColorMode, camPar.rectAOI.s32Width , camPar.rectAOI.s32Height, 0, 0, 0) + nRet = ueye_tools.isavi_SetImageQuality(camPar.avi, 100) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_SetImageQuality ERROR") + + nRet = ueye_tools.isavi_OpenAVI(camPar.avi, strFileName) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_OpenAVI ERROR") + print('Error code = ' + str(nRet)) + print('Certainly, it is a problem with the file name, Avoid special character like "µ" or try to redcue its size') + + nRet = ueye_tools.isavi_SetFrameRate(camPar.avi, camPar.fps) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_SetFrameRate ERROR") + nRet = ueye_tools.isavi_StartAVI(camPar.avi) + # print("isavi_StartAVI") + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_StartAVI ERROR") + + + elif camPar.vidFormat == 'bin': + camPar.punFileID = ueye.c_uint() + nRet = ueye_tools.israw_InitFile(camPar.punFileID, ueye_tools.IS_FILE_ACCESS_MODE_WRITE) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("INIT RAW FILE ERROR") + + nRet = ueye_tools.israw_SetImageInfo(camPar.punFileID, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("SET IMAGE INFO ERROR") + + if nRet == ueye.IS_SUCCESS: + # print('initFile ok') + # print('SetImageInfo ok') + nRet = ueye_tools.israw_OpenFile(camPar.punFileID, strFileName) + # if nRet == ueye.IS_SUCCESS: + # # print('OpenFile success') + + # --------------------------------------------------------- + # Activates the camera's live video mode (free run mode) + # --------------------------------------------------------- + nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) + + if nRet != ueye.IS_SUCCESS: + print("is_CaptureVideo ERROR") + else: + camPar.camActivated = 1 + + return camPar + + +def runCam_thread(camPar, start_chrono): + """Acquire video with the IDS monochrome camera in a thread + + Parameters: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + start_chrono : int + to save a delay for each acquisition frame of the video. + + Returns: + ------- + None. + """ + + imageinfo = ueye.UEYEIMAGEINFO() + current_buffer = ueye.c_mem_p() + current_id = ueye.int() + # inc = 0 + entier_old = 0 + # time.sleep(0.01) + while True: + nret = ueye.is_WaitForNextImage(camPar.hCam, camPar.timeout, current_buffer, current_id) + if nret == ueye.IS_SUCCESS: + check_ueye(ueye.is_GetImageInfo, camPar.hCam, current_id, imageinfo, ueye.sizeof(imageinfo)) + start_time = time.time() + counter = start_time - start_chrono + camPar.time_array.append(counter) + if camPar.vidFormat == 'avi': + nRet = ueye_tools.isavi_AddFrame(camPar.avi, current_buffer) + elif camPar.vidFormat == 'bin': + nRet = ueye_tools.israw_AddFrame(camPar.punFileID, current_buffer, imageinfo.u64TimestampDevice) + + check_ueye(ueye.is_UnlockSeqBuf, camPar.hCam, current_id, current_buffer) + else: + print('Thread finished') + break + + +def stopCam(camPar): + """To stop the acquisition of the video + + Parameters + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + + if camPar.vidFormat == 'avi': + ueye_tools.isavi_StopAVI(camPar.hCam) + ueye_tools.isavi_CloseAVI(camPar.hCam) + ueye_tools.isavi_ExitAVI(camPar.hCam) + elif camPar.vidFormat == 'bin': + ueye_tools.israw_CloseFile(camPar.punFileID) + ueye_tools.israw_ExitFile(camPar.punFileID) + camPar.punFileID = ueye.c_uint() + + return camPar + + +def disconnect(ava: Optional[Avantes]=None, + DMD: Optional[ALP4]=None): + """Disconnect spectrometer and DMD. + + Disconnects equipments trying to stop a running pattern sequence (possibly + blocking correct functioning) and trying to free DMD memory to avoid errors + in later acqusitions. + + Args: + ava (Avantes, optional): + Connected spectrometer (Avantes object). Defaults to None. + DMD (ALP4, optional): + Connected DMD. Defaults to None. + """ + + if ava is not None: + ava.disconnect() + print('Spectro disconnected') + + if DMD is not None: + + # Stop the sequence display + DMD.Halt() + + # Free the sequence from the onboard memory (if any is present) + if (DMD.Seqs): + DMD.FreeSeq() + + DMD.Free() + print('DMD disconnected') + + +def disconnect_2arms(ava: Optional[Avantes]=None, + DMD: Optional[ALP4]=None, + camPar=None): + """Disconnect spectrometer, DMD and the IDS monochrome camera. + + Disconnects equipments trying to stop a running pattern sequence (possibly + blocking correct functioning) and trying to free DMD memory to avoid errors + in later acqusitions. + + Args: + ava (Avantes, optional): + Connected spectrometer (Avantes object). Defaults to None. + DMD (ALP4, optional): + Connected DMD. Defaults to None. + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + + if ava is not None: + ava.disconnect() + print('Spectro disconnected') + + if DMD is not None: + # Stop the sequence display + try: + DMD.Halt() + # Free the sequence from the onboard memory (if any is present) + if (DMD.Seqs): + DMD.FreeSeq() + + DMD.Free() + print('DMD disconnected') + + except: + print('probelm to Halt the DMD') + + + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + else: + print('Problem to stop video, need to restart spyder') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + else: + print('Problem to deallocate camera memory, need to restart spyder') + + + if camPar.Exit == 1 or camPar.Exit == 2: + nRet = ueye.is_ExitCamera(camPar.hCam) + if nRet == ueye.IS_SUCCESS: + camPar.Exit = 0 + print('Camera disconnected') + else: + print('Problem to disconnect camera, need to restart spyder') + + +def _init_CAM(): + """ + Initialize and connect to the IDS camera. + + Returns: + CAM: a structure containing the parameters of the IDS camera + """ + camPar = CAM(hCam = ueye.HIDS(0), + sInfo = ueye.SENSORINFO(), + cInfo = ueye.CAMINFO(), + nBitsPerPixel = ueye.INT(8), + m_nColorMode = ueye.INT(), + bytes_per_pixel = int( ueye.INT(8)/ 8), + rectAOI = ueye.IS_RECT(), + pcImageMemory = ueye.c_mem_p(), + MemID = ueye.int(), + pitch = ueye.INT(), + fps = float(), + gain = int(), + gainBoost = str(), + gamma = float(), + exposureTime = float(), + blackLevel = int(), + camActivated = bool(), + pixelClock = ueye.uint(), + bandwidth = float(), + Memory = bool(), + Exit = int(), + vidFormat = str(), + gate_period = int(), + trigger_mode = str(), + avi = ueye.int(), + punFileID = ueye.c_uint(), + timeout = int(), + time_array = [], + int_time_spect = float(), + black_pattern_num = int(), + insert_patterns = bool(), + acq_mode = str(), + ) + + # # Camera Initialization --- + ### Starts the driver and establishes the connection to the camera + nRet = ueye.is_InitCamera(camPar.hCam, None) + if nRet != ueye.IS_SUCCESS: + print("is_InitCamera ERROR") + + ### Reads out the data hard-coded in the non-volatile camera memory and writes it to the data structure that cInfo points to + nRet = ueye.is_GetCameraInfo(camPar.hCam, camPar.cInfo) + if nRet != ueye.IS_SUCCESS: + print("is_GetCameraInfo ERROR") + + ### You can query additional information about the sensor type used in the camera + nRet = ueye.is_GetSensorInfo(camPar.hCam, camPar.sInfo) + if nRet != ueye.IS_SUCCESS: + print("is_GetSensorInfo ERROR") + + ### set camera parameters to default values + nRet = ueye.is_ResetToDefault(camPar.hCam) + if nRet != ueye.IS_SUCCESS: + print("is_ResetToDefault ERROR") + + ### Set display mode to DIB + nRet = ueye.is_SetDisplayMode(camPar.hCam, ueye.IS_SET_DM_DIB) + + ### Set the right color mode + if int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_BAYER: + # setup the color depth to the current windows setting + ueye.is_GetColorDepth(camPar.hCam, camPar.nBitsPerPixel, camPar.m_nColorMode) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_CBYCRY: + # for color camera models use RGB32 mode + camPar.m_nColorMode = ueye.IS_CM_BGRA8_PACKED + camPar.nBitsPerPixel = ueye.INT(32) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_MONOCHROME: + # for color camera models use RGB32 mode + camPar.m_nColorMode = ueye.IS_CM_MONO8 + camPar.nBitsPerPixel = ueye.INT(8) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + else: + # for monochrome camera models use Y8 mode + camPar.m_nColorMode = ueye.IS_CM_MONO8 + camPar.nBitsPerPixel = ueye.INT(8) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + # print("else") + + ### Get the AOI (Area Of Interest) + sizeofrectAOI = ueye.c_uint(4*4) + nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_GET_AOI, camPar.rectAOI, sizeofrectAOI) + if nRet != ueye.IS_SUCCESS: + print("AOI getting ERROR") + + camPar.camActivated = 0 + + # Get current pixel clock + getpixelclock = ueye.UINT(0) + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, + ueye.sizeof(getpixelclock)) + + camPar.pixelClock = getpixelclock + # print('pixel clock = ' + str(getpixelclock) + ' MHz') + + # get the bandwidth (in MByte/s) + camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) + + camPar.Exit = 1 + + print('IDS camera connected') + + return camPar + + +def captureVid(camPar): + """ + Allocate memory and begin video capture of the IDS camera + + Args: + camPar : a structure containing the parameters of the IDS camera. + + Returns: + camPar : a structure containing the parameters of the IDS camera. + """ + camPar = stopCapt_DeallocMem_ExitCam(camPar) + + if camPar.Exit == 0: + camPar = _init_CAM() + camPar.Exit = 1 + + + ### Set the AOI + sizeofrectAOI = ueye.c_uint(4*4) + nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_SET_AOI, camPar.rectAOI, sizeofrectAOI) + if nRet != ueye.IS_SUCCESS: + print("AOI setting ERROR") + + width = camPar.rectAOI.s32Width + height = camPar.rectAOI.s32Height + + ### Allocates an image memory for an image having its dimensions defined by width and height and its color depth defined by nBitsPerPixel + nRet = ueye.is_AllocImageMem(camPar.hCam, width, height, camPar.nBitsPerPixel, camPar.pcImageMemory, camPar.MemID) + if nRet != ueye.IS_SUCCESS: + print("is_AllocImageMem ERROR") + else: + # Makes the specified image memory the active memory + camPar.Memory = 1 + nRet = ueye.is_SetImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet != ueye.IS_SUCCESS: + print("is_SetImageMem ERROR") + else: + # Set the desired color mode + nRet = ueye.is_SetColorMode(camPar.hCam, camPar.m_nColorMode) + + + ### Activates the camera's live video mode (free run mode) + nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) + if nRet != ueye.IS_SUCCESS: + print("is_CaptureVideo ERROR") + + + ### Enables the queue mode for existing image memory sequences + nRet = ueye.is_InquireImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID, width, height, camPar.nBitsPerPixel, camPar.pitch) + if nRet != ueye.IS_SUCCESS: + print("is_InquireImageMem ERROR") + + camPar.camActivated = 1 + + return camPar + +def setup_cam(camPar, pixelClock, fps, Gain, gain_boost, nGamma, ExposureTime, black_level): + """ + Set and read the camera parameters + + Args: + pixelClock = [118, 237 or 474] (MHz) + fps: fps boundary => [1 - No Value] sup limit depend of image size (216 fps for 768x544 pixels for example) + Gain: Gain boundary => [0 100] + gain_boost: 'ON' set "ON" to activate gain boost, "OFF" to deactivate + nGamma: Gamma boundary => [1 - 2.2] + ExposureTime: Exposure time (ms) boundarye => [0.032 - 56.221] + black_level: Black Level boundary => [0 255] + + returns: + CAM: a structure containing the parameters of the IDS camera + """ + # It is necessary to execute twice this code to take account the parameter modification + for i in range(2): + ############################### Set Pixel Clock ############################### + ### Get range of pixel clock, result : range = [118 474] MHz (Inc = 0) + getpixelclock = ueye.UINT(0) + newpixelclock = ueye.UINT(0) + newpixelclock.value = pixelClock + PixelClockRange = (ueye.int * 3)() + + # Get pixel clock range + nRet = ueye.is_PixelClock(camPar.hCam, ueye.IS_PIXELCLOCK_CMD_GET_RANGE, PixelClockRange, ueye.sizeof(PixelClockRange)) + if nRet == ueye.IS_SUCCESS: + nPixelClockMin = PixelClockRange[0] + nPixelClockMax = PixelClockRange[1] + nPixelClockInc = PixelClockRange[2] + + # Set pixel clock + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_SET, newpixelclock, + ueye.sizeof(newpixelclock)) + # Get current pixel clock + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, + ueye.sizeof(getpixelclock)) + + camPar.pixelClock = getpixelclock.value + if i == 1: + print(' pixel clock = ' + str(getpixelclock) + ' MHz') + if getpixelclock == 118: + if i == 1: + print('Pixel clcok blocked to 118 MHz, it is necessary to unplug the camera if not desired') + # get the bandwidth (in MByte/s) + camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) + if i == 1: + print(' Bandwidth = ' + str(camPar.bandwidth) + ' MB/s') + ############################### Set FrameRate ################################# + ### Read current FrameRate + dblFPS_init = ueye.c_double() + nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_init) + if nRet != ueye.IS_SUCCESS: + print("FrameRate getting ERROR") + else: + dblFPS_eff = dblFPS_init + if i == 1: + print(' current FPS = '+str(round(dblFPS_init.value*100)/100) + ' fps') + if fps < 1: + fps = 1 + if i == 1: + print('FPS exceed lower limit >= 1') + + dblFPS = ueye.c_double(fps) + if (dblFPS.value < dblFPS_init.value-0.01) | (dblFPS.value > dblFPS_init.value+0.01): + newFPS = ueye.c_double() + nRet = ueye.is_SetFrameRate(camPar.hCam, dblFPS, newFPS) + time.sleep(1) + if nRet != ueye.IS_SUCCESS: + print("FrameRate setting ERROR") + else: + if i == 1: + print(' new FPS = '+str(round(newFPS.value*100)/100) + ' fps') + ### Read again the effective FPS / depend of the image size, 17.7 fps is not possible with the entire image size (ie 2076x3088) + dblFPS_eff = ueye.c_double() + nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_eff) + if nRet != ueye.IS_SUCCESS: + print("FrameRate getting ERROR") + else: + if i == 1: + print(' effective FPS = '+str(round(dblFPS_eff.value*100)/100) + ' fps') + ############################### Set GAIN ###################################### + #### Maximum gain is depending of the sensor. Convertion gain code to gain to limit values from 0 to 100 + # gain_code = gain * slope + b + gain_max_code = 1450 + gain_min_code = 100 + gain_max = 100 + gain_min = 0 + slope = (gain_max_code-gain_min_code)/(gain_max-gain_min) + b = gain_min_code + #### Read gain setting + current_gain_code = ueye.c_int() + current_gain_code = ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_GET_MASTER_GAIN_FACTOR, current_gain_code) + current_gain = round((current_gain_code-b)/slope) + + if i == 1: + print(' current GAIN = '+str(current_gain)) + gain_eff = current_gain + + ### Set new gain value + gain = ueye.c_int(Gain) + if gain.value != current_gain: + if gain.value < 0: + gain = ueye.c_int(0) + if i == 1: + print('Gain exceed lower limit >= 0') + elif gain.value > 100: + gain = ueye.c_int(100) + if i == 1: + print('Gain exceed upper limit <= 100') + gain_code = ueye.c_int(round(slope*gain.value+b)) + + ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_SET_MASTER_GAIN_FACTOR, gain_code) + new_gain = round((gain_code-b)/slope) + + if i == 1: + print(' new GAIN = '+str(new_gain)) + gain_eff = new_gain + ############################### Set GAIN Boost ################################ + ### Read current state of the gain boost + current_gain_boost_bool = ueye.is_SetGainBoost(camPar.hCam, ueye.IS_GET_GAINBOOST) + if nRet != ueye.IS_SUCCESS: + print("Gain boost ERROR") + if current_gain_boost_bool == 0: + current_gain_boost = 'OFF' + elif current_gain_boost_bool == 1: + current_gain_boost = 'ON' + + if i == 1: + print('current Gain boost mode = ' + current_gain_boost) + + ### Set the state of the gain boost + if gain_boost != current_gain_boost: + if gain_boost == 'OFF': + nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_OFF) + print(' new Gain Boost : OFF') + + elif gain_boost == 'ON': + nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_ON) + print(' new Gain Boost : ON') + + if nRet != ueye.IS_SUCCESS: + print("Gain boost setting ERROR") + ############################### Set Gamma ##################################### + ### Check boundary of Gamma + if nGamma > 2.2: + nGamma = 2.2 + if i == 1: + print('Gamma exceed upper limit <= 2.2') + elif nGamma < 1: + nGamma = 1 + if i == 1: + print('Gamma exceed lower limit >= 1') + ### Read current Gamma + c_nGamma_init = ueye.c_void_p() + sizeOfnGamma = ueye.c_uint(4) + nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_GET, c_nGamma_init, sizeOfnGamma) + if nRet != ueye.IS_SUCCESS: + print("Gamma getting ERROR") + else: + if i == 1: + print(' current Gamma = ' + str(c_nGamma_init.value/100)) + ### Set Gamma + c_nGamma = ueye.c_void_p(round(nGamma*100)) # need to multiply by 100 [100 - 220] + if c_nGamma_init.value != c_nGamma.value: + nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_SET, c_nGamma, sizeOfnGamma) + if nRet != ueye.IS_SUCCESS: + print("Gamma setting ERROR") + else: + if i == 1: + print(' new Gamma = '+str(c_nGamma.value/100)) + ############################### Set Exposure time ############################# + ### Read current Exposure Time + getExposure = ueye.c_double() + sizeOfpParam = ueye.c_uint(8) + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE, getExposure, sizeOfpParam) + if nRet == ueye.IS_SUCCESS: + getExposure.value = round(getExposure.value*1000)/1000 + + if i == 1: + print(' current Exposure Time = ' + str(getExposure.value) + ' ms') + ### Get minimum Exposure Time + minExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MIN, minExposure, sizeOfpParam) + ### Get maximum Exposure Time + maxExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MAX, maxExposure, sizeOfpParam) + ### Get increment Exposure Time + incExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_INC, incExposure, sizeOfpParam) + ### Set new Exposure Time + setExposure = ueye.c_double(ExposureTime) + if setExposure.value > maxExposure.value: + setExposure.value = maxExposure.value + if i == 1: + print('Exposure Time exceed upper limit <= ' + str(maxExposure.value)) + elif setExposure.value < minExposure.value: + setExposure.value = minExposure.value + if i == 1: + print('Exposure Time exceed lower limit >= ' + str(minExposure.value)) + + if (setExposure.value < getExposure.value-incExposure.value/2) | (setExposure.value > getExposure.value+incExposure.value/2): + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_SET_EXPOSURE, setExposure, sizeOfpParam) + if nRet != ueye.IS_SUCCESS: + print("Exposure Time ERROR") + else: + if i == 1: + print(' new Exposure Time = ' + str(round(setExposure.value*1000)/1000) + ' ms') + ############################### Set Black Level ############################### + current_black_level_c = ueye.c_uint() + sizeOfBlack_level = ueye.c_uint(4) + ### Read current Black Level + nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_GET_OFFSET, current_black_level_c, sizeOfBlack_level) + if nRet != ueye.IS_SUCCESS: + print("Black Level getting ERROR") + else: + if i == 1: + print(' current Black Level = ' + str(current_black_level_c.value)) + + ### Set Black Level + if black_level > 255: + black_level = 255 + if i == 1: + print('Black Level exceed upper limit <= 255') + if black_level < 0: + black_level = 0 + if i == 1: + print('Black Level exceed lower limit >= 0') + + black_level_c = ueye.c_uint(black_level) + if black_level != current_black_level_c.value : + nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_SET_OFFSET, black_level_c, sizeOfBlack_level) + if nRet != ueye.IS_SUCCESS: + print("Black Level setting ERROR") + else: + if i == 1: + print(' new Black Level = ' + str(black_level_c.value)) + + + camPar.fps = round(dblFPS_eff.value*100)/100 + camPar.gain = gain_eff + camPar.gainBoost = gain_boost + camPar.gamma = c_nGamma.value/100 + camPar.exposureTime = round(setExposure.value*1000)/1000 + camPar.blackLevel = black_level_c.value + + return camPar + + +def snapshot(camPar, pathIDSsnapshot, pathIDSsnapshot_overview): + """ + Snapshot of the IDS camera + + Args: + CAM: a structure containing the parameters of the IDS camera + """ + array = ueye.get_data(camPar.pcImageMemory, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel, camPar.pitch, copy=False) + + # ...reshape it in an numpy array... + frame = np.reshape(array,(camPar.rectAOI.s32Height.value, camPar.rectAOI.s32Width.value))#, camPar.bytes_per_pixel)) + + with pathIDSsnapshot.open('wb') as f: #('ab') as f: #(pathname, mode='w', encoding='utf-8') as f: #('ab') as f: + np.save(f,frame) + + maxi = np.amax(frame) + if maxi == 0: + maxi = 1 + im = Image.fromarray(frame*math.floor(255/maxi)) + im.save(pathIDSsnapshot_overview) + + maxi = np.amax(frame) + # print() + # print('frame max = ' + str(maxi)) + # print('frame min = ' + str(np.amin(frame))) + if maxi >= 255: + print('Saturation detected') + + plt.figure + plt.imshow(frame)#, cmap='gray', vmin=mini, vmax=maxi) + plt.colorbar(); + plt.show() + + \ No newline at end of file diff --git a/spas/acquisition_SPC2D.py b/spas/acquisition_SPC2D.py new file mode 100644 index 0000000..1c39899 --- /dev/null +++ b/spas/acquisition_SPC2D.py @@ -0,0 +1,2878 @@ +# -*- coding: utf-8 -*- +__author__ = 'Guilherme Beneti Martins' + +"""Acquisition utility functions. + + Acquisition module is a generic module that call function in different setup (SPC2D_1arm, SPC2D_2arms, SCP1D and SPIM) + +""" + +import warnings +from time import sleep, perf_counter_ns +from typing import NamedTuple, Tuple, List, Optional +from collections import namedtuple +from pathlib import Path +from multiprocessing import Process, Queue +import shutil +import math + +import numpy as np +from PIL import Image +##### DLL for the DMD +try: + from ALP4 import ALP4, ALP_FIRSTFRAME, ALP_LASTFRAME + from ALP4 import ALP_AVAIL_MEMORY, ALP_DEV_DYN_SYNCH_OUT1_GATE, tAlpDynSynchOutGate + # print('ALP4 is ok in Acquisition file') +except: + class ALP4: + pass +##### DLL for the spectrometer Avantes +try: + from msl.equipment import EquipmentRecord, ConnectionRecord, Backend + from msl.equipment.resources.avantes import MeasureCallback, Avantes +except: + pass + +from tqdm import tqdm +from spas.metadata import DMDParameters, MetaData, AcquisitionParameters +from spas.metadata import SpectrometerParameters, save_metadata, CAM, save_metadata_2arms +from spas.reconstruction_nn import reconstruct_process, plot_recon, ReconstructionParameters + +# DLL for the IDS CAMERA +try: + from pyueye import ueye, ueye_tools +except: + print('ueye DLL not installed') + +from matplotlib import pyplot as plt +from IPython import get_ipython +import ctypes as ct +import logging +import time +import threading + + +def _init_spectrometer() -> Avantes: + """Initialize and connect to an Avantes Spectrometer. + + Returns: + Avantes: Avantes spectrometer. + """ + + dll_path = Path(__file__).parent.parent.joinpath( + 'lib/avaspec3/avaspecx64.dll') + + record = EquipmentRecord( + manufacturer='Avantes', + model='AvaSpec-UCLS2048BCL-EVO-RS', # update for your device + serial='2011126U1', # update for your device + connection=ConnectionRecord( + address=f'SDK::{dll_path}', + backend=Backend.MSL)) + + # Initialize Avantes SDK and establish the connection to the spectrometer + ava = record.connect() + print('Spectrometer connected') + + return ava + + +def _init_DMD(dmd_lib_version: str = '4.2') -> Tuple[ALP4, int]: + """Initialize a DMD and clean its allocated memory from a previous use. + + Args: + dmd_lib_version [str]: the version of the DMD library + + Returns: + Tuple[ALP4, int]: Tuple containing initialized DMD object and DMD + initial available memory. + """ + + # Initializing DMD + stop_init = False + if dmd_lib_version == '4.1': + print('dmd lib version = ' + dmd_lib_version + ' not installed, please, install it at the location : "openspyrit/spas/alpV41"') + stop_init = True + elif dmd_lib_version == '4.2': + dll_path = Path(__file__).parent.parent.joinpath('lib/alpV42').__str__() + DMD = ALP4(version='4.2',libDir=dll_path) + elif dmd_lib_version == '4.3': + dll_path = Path(__file__).parent.parent.joinpath('lib/alpV43').__str__() + DMD = ALP4(version='4.3',libDir=dll_path) + else: + print('unknown version of dmd library') + stop_init = True + + if stop_init == False: + DMD.Initialize(DeviceNum=None) + + #print(f'DMD initial available memory: {DMD.DevInquire(ALP_AVAIL_MEMORY)}') + print('DMD connected') + + return DMD, DMD.DevInquire(ALP_AVAIL_MEMORY) + else: + print('DMD initialisation aborted') + + +def init(dmd_lib_version: str = '4.2') -> Tuple[Avantes, ALP4, int]: + """Call functions to initialize spectrometer and DMD. + + Args: + dmd_lib_version [str]: the version of the DMD library + + Returns: + Tuple[Avantes, ALP4, int]: Tuple containing equipments and DMD initial + available memory: + Avantes: + Connected spectrometer object. + ALP4: + Connected DMD object. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + """ + + DMD, DMD_initial_memory = _init_DMD(dmd_lib_version) + return _init_spectrometer(), DMD, DMD_initial_memory + + +def init_2arms(dmd_lib_version: str = '4.2') -> Tuple[Avantes, ALP4, int]: + """Call functions to initialize spectrometer and DMD. + + Args: + dmd_lib_version [str]: the version of the DMD library + + Returns: + Tuple[Avantes, ALP4, int]: Tuple containing equipments and DMD initial + available memory: + Avantes: + Connected spectrometer object. + ALP4: + Connected DMD object. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + """ + + DMD, DMD_initial_memory = _init_DMD(dmd_lib_version) + camPar = _init_CAM() + return _init_spectrometer(), DMD, DMD_initial_memory, camPar + + +def _calculate_timings(integration_time: float = 1, + integration_delay: int = 0, + add_illumination_time: int = 300, + synch_pulse_delay: int = 0, + dark_phase_time: int = 44, + ) -> Tuple[int, int, int]: + """Calculate spectrometer and DMD dependant timings. + + Args: + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. + Default is 1 ms. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + Default is 0 us. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". Default is 365 us. + synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). Default is + 0 us. + dark_phase_time (int): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. Default is 44 us. + + Returns: + [Tuple]: DMD timings which depend on spectrometer's parameters. + synch_pulse_width: Duration of DMD's frame synch output pulse. Units + in microseconds. + illumination_time: Duration of the display of one pattern in a DMD + sequence. Units in microseconds. + picture_time: Time between the start of two consecutive pictures + (i.e. this parameter defines the image display rate). Units in + microseconds. + """ + + illumination_time = (integration_delay/1000 + integration_time*1000 + + add_illumination_time) + picture_time = illumination_time + dark_phase_time + synch_pulse_width = round(illumination_time/2 + synch_pulse_delay) + illumination_time = round(illumination_time) + picture_time = round(picture_time) + + return synch_pulse_width, illumination_time, picture_time + + +def _setup_spectrometer(ava: Avantes, + integration_time: float, + integration_delay: int, + start_pixel: int, + stop_pixel: int, + ) -> Tuple[SpectrometerParameters, List[float]]: + """Sets configurations in the spectrometer. + + Set all necessary configurations in the spectrometer preparing it for a + measurement. Creates SpectrometerData containing its metadata. Gets the + correct wavelengths depending on the selected pixels to be used. + + Args: + ava (Avantes): + Avantes spectrometer. + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + start_pixel (int): + Initial pixel data received from spectrometer. + stop_pixel (int, optional): + Last pixel data received from spectrometer. If None, then its value + will be determined from the amount of available pixels in the + spectrometer. + Returns: + Tuple[SpectrometerParameters, List[float]]: Metadata and wavelengths. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata. + wavelengths (List): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + """ + + spectrometer_detector = ava.SensType( + ava.get_parameter().m_Detector.m_SensorType).name + + # Get the number of pixels that the spectrometer has + initial_available_pixels = ava.get_num_pixels() + + # print(f'\nThe spectrometer has {initial_available_pixels} pixels') + + # Enable the 16-bit AD converter for High-Resolution + ava.use_high_res_adc(True) + + # Creating configuration block + measconfig = ava.MeasConfigType() + + measconfig.m_StartPixel = start_pixel + + if stop_pixel is None: + measconfig.m_StopPixel = initial_available_pixels - 1 + else: + measconfig.m_StopPixel = stop_pixel + + measconfig.m_IntegrationTime = integration_time + measconfig.m_IntegrationDelay = integration_delay + measconfig.m_NrAverages = 1 + + dark_correction = ava.DarkCorrectionType() + dark_correction.m_Enable = 0 + dark_correction.m_ForgetPercentage = 100 + measconfig.m_CorDynDark = dark_correction + + smoothing = ava.SmoothingType() + smoothing.m_SmoothPix = 0 + smoothing.m_SmoothModel = 0 + measconfig.m_Smoothing = smoothing + + measconfig.m_SaturationDetection = 1 + + trigger = ava.TriggerType() + trigger.m_Mode = 2 + trigger.m_Source = 0 + trigger.m_SourceType = 0 + measconfig.m_Trigger = trigger + + control_settings = ava.ControlSettingsType() + control_settings.m_StrobeControl = 0 + control_settings.m_LaserDelay = 0 + control_settings.m_LaserWidth = 0 + control_settings.LaserWaveLength = 0.00 + control_settings.m_StoreToRam = 0 + measconfig.m_Control = control_settings + + ava.prepare_measure(measconfig) + + spectrometer_params = SpectrometerParameters( + high_resolution=True, + initial_available_pixels=initial_available_pixels, + detector=spectrometer_detector, + configs=measconfig, + version_info=ava.get_version_info()) + + # Get the wavelength corresponding to each pixel + wavelengths = ava.get_lambda()[ + spectrometer_params.start_pixel:spectrometer_params.stop_pixel+1] + + return spectrometer_params, np.asarray(wavelengths) + + +def _setup_DMD(DMD: ALP4, + add_illumination_time: int, + initial_memory: int + ) -> DMDParameters: + """Create DMD metadata. + + Creates basic DMD metadata, but leaves most of its fields empty to be set + later. Sets up the initial free memory present in the DMD. + This function's name is used to create cohesion between spectrometer and DMD + related functions. + + Args: + DMD (ALP4): + Connected DMD object. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + initial_memory (int): + Initial memory available in DMD after initialization. + + Returns: + DMDParameters: + DMD metadata object. + """ + + return DMDParameters( + add_illumination_time_us=add_illumination_time, + initial_memory=initial_memory, + DMD=DMD) + + +def _sequence_limits(DMD: ALP4, + pattern_compression: int, + sequence_lenght: int, + pos_neg: bool = True) -> int: + """Set sequence limits based on a sequence already uploaded to DMD. + + Args: + DMD (ALP4): + Connected DMD object. + pattern_compression (int): + Percentage of total available patterns to be present in an + acquisition sequence. + sequence_lenght (int): + Amount of patterns present in DMD memory. + pos_neg (bool): + Boolean indicating if sequence is formed by positive and negative + patterns. Default is True. + + Returns: + frames (int): + Amount of patterns to be used from a sequence based on the pattern + compression. + """ + + # Choosing beggining of the sequence + # DMD.SeqControl(ALP_BITNUM, 1) + DMD.SeqControl(ALP_FIRSTFRAME, 0) + + # Choosing the end of the sequence + if (round(pattern_compression * sequence_lenght) % 2 == 0) or not (pos_neg): + frames = round(pattern_compression * sequence_lenght) + else: + frames = round(pattern_compression * sequence_lenght) + 1 + + DMD.SeqControl(ALP_LASTFRAME, frames - 1) + + return frames + + +def _update_sequence(DMD: ALP4, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + pattern_source: str, + pattern_prefix: str, + pattern_order: List[int], + bitplanes: int = 1): + """Send new complete pattern sequence to DMD. + + Args: + DMD (ALP4): + Connected DMD object. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + pattern_source (str): + Pattern source folder. + pattern_preffix (str): + Prefix used in pattern naming. + pattern_order (List[int]): + List of the pattern indices in a certain order for upload to DMD. + bitplanes (int, optional): + Pattern bitplanes. Defaults to 1. + """ + + import cv2 + + path_base = Path(pattern_source) + + seqId = DMD.SeqAlloc(nbImg=len(pattern_order), + bitDepth=bitplanes) + + zoom = acquisition_params.zoom + x_offset = acquisition_params.xw_offset + y_offset = acquisition_params.yh_offset + Np = acquisition_params.pattern_dimension_x + + dmd_height = DMD_params.display_height + dmd_width = DMD_params.display_width + len_im = int(dmd_height / zoom) + + t = perf_counter_ns() + + # for adaptative patterns into a ROI + apply_mask = False + mask_index = acquisition_params.mask_index + + if len(mask_index) > 0: + apply_mask = True + Npx = acquisition_params.pattern_dimension_x + Npy = acquisition_params.pattern_dimension_y + mask_element_nbr = len(mask_index) + x_mask_coord = acquisition_params.x_mask_coord + y_mask_coord = acquisition_params.y_mask_coord + x_mask_length = x_mask_coord[1] - x_mask_coord[0] + y_mask_length = y_mask_coord[1] - y_mask_coord[0] + + first_pass = True + for index,pattern_name in enumerate(tqdm(pattern_order, unit=' patterns', total=len(pattern_order))): + # read numpy patterns + path = path_base.joinpath(f'{pattern_prefix}_{pattern_name}.npy') + im = np.load(path) + + patterns = np.zeros((dmd_height, dmd_width), dtype=np.uint8) + + if apply_mask == True: # for adaptative patterns into a ROI + pat_mask_all = np.zeros(y_mask_length*x_mask_length) # initialize a vector of lenght = size of the cropped mask + pat_mask_all[mask_index] = im[:mask_element_nbr] #pat_re_vec[:mask_element_nbr] # put the pattern into the vector + pat_mask_all_mat = np.reshape(pat_mask_all, [y_mask_length, x_mask_length]) # reshape the vector into a matrix of the 2d cropped mask + # resize the matrix to the DMD size + pat_mask_all_mat_DMD = cv2.resize(pat_mask_all_mat, (int(dmd_height*x_mask_length/(Npx*zoom)), int(dmd_height*y_mask_length/(Npy*zoom))), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + first_pass = False + len_im3 = pat_mask_all_mat_DMD.shape + + patterns[y_offset:y_offset+len_im3[0], x_offset:x_offset+len_im3[1]] = pat_mask_all_mat_DMD + else: # send the entire square pattern without the mask + im_mat = np.reshape(im, [Np,Np]) + im_HD = cv2.resize(im_mat, (int(dmd_height/zoom), int(dmd_height/zoom)), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + len_im = im_HD.shape + first_pass = False + + patterns[y_offset:y_offset+len_im[0], x_offset:x_offset+len_im[1]] = im_HD + + # if pattern_name == 800: + # plt.figure() + # # plt.imshow(pat_c_re) + # # plt.imshow(pat_mask_all_mat) + # # plt.imshow(pat_mask_all_mat_DMD) + # plt.imshow(np.rot90(patterns,2)) + # plt.colorbar() + # plt.title('pattern n°' + str(pattern_name)) + + patterns = patterns.ravel() + + DMD.SeqPut( + imgData=patterns.copy(), + PicOffset=index, + PicLoad=1) + + print(f'\nTime for sending all patterns: ' + f'{(perf_counter_ns() - t)/1e+9} s') + + +def _setup_patterns(DMD: ALP4, + metadata: MetaData, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + cov_path: str = None, + pattern_to_display: str = 'white', + loop: bool = False) -> None: + """Read and send patterns to DMD. + + Reads patterns from a file and sends a percentage of them to the DMD, + considering positve and negative Hadamard patterns, which should be even in + number. + Prints time taken to read all patterns and send the requested ones + to DMD. + Updates available memory in DMD metadata object (DMD_params). + + Args: + DMD (ALP4): + Connected DMD object. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + loop (bool): + is to projet in loop, one or few patterns continously (see AlpProjStartCont + in the doc for more detail). Default is False + """ + + file = np.load(Path(metadata.pattern_order_source)) + pattern_order = file['pattern_order'] + pos_neg = file['pos_neg'] + + if loop == True: + pos_neg = False + if pattern_to_display == 'white': + pattern_order = np.array(pattern_order[0:1], dtype=np.int16) + elif pattern_to_display == 'black': + pattern_order = np.array(pattern_order[1:2], dtype=np.int16) + elif pattern_to_display == 'gray': + index = int(np.where(pattern_order == 1953)[0]) + print(index) + pattern_order = np.array(pattern_order[index:index+1], dtype=np.int16) + + bitplanes = 1 + DMD_params.bitplanes = bitplanes + + if (DMD_params.initial_memory - DMD.DevInquire(ALP_AVAIL_MEMORY) == + len(pattern_order)) and loop == False: + print('Reusing patterns from previous acquisition') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + else: + if (DMD.Seqs): + DMD.FreeSeq() + + _update_sequence(DMD, DMD_params, acquisition_params, metadata.pattern_source, metadata.pattern_prefix, + pattern_order, bitplanes) + print(f'DMD available memory after sequence allocation: ' + f'{DMD.DevInquire(ALP_AVAIL_MEMORY)}') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + acquisition_params.patterns = ( + pattern_order[0:acquisition_params.pattern_amount]) + + # Confirm memory allocated in DMD + DMD_params.update_memory(DMD.DevInquire(ALP_AVAIL_MEMORY)) + + +def _setup_patterns_2arms(DMD: ALP4, + metadata: MetaData, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + camPar: CAM, + cov_path: str = None) -> None: + """Read and send patterns to DMD. + + Reads patterns from a file and sends a percentage of them to the DMD, + considering positve and negative Hadamard patterns, which should be even in + number. + Prints time taken to read all patterns and send the requested ones + to DMD. + Updates available memory in DMD metadata object (DMD_params). + + Args: + DMD (ALP4): + Connected DMD object. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + cov_path (str): + Path to the covariance matrix used for reconstruction. + It must be a .npy (numpy) or .pt (pytorch) file. It is converted to + a torch tensor for reconstruction. + + """ + + file = np.load(Path(metadata.pattern_order_source)) + pattern_order = file['pattern_order'] + pattern_order = pattern_order.astype('int32') + + # copy the black pattern image (png) to the number = -1 + # black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.png' ) + black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.npy' ) + + if black_pattern_dest_path.is_file() == False: + # black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + + # str(camPar.black_pattern_num) + '.png' ) + black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + + str(camPar.black_pattern_num) + '.npy' ) + shutil.copyfile(black_pattern_orig_path, black_pattern_dest_path) + + + # add white patterns for the camera + if camPar.insert_patterns == 1: + inc = 0 + while True: + try: + pattern_order[inc] # except error from the end of array to stop the loop + if (inc % camPar.gate_period) == 0:#16) == 0: + pattern_order = np.insert(pattern_order, inc, -1) # double white pattern is required if integration time is shorter than 3.85 ms + if camPar.int_time_spect < 3.85: + pattern_order = np.insert(pattern_order, inc+1, -1) + if camPar.int_time_spect < 1.65: + pattern_order = np.insert(pattern_order, inc+2, -1) + if camPar.int_time_spect < 1: + pattern_order = np.insert(pattern_order, inc+1, -1) + if camPar.int_time_spect <= 0.6: + pattern_order = np.insert(pattern_order, inc+1, -1) + inc = inc + 1 + except: + # print('while loop finished') + break + + # if camPar.int_time_spect < 1.75: # add one pattern at the beginning of the sequence when the integration time of the spectrometer is shorter than 1.75 ms + # print('no interleaving') + # #pattern_order = np.insert(pattern_order, 0, -1) + # # pattern_order = np.insert(pattern_order, 0, -1) + if (len(pattern_order)%2) != 0: # Add one pattern at the end of the sequence if the pattern number is even + pattern_order = np.insert(pattern_order, len(pattern_order), -1) + print('pattern order is odd => a black image is automaticly insert, need to be deleted in the case for tuning the spectrometer') + + pos_neg = file['pos_neg'] + + bitplanes = 1 + DMD_params.bitplanes = bitplanes + + if (DMD_params.initial_memory - DMD.DevInquire(ALP_AVAIL_MEMORY) == + len(pattern_order)): + print('Reusing patterns from previous acquisition') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + else: + if (DMD.Seqs): + DMD.FreeSeq() + + _update_sequence(DMD, DMD_params, acquisition_params, metadata.pattern_source, metadata.pattern_prefix, + pattern_order, bitplanes) + print(f'DMD available memory after sequence allocation: ' + f'{DMD.DevInquire(ALP_AVAIL_MEMORY)}') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + acquisition_params.patterns = ( + pattern_order[0:acquisition_params.pattern_amount]) + + acquisition_params.patterns_wp = acquisition_params.patterns + + # Confirm memory allocated in DMD + DMD_params.update_memory(DMD.DevInquire(ALP_AVAIL_MEMORY)) + +def _setup_timings(DMD: ALP4, + DMD_params: DMDParameters, + picture_time: int, + illumination_time: int, + synch_pulse_delay: int, + synch_pulse_width: int, + trigger_in_delay: int, + add_illumination_time: int) -> None: + """Setup pattern sequence timings in DMD. + + Send previously user-defined plus calculated timings to DMD. + Updates DMD metadata with sequence and timing related data. + This function has no default values for timings and lets the burden of + setting them to the setup function. + + Args: + DMD (ALP4): + Connected DMD object. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + picture_time (int): + Time between the start of two consecutive pictures (i.e. this + parameter defines the image display rate). Units in microseconds. + illumination_time (int): + Duration of the display of one pattern in a DMD sequence. + Units in microseconds. + synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). + synch_pulse_width (int): + Duration of DMD's frame synch output pulse. Units in microseconds. + trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + """ + + DMD.SetTiming(illuminationTime=illumination_time, + pictureTime=picture_time, + synchDelay=synch_pulse_delay, + synchPulseWidth=synch_pulse_width, + triggerInDelay=trigger_in_delay) + + DMD_params.update_sequence_parameters(add_illumination_time, DMD=DMD) + + +def setup(spectrometer: Avantes, + DMD: ALP4, + DMD_initial_memory: int, + metadata: MetaData, + acquisition_params: AcquisitionParameters, + start_pixel: int = 0, + stop_pixel: Optional[int] = None, + integration_time: float = 1, + integration_delay: int = 0, + DMD_output_synch_pulse_delay: int = 0, + add_illumination_time: int = 356, + dark_phase_time: int = 44, + DMD_trigger_in_delay: int = 0, + pattern_to_display: str = 'white', + loop: bool = False + ) -> Tuple[SpectrometerParameters, DMDParameters]: + """Setup everything needed to start an acquisition. + + Sets all parameters for DMD, spectrometer, DMD patterns and DMD timings. + Must be called before every acquisition. + + Args: + spectrometer (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y. + start_pixel (int): + Initial pixel data received from spectrometer. Default is 0. + stop_pixel (int, optional): + Last pixel data received from spectrometer. Default is None if it + should be determined from the amount of available pixels in the + spectrometer. + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. Default + is 1 ms. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + Default is 0 us. + DMD_output_synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). Default is + 0 us. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". Default is 365 us. + dark_phase_time (int): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. Default is 44 us. + DMD_trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). Default is 0 us. + pattern_to_display (string): + display one pattern on the DMD to tune the spectrometer. Default is white + pattern + loop (bool): + is to projet in loop, one or few patterns continuously (see AlpProjStartCont + in the doc for more detail). Default is False + Raises: + ValueError: Sum of dark phase and additional illumination time is lower + than 400 us. + + Returns: + Tuple[SpectrometerParameters, DMDParameters, List]: Tuple containing DMD + and spectrometer relate metadata, as well as wavelengths. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + """ + + if loop == False: + path = Path(metadata.output_directory) + if not path.exists(): + path.mkdir() + + if dark_phase_time + add_illumination_time < 350: + raise ValueError(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' Must be greater than 350 µs.') + + elif dark_phase_time + add_illumination_time < 400: + warnings.warn(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' It is recomended to choose at least 400 µs.') + + synch_pulse_width, illumination_time, picture_time = _calculate_timings( + integration_time, + integration_delay, + add_illumination_time, + DMD_output_synch_pulse_delay, + dark_phase_time) + + spectrometer_params, wavelenghts = _setup_spectrometer( + spectrometer, + integration_time, + integration_delay, + start_pixel, + stop_pixel) + + acquisition_params.wavelengths = np.asarray(wavelenghts, dtype=np.float64) + + DMD_params = _setup_DMD(DMD, add_illumination_time, DMD_initial_memory) + + _setup_patterns(DMD=DMD, metadata=metadata, DMD_params=DMD_params, + acquisition_params=acquisition_params, loop=loop, + pattern_to_display=pattern_to_display) + + _setup_timings(DMD, DMD_params, picture_time, illumination_time, + DMD_output_synch_pulse_delay, synch_pulse_width, + DMD_trigger_in_delay, add_illumination_time) + + return spectrometer_params, DMD_params + + +def change_patterns(DMD: ALP4, + acquisition_params: AcquisitionParameters, + zoom: int = 1, + xw_offset: int = 0, + yh_offset: int = 0, + force_change: bool = False + ): + """ + Delete patterns in the memory of the DMD in the case where the zoom or (x,y) offset change + + DMD (ALP4): + Connected DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + zoom (int): + digital zoom. Deafult is x1. + xw_offset (int): + offset int he width direction of the patterns for zoom > 1. + Default is 0. + yh_offset (int): + offset int he height direction of the patterns for zoom > 1. + Default is 0. + force_change (bool): + to force the changement of the pattern sequence. Default is False. + """ + + if acquisition_params.zoom != zoom or acquisition_params.xw_offset != xw_offset or acquisition_params.yh_offset != yh_offset or force_change == True: + if (DMD.Seqs): + DMD.FreeSeq() + + +def setup_2arms(spectrometer: Avantes, + DMD: ALP4, + camPar: CAM, + DMD_initial_memory: int, + metadata: MetaData, + acquisition_params: AcquisitionParameters, + start_pixel: int = 0, + stop_pixel: Optional[int] = None, + integration_time: float = 1, + integration_delay: int = 0, + DMD_output_synch_pulse_delay: int = 0, + add_illumination_time: int = 356, + dark_phase_time: int = 44, + DMD_trigger_in_delay: int = 0 + ) -> Tuple[SpectrometerParameters, DMDParameters]: + """Setup everything needed to start an acquisition. + + Sets all parameters for DMD, spectrometer, DMD patterns and DMD timings. + Must be called before every acquisition. + + Args: + spectrometer (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + start_pixel (int): + Initial pixel data received from spectrometer. Default is 0. + stop_pixel (int, optional): + Last pixel data received from spectrometer. Default is None if it + should be determined from the amount of available pixels in the + spectrometer. + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. Default + is 1 ms. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + Default is 0 us. + DMD_output_synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). Default is + 0 us. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". Default is 365 us. + dark_phase_time (int): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. Default is 44 us. + DMD_trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). Default is 0 us. + + Raises: + ValueError: Sum of dark phase and additional illumination time is lower + than 400 us. + + Returns: + Tuple[SpectrometerParameters, DMDParameters, List]: Tuple containing DMD + and spectrometer relate metadata, as well as wavelengths. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + """ + + path = Path(metadata.output_directory) + if not path.exists(): + path.mkdir() + + if dark_phase_time + add_illumination_time < 350: + raise ValueError(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' Must be greater than 350 µs.') + + elif dark_phase_time + add_illumination_time < 400: + warnings.warn(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' It is recomended to choose at least 400 µs.') + + synch_pulse_width, illumination_time, picture_time = _calculate_timings( + integration_time, + integration_delay, + add_illumination_time, + DMD_output_synch_pulse_delay, + dark_phase_time) + + spectrometer_params, wavelenghts = _setup_spectrometer( + spectrometer, + integration_time, + integration_delay, + start_pixel, + stop_pixel) + + if camPar.gate_period > 16: + gate_period = 16 + print('Warning, gate period is ' + str(camPar.gate_period) + ' > than the max: 16.') + print('Try to increase the FPS of the camera, or the integration time of the spectrometer.') + print('Check the Pixel clock which must be = 474 MHz') + print('Otherwise some frames will be lost.') + elif camPar.gate_period <1: + print('Warning, gate period is ' + str(camPar.gate_period) + ' < than the min: 1.') + gate_period = 1 + else: + gate_period = camPar.gate_period + + camPar.gate_period = gate_period + Gate = tAlpDynSynchOutGate() + Gate.byref[0] = ct.c_ubyte(gate_period) # Period [1 to 16] (it is a multiple of the trig period which go to the spectro) + Gate.byref[1] = ct.c_ubyte(1) # Polarity => 0: active pulse is low, 1: high + Gate.byref[2] = ct.c_ubyte(1) # Gate1 ok to send TTL + Gate.byref[3] = ct.c_ubyte(0) # Gate2 do not send TTL + Gate.byref[4] = ct.c_ubyte(0) # Gate3 do not send TTL + DMD.DevControlEx(ALP_DEV_DYN_SYNCH_OUT1_GATE, Gate) + camPar.gate_period = gate_period + camPar.int_time_spect = integration_time + + acquisition_params.wavelengths = np.asarray(wavelenghts, dtype=np.float64) + + DMD_params = _setup_DMD(DMD, add_illumination_time, DMD_initial_memory) + + _setup_patterns_2arms(DMD=DMD, metadata=metadata, DMD_params=DMD_params, + acquisition_params=acquisition_params, camPar=camPar) + + _setup_timings(DMD, DMD_params, picture_time, illumination_time, + DMD_output_synch_pulse_delay, synch_pulse_width, + DMD_trigger_in_delay, add_illumination_time) + + return spectrometer_params, DMD_params, camPar + + +def _calculate_elapsed_time(start_measurement_time: int, + measurement_time: np.ndarray, + timestamps: List[int], + ) -> Tuple[np.ndarray, np.ndarray]: + """Calculate acquisition timings. + + Calculates elapsed time between each callback measurement taking into + account the moment when the DMD started running a sequence. + Calculates elapsed time between each spectrum acquired by the spectrometer + based on the spectrometer's internal clock. + + Args: + start_measurement_time (int): + Time in nanoseconds when DMD is set to start running a sequence. + measurement_time (np.ndarray): + 1D array with `int` type timings in nanoseconds when each callbacks + starts. + timestamps (List[int]): + 1D array with measurement timestamps from spectrometer. + Timestamps count ticks for the last pixel of the spectrum was + received by the spectrometer microcontroller. Ticks are in 10 + microsecond units since the spectrometer started. + + Returns: + Tuple[np.ndarray, np.ndarray]: Tuple with measurement timings. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + """ + + measurement_time = np.concatenate( + (start_measurement_time,measurement_time),axis=None) + + measurement_time = np.diff(measurement_time)/1e+6 # In ms + timestamps = np.diff(timestamps)/100 # In ms + + return measurement_time, timestamps + + +def _save_acquisition(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + acquisition_parameters: AcquisitionParameters, + spectral_data: np.ndarray) -> None: + """Save all acquisition data and metadata. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + acquisition_parameters (AcquisitionParameters): + Acquisition related metadata object. + spectral_data (ndarray): + 1D array with `float` type spectrometer measurements. Array size + depends on start and stop pixels previously set to the spectrometer. + """ + + # Saving collected data and timings + path = Path(metadata.output_directory) + path = path / f'{metadata.experiment_name}_spectraldata.npz' + np.savez_compressed(path, spectral_data=spectral_data) + + # 'save_metadata' function is commented because the 'save_metadata_2arms' function is executed after the 'acquire' function in the "main_seq_2arms.py" prog + # # Saving metadata + # save_metadata(metadata, + # DMD_params, + # spectrometer_params, + # acquisition_parameters) + +def _save_acquisition_2arms(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + camPar: CAM, + acquisition_parameters: AcquisitionParameters, + spectral_data: np.ndarray) -> None: + """Save all acquisition data and metadata. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + camPar (CAM): + Metadata object of the IDS monochrome camera + acquisition_parameters (AcquisitionParameters): + Acquisition related metadata object. + spectral_data (ndarray): + 1D array with `float` type spectrometer measurements. Array size + depends on start and stop pixels previously set to the spectrometer. + """ + + # Saving collected data and timings + path = Path(metadata.output_directory) + path = path / f'{metadata.experiment_name}_spectraldata.npz' + np.savez_compressed(path, spectral_data=spectral_data) + + # Saving metadata + save_metadata_2arms(metadata, + DMD_params, + spectrometer_params, + camPar, + acquisition_parameters) + + +def _acquire_raw(ava: Avantes, + DMD: ALP4, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + loop: bool = False + ) -> NamedTuple: + """Raw data acquisition. + + Setups a callback function to receive messages from spectrometer whenever a + measurement is ready to be read. Reads a measurement via a callback. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + loop (bool): + if True, projet continuously the pattern, see the AlpProjStartCont function + if False, projet one time the seq of the patterns, see the AlpProjStart function (Default) + + Returns: + NamedTuple: NamedTuple containig spectral data and measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + spectrum_index (int): + Index of the last acquired spectrum. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + start_measurement_time (float): + Time when acquisition started. + saturation_detected (bool): + Boolean incating if saturation was detected during acquisition. + """ + def register_callback(measurement_time, timestamps, + spectral_data, ava): + + def measurement_callback(handle, info): # If we want to reconstruct during callback; can use it in here. Add function as parameter. + nonlocal spectrum_index + nonlocal saturation_detected + + measurement_time[spectrum_index] = perf_counter_ns() + + if info.contents.value >= 0: + timestamp,spectrum = ava.get_data() + spectral_data[spectrum_index,:] = ( + np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + if np.any(ava.get_saturated_pixels() > 0): + saturation_detected = True + + timestamps[spectrum_index] = np.ctypeslib.as_array(timestamp) + + else: # Set values to zero if an error occured + spectral_data[spectrum_index,:] = 0 + timestamps[spectrum_index] = 0 + + spectrum_index += 1 + + return measurement_callback + + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + + measurement_time = np.zeros((acquisition_params.pattern_amount)) + timestamps = np.zeros((acquisition_params.pattern_amount),dtype=np.uint32) + spectral_data = np.zeros( + (acquisition_params.pattern_amount,pixel_amount),dtype=np.float64) + + # Boolean to indicate if saturation was detected during acquisition + saturation_detected = False + + spectrum_index = 0 # Accessed as nonlocal variable inside the callback + + if loop == False: + #spectro.register_callback(-2,acquisition_params.pattern_amount,pixel_amount) + callback = register_callback(measurement_time, timestamps, + spectral_data, ava) + measurement_callback = MeasureCallback(callback) + ava.measure_callback(-2, measurement_callback) + else: + ava.measure(-1) + + + DMD.Run(loop=loop) # if loop=False : Run the whole sequence only once, if loop=True : Run continuously one pattern + start_measurement_time = perf_counter_ns() + + if loop == False: + while(True): + if(spectrum_index >= acquisition_params.pattern_amount) and loop == False: + break + elif((perf_counter_ns() - start_measurement_time) / 1e+6 > + (2 * acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+3)) and loop == False: + print('Stopping measurement. One of the equipments may be blocked ' + 'or disconnected.') + break + else: + sleep(acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+6 / 10) + DMD.Halt() + else: + sleep(0.1) + + timestamp, spectrum = ava.get_data() + spectral_data_1 = (np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + get_ipython().run_line_magic('matplotlib', 'qt') + plt.ion() # create GUI + figure, ax = plt.subplots(figsize=(10, 8)) + line1, = ax.plot(acquisition_params.wavelengths, spectral_data_1) + + plt.title("Tune the Spectrometer", fontsize=20) + plt.xlabel("Lambda (nm)") + plt.ylabel("counts") + plt.xticks(fontsize=14) + plt.yticks(fontsize=14) + plt.grid() + printed = False + while(True): + try: + timestamp, spectrum = ava.get_data() + spectral_data_1 = (np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + line1.set_xdata(acquisition_params.wavelengths) + line1.set_ydata(spectral_data_1) # updating data values + + figure.canvas.draw() # drawing updated values + figure.canvas.flush_events() # flush prior plot + + if not printed: + print('Press "Ctrl + c" to exit') + if np.amax(spectral_data_1) >= 65535: + print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') + printed = True + + except KeyboardInterrupt: + if (DMD.Seqs): + DMD.Halt() + DMD.FreeSeq() + plt.close() + get_ipython().run_line_magic('matplotlib', 'inline') + break + + ava.stop_measure() + + AcquisitionResult = namedtuple('AcquisitionResult', [ + 'spectral_data', + 'spectrum_index', + 'timestamps', + 'measurement_time', + 'start_measurement_time', + 'saturation_detected']) + + return AcquisitionResult(spectral_data, + spectrum_index, + timestamps, + measurement_time, + start_measurement_time, + saturation_detected) + + +def acquire(ava: Avantes, + DMD: ALP4, + metadata: MetaData, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + repetitions: int = 1, + verbose: bool = False, + reconstruct: bool = False, + reconstruction_params: ReconstructionParameters = None + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Perform a complete acquisition. + + Performs single or multiple acquisitions using the same setup configurations + previously chosen. + Finnaly saves all acqusition related data and metadata. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + wavelengths (List[float]): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + repetitions (int): + Number of times the acquisition will be repeated with the same + configurations. Default is 1, a single acquisition. + verbose (bool): + Chooses if data concerning each acquisition should be printed to + user. If False, only overall data regarding all repetitions is + printed. Default is False. + reconstruct (bool): + If True, will perform reconstruction alongside acquisition using + multiprocessing. + reconstruction_params (ReconstructionParameters): + Object containing parameters of the neural network to be loaded for + reconstruction. + + Returns: + Tuple[ndarray, ndarray, ndarray]: Tuple containig spectral data and + measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + """ + + loop = False # if true, is to projet continuously a unique pattern to tune the spectrometer + + if reconstruct == True: + print('Creating reconstruction processes') + + # Creating a Queue for sending spectral data to reconstruction process + queue_to_recon = Queue() + + # Creating a Queue for sending reconstructed images to plot + queue_reconstructed = Queue() + + sleep_time = (acquisition_params.pattern_amount * + DMD_params.picture_time_us/1e+6) + + # Creating reconstruction process + recon_process = Process(target=reconstruct_process, + args=(reconstruction_params.model, + reconstruction_params.device, + queue_to_recon, + queue_reconstructed, + reconstruction_params.batches, + reconstruction_params.noise, + sleep_time)) + + # Creating plot process + plot_process = Process(target=plot_recon, + args=(queue_reconstructed, sleep_time)) + + # Starting processes + recon_process.start() + plot_process.start() + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + measurement_time = np.zeros( + (acquisition_params.pattern_amount * repetitions)) + timestamps = np.zeros( + ((acquisition_params.pattern_amount - 1) * repetitions), + dtype=np.float64) + spectral_data = np.zeros( + (acquisition_params.pattern_amount * repetitions,pixel_amount), + dtype=np.float64) + + acquisition_params.acquired_spectra = 0 + print() + + for repetition in range(repetitions): + if verbose: + print(f"Acquisition {repetition}") + + AcquisitionResults = _acquire_raw(ava, DMD, spectrometer_params, + DMD_params, acquisition_params, loop) + + (data, spectrum_index, timestamp, time, + start_measurement_time, saturation_detected) = AcquisitionResults + + print('Acquisition number : ' + str(repetition) + ' finished') + + if reconstruct == True: + queue_to_recon.put(data.T) + print('Data sent') + + time, timestamp = _calculate_elapsed_time( + start_measurement_time, time, timestamp) + + begin = repetition * acquisition_params.pattern_amount + end = (repetition + 1) * acquisition_params.pattern_amount + spectral_data[begin:end] = data + measurement_time[begin:end] = time + + begin = repetition * (acquisition_params.pattern_amount - 1) + end = (repetition + 1) * (acquisition_params.pattern_amount - 1) + timestamps[begin:end] = timestamp + + acquisition_params.acquired_spectra += spectrum_index + + acquisition_params.saturation_detected = saturation_detected + + if saturation_detected is True: + print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') + # Print data for each repetition + if (verbose): + print('Spectra acquired: {}'.format(spectrum_index)) + print('Mean callback acquisition time: {} ms'.format( + np.mean(time))) + print('Total callback acquisition time: {} s'.format( + np.sum(time)/1000)) + print('Mean spectrometer acquisition time: {} ms'.format( + np.mean(timestamp))) + print('Total spectrometer acquisition time: {} s'.format( + np.sum(timestamp)/1000)) + + # Print shape of acquisition matrix for one repetition + print(f'Partial acquisition matrix dimensions:' + f'{data.shape}') + print() + + acquisition_params.update_timings(timestamps, measurement_time) + # Real time between each spectrum acquisition by the spectrometer + print('Complete acquisition done') + print('Spectra acquired: {}'.format(acquisition_params.acquired_spectra)) + print('Total acquisition time: {0:.2f} s'.format(acquisition_params.total_spectrometer_acquisition_time_s)) + + _save_acquisition(metadata, DMD_params, spectrometer_params, + acquisition_params, spectral_data) + + # Joining processes and closing queues + if reconstruct == True: + queue_to_recon.put('kill') # Sends a message to stop reconstruction + recon_process.join() + queue_to_recon.close() + plot_process.join() + queue_reconstructed.close() + + maxi = np.amax(spectral_data[0,:]) + print('------------------------------------------------') + print('maximum in the spectrum = ' + str(maxi)) + print('------------------------------------------------') + if maxi >= 65535: + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + print('!!!!! warning, spectrum saturation !!!!!!!!') + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + + return spectral_data + +def _acquire_raw_2arms(ava: Avantes, + DMD: ALP4, + camPar: CAM, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + metadata, + repetition, + repetitions + ) -> NamedTuple: + """Raw data acquisition. + + Setups a callback function to receive messages from spectrometer whenever a + measurement is ready to be read. Reads a measurement via a callback. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + + Returns: + NamedTuple: NamedTuple containig spectral data and measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + spectrum_index (int): + Index of the last acquired spectrum. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + start_measurement_time (float): + Time when acquisition started. + saturation_detected (bool): + Boolean incating if saturation was detected during acquisition. + """ + # def for spectrometer acquisition + def register_callback(measurement_time, timestamps, + spectral_data, ava): + + def measurement_callback(handle, info): # If we want to reconstruct during callback; can use it in here. Add function as parameter. + nonlocal spectrum_index + nonlocal saturation_detected + + measurement_time[spectrum_index] = perf_counter_ns() + + if info.contents.value >= 0: + timestamp,spectrum = ava.get_data() + spectral_data[spectrum_index,:] = ( + np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + if np.any(ava.get_saturated_pixels() > 0): + saturation_detected = True + + timestamps[spectrum_index] = np.ctypeslib.as_array(timestamp) + + else: # Set values to zero if an error occured + spectral_data[spectrum_index,:] = 0 + timestamps[spectrum_index] = 0 + + spectrum_index += 1 + + return measurement_callback + + # def for camera acquisition + if repetition == 0: + camPar = stopCapt_DeallocMem(camPar) + camPar.trigger_mode = 'hard'#'soft'# + imageQueue(camPar) + camPar = prepareCam(camPar, metadata) + camPar.timeout = 1000 # time out in ms for the "is_WaitForNextImage" function + start_chrono = time.time() + x = threading.Thread(target = runCam_thread, args=(camPar, start_chrono)) + x.start() + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + + measurement_time = np.zeros((acquisition_params.pattern_amount)) + timestamps = np.zeros((acquisition_params.pattern_amount),dtype=np.uint32) + spectral_data = np.zeros( + (acquisition_params.pattern_amount,pixel_amount),dtype=np.float64) + + # Boolean to indicate if saturation was detected during acquisition + saturation_detected = False + + spectrum_index = 0 # Accessed as nonlocal variable inside the callback + + #spectro.register_callback(-2,acquisition_params.pattern_amount,pixel_amount) + callback = register_callback(measurement_time, timestamps, + spectral_data, ava) + measurement_callback = MeasureCallback(callback) + ava.measure_callback(-2, measurement_callback) + + # time.sleep(0.5) + # Run the whole sequence only once + DMD.Run(loop=False) + start_measurement_time = perf_counter_ns() + #sleep(13) + + while(True): + if(spectrum_index >= acquisition_params.pattern_amount): + break + elif((perf_counter_ns() - start_measurement_time) / 1e+6 > + (2 * acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+3)): + print('Stopping measurement. One of the equipments may be blocked ' + 'or disconnected.') + break + else: + time.sleep(acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+6 / 10) + + ava.stop_measure() + DMD.Halt() + camPar.Exit = 2 + if repetition == repetitions-1: + camPar = stopCam(camPar) + #Yprint('MAIN :// camPar.camActivated = ' + str(camPar.camActivated)) + AcquisitionResult = namedtuple('AcquisitionResult', [ + 'spectral_data', + 'spectrum_index', + 'timestamps', + 'measurement_time', + 'start_measurement_time', + 'saturation_detected']) + + return AcquisitionResult(spectral_data, + spectrum_index, + timestamps, + measurement_time, + start_measurement_time, + saturation_detected) + + +def acquire_2arms(ava: Avantes, + DMD: ALP4, + camPar: CAM, + metadata: MetaData, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + repetitions: int = 1, + verbose: bool = False, + reconstruct: bool = False, + reconstruction_params: ReconstructionParameters = None + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Perform a complete acquisition. + + Performs single or multiple acquisitions using the same setup configurations + previously chosen. + Finnaly saves all acqusition related data and metadata. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + wavelengths (List[float]): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + repetitions (int): + Number of times the acquisition will be repeated with the same + configurations. Default is 1, a single acquisition. + verbose (bool): + Chooses if data concerning each acquisition should be printed to + user. If False, only overall data regarding all repetitions is + printed. Default is False. + reconstruct (bool): + If True, will perform reconstruction alongside acquisition using + multiprocessing. + reconstruction_params (ReconstructionParameters): + Object containing parameters of the neural network to be loaded for + reconstruction. + + Returns: + Tuple[ndarray, ndarray, ndarray]: Tuple containig spectral data and + measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + """ + + if reconstruct == True: + print('Creating reconstruction processes') + + # Creating a Queue for sending spectral data to reconstruction process + queue_to_recon = Queue() + + # Creating a Queue for sending reconstructed images to plot + queue_reconstructed = Queue() + + sleep_time = (acquisition_params.pattern_amount * + DMD_params.picture_time_us/1e+6) + + # Creating reconstruction process + recon_process = Process(target=reconstruct_process, + args=(reconstruction_params.model, + reconstruction_params.device, + queue_to_recon, + queue_reconstructed, + reconstruction_params.batches, + reconstruction_params.noise, + sleep_time)) + + # Creating plot process + plot_process = Process(target=plot_recon, + args=(queue_reconstructed, sleep_time)) + + # Starting processes + recon_process.start() + plot_process.start() + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + measurement_time = np.zeros( + (acquisition_params.pattern_amount * repetitions)) + timestamps = np.zeros( + ((acquisition_params.pattern_amount - 1) * repetitions), + dtype=np.float64) + spectral_data = np.zeros( + (acquisition_params.pattern_amount * repetitions,pixel_amount), + dtype=np.float64) + + acquisition_params.acquired_spectra = 0 + print() + + for repetition in range(repetitions): + if verbose: + print(f"Acquisition {repetition}") + + AcquisitionResults = _acquire_raw_2arms(ava, DMD, camPar, spectrometer_params, + DMD_params, acquisition_params, metadata, repetition, repetitions) + + (data, spectrum_index, timestamp, time, + start_measurement_time, saturation_detected) = AcquisitionResults + + print('Acquisition number : ' + str(repetition) + ' finished') + + if reconstruct == True: + queue_to_recon.put(data.T) + print('Data sent') + + time, timestamp = _calculate_elapsed_time( + start_measurement_time, time, timestamp) + + begin = repetition * acquisition_params.pattern_amount + end = (repetition + 1) * acquisition_params.pattern_amount + spectral_data[begin:end] = data + measurement_time[begin:end] = time + + begin = repetition * (acquisition_params.pattern_amount - 1) + end = (repetition + 1) * (acquisition_params.pattern_amount - 1) + timestamps[begin:end] = timestamp + + acquisition_params.acquired_spectra += spectrum_index + + acquisition_params.saturation_detected = saturation_detected + + if saturation_detected is True: + print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') + # Print data for each repetition + if (verbose): + print('Spectra acquired: {}'.format(spectrum_index)) + print('Mean callback acquisition time: {} ms'.format( + np.mean(time))) + print('Total callback acquisition time: {} s'.format( + np.sum(time)/1000)) + print('Mean spectrometer acquisition time: {} ms'.format( + np.mean(timestamp))) + print('Total spectrometer acquisition time: {} s'.format( + np.sum(timestamp)/1000)) + + # Print shape of acquisition matrix for one repetition + print(f'Partial acquisition matrix dimensions:' + f'{data.shape}') + print() + + acquisition_params.update_timings(timestamps, measurement_time) + # Real time between each spectrum acquisition by the spectrometer + print('Complete acquisition done') + print('Spectra acquired: {}'.format(acquisition_params.acquired_spectra)) + print('Total acquisition time: {0:.2f} s'.format(acquisition_params.total_spectrometer_acquisition_time_s)) + + # delete acquisition with black pattern (white for the camera) + if camPar.insert_patterns == 1: + black_pattern_index = np.where(acquisition_params.patterns_wp == -1) + # print('index of white patterns :') + # print(black_pattern_index[0:38]) + if acquisition_params.patterns_wp.shape == acquisition_params.patterns.shape: + acquisition_params.patterns = np.delete(acquisition_params.patterns, black_pattern_index) + spectral_data = np.delete(spectral_data, black_pattern_index, axis = 0) + acquisition_params.timestamps = np.delete(acquisition_params.timestamps, black_pattern_index[1:]) + acquisition_params.measurement_time = np.delete(acquisition_params.measurement_time, black_pattern_index) + acquisition_params.acquired_spectra = len(acquisition_params.patterns) + + _save_acquisition_2arms(metadata, DMD_params, spectrometer_params, camPar, + acquisition_params, spectral_data) + + # Joining processes and closing queues + if reconstruct == True: + queue_to_recon.put('kill') # Sends a message to stop reconstruction + recon_process.join() + queue_to_recon.close() + plot_process.join() + queue_reconstructed.close() + + maxi = np.amax(spectral_data[0,:]) + print('------------------------------------------------') + print('maximum in the spectrum = ' + str(maxi)) + print('------------------------------------------------') + if maxi >= 65535: + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + print('!!!!! warning, spectrum saturation !!!!!!!!') + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + + return spectral_data + + +def setup_tuneSpectro(spectrometer, + DMD, + DMD_initial_memory, + pattern_to_display, + ti : float = 1, + zoom : int = 1, + xw_offset: int = 128, + yh_offset: int = 0, + mask_index : np.array = []): + """ Setup the hadrware to tune the spectrometer in live. The goal is to find + the integration time of the spectrometer, noise is around 700 counts, + saturation is equal to 2**16=65535 + + Args: + spectrometer (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y. + pattern_to_display (string): + display one pattern on the DMD to tune the spectrometer. Default is + white pattern + ti (float): + The integration time of the spectrometer during one scan in miliseconds. + Default is 1 ms. + zoom (int): + digital zoom on the DMD. Default is 1 + xw_offset (int): + offset of the pattern in the DMD for zoom > 1 in the width (x) direction + yh_offset (int): + offset of the pattern in the DMD for zoom > 1 in the heihgt (y) direction + mask_index (Union[np.ndarray, str], optional): + Array of `int` type corresponding to the index of the mask vector where + the value is egal to 1 + + return: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + """ + + data_folder_name = 'Tune' + data_name = 'test' + # all_path = func_path(data_folder_name, data_name) + + scan_mode = 'Walsh' + Np = 16 + source = '' + object_name = '' + + metadata = MetaData( + output_directory = '',#all_path.subfolder_path, + pattern_order_source = 'C:/openspyrit/spas/stats/pattern_order_' + scan_mode + '_' + str(Np) + 'x' + str(Np) + '.npz', + pattern_source = 'C:/openspyrit/spas/Patterns/' + scan_mode + '_' + str(Np) + 'x' + str(Np), + pattern_prefix = scan_mode + '_' + str(Np) + 'x' + str(Np), + experiment_name = data_name, + light_source = source, + object = object_name, + filter = '', + description = '' + ) + + acquisition_parameters = AcquisitionParameters( + pattern_compression = 1, + pattern_dimension_x = 16, + pattern_dimension_y = 16, + zoom = zoom, + xw_offset = xw_offset, + yh_offset = yh_offset, + mask_index = [] ) + + acquisition_parameters.pattern_amount = 1 + + spectrometer_params, DMD_params = setup( + spectrometer = spectrometer, + DMD = DMD, + DMD_initial_memory = DMD_initial_memory, + metadata = metadata, + acquisition_params = acquisition_parameters, + pattern_to_display = pattern_to_display, + integration_time = ti, + loop = True ) + + return metadata, spectrometer_params, DMD_params, acquisition_parameters + + +def displaySpectro(ava: Avantes, + DMD: ALP4, + metadata: MetaData, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + reconstruction_params: ReconstructionParameters = None + ): + """Perform a continousely acquisition on the spectrometer for optical tuning. + + Send a pattern on the DMD to project light on the spectrometer. The goal is + to have a look on the amplitude of the spectrum to tune the illumination to + avoid saturation (sat >= 65535) and noisy signal (amp <= 650). + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + wavelengths (List[float]): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + reconstruction_params (ReconstructionParameters): + Object containing parameters of the neural network to be loaded for + reconstruction. + """ + + loop = True # is to project continuously a unique pattern to tune the spectrometer + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + + spectral_data = np.zeros( + (acquisition_params.pattern_amount,pixel_amount), + dtype=np.float64) + + acquisition_params.acquired_spectra = 0 + + AcquisitionResults = _acquire_raw(ava, DMD, spectrometer_params, + DMD_params, acquisition_params, loop) + + (data, spectrum_index, timestamp, time, + start_measurement_time, saturation_detected) = AcquisitionResults + + time, timestamp = _calculate_elapsed_time( + start_measurement_time, time, timestamp) + + begin = acquisition_params.pattern_amount + end = 2 * acquisition_params.pattern_amount + spectral_data[begin:end] = data + + acquisition_params.acquired_spectra += spectrum_index + + acquisition_params.saturation_detected = saturation_detected + + +def check_ueye(func, *args, exp=0, raise_exc=True, txt=None): + """Check for bad input value + + Args: + ---------- + func : TYPE + the ueye function. + *args : TYPE + the input value. + exp : TYPE, optional + DESCRIPTION. The default is 0. + raise_exc : TYPE, optional + DESCRIPTION. The default is True. + txt : TYPE, optional + DESCRIPTION. The default is None. + + Raises + ------ + RuntimeError + DESCRIPTION. + + Returns + ------- + None. + """ + + ret = func(*args) + if not txt: + txt = "{}: Expected {} but ret={}!".format(str(func), exp, ret) + if ret != exp: + if raise_exc: + raise RuntimeError(txt) + else: + logging.critical(txt) + + +def stopCapt_DeallocMem(camPar): + """Stop capture and deallocate camera memory if need to change AOI + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns: + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + print('video stop successful') + else: + print('problem to stop the video') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + print('deallocate memory successful') + else: + print('Problem to deallocate memory of the camera') + + return camPar + + +def stopCapt_DeallocMem_ExitCam(camPar): + """Stop capture, deallocate camera memory if need to change AOI and disconnect the camera + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns: + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + print('video stop successful') + else: + print('problem to stop the video') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + print('deallocate memory successful') + else: + print('Problem to deallocate memory of the camera') + + if camPar.Exit == 2: + nRet = ueye.is_ExitCamera(camPar.hCam) + if nRet == ueye.IS_SUCCESS: + camPar.Exit = 0 + print('Camera disconnected') + else: + print('Problem to disconnect camera, need to restart spyder') + + return camPar + + +class ImageBuffer: + """A class to allocate buffer in the camera memory + """ + + pcImageMemory = None + MemID = None + width = None + height = None + nbitsPerPixel = None + + +def imageQueue(camPar): + """Create Imagequeue / Allocate 3 ore more buffers depending on the framerate / Initialize Image queue + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns: + ------- + None. + + """ + + sleep(1) # is required (delay of 1s was not optimized!!) + buffers = [] + for y in range(10): + buffers.append(ImageBuffer()) + + for x in range(len(buffers)): + buffers[x].nbitsPerPixel = camPar.nBitsPerPixel # RAW8 + buffers[x].height = camPar.rectAOI.s32Height # sensorinfo.nMaxHeight + buffers[x].width = camPar.rectAOI.s32Width # sensorinfo.nMaxWidth + buffers[x].MemID = ueye.int(0) + buffers[x].pcImageMemory = ueye.c_mem_p() + check_ueye(ueye.is_AllocImageMem, camPar.hCam, buffers[x].width, buffers[x].height, buffers[x].nbitsPerPixel, + buffers[x].pcImageMemory, buffers[x].MemID) + check_ueye(ueye.is_AddToSequence, camPar.hCam, buffers[x].pcImageMemory, buffers[x].MemID) + + check_ueye(ueye.is_InitImageQueue, camPar.hCam, ueye.c_int(0)) + if camPar.trigger_mode == 'soft': + check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_SOFTWARE) + elif camPar.trigger_mode == 'hard': + check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_LO_HI) + + +def prepareCam(camPar, metadata): + """Prepare the IDS monochrome camera before acquisition + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + + Returns: + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + """ + cam_path = metadata.output_directory + '\\' + metadata.experiment_name + '_video.' + camPar.vidFormat + strFileName = ueye.c_char_p(cam_path.encode('utf-8')) + + if camPar.vidFormat == 'avi': + # print('Video format : AVI') + camPar.avi = ueye.int() + nRet = ueye_tools.isavi_InitAVI(camPar.avi, camPar.hCam) + # print("isavi_InitAVI") + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_InitAVI ERROR") + + nRet = ueye_tools.isavi_SetImageSize(camPar.avi, camPar.m_nColorMode, camPar.rectAOI.s32Width , camPar.rectAOI.s32Height, 0, 0, 0) + nRet = ueye_tools.isavi_SetImageQuality(camPar.avi, 100) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_SetImageQuality ERROR") + + nRet = ueye_tools.isavi_OpenAVI(camPar.avi, strFileName) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_OpenAVI ERROR") + print('Error code = ' + str(nRet)) + print('Certainly, it is a problem with the file name, Avoid special character like "µ" or try to redcue its size') + + nRet = ueye_tools.isavi_SetFrameRate(camPar.avi, camPar.fps) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_SetFrameRate ERROR") + nRet = ueye_tools.isavi_StartAVI(camPar.avi) + # print("isavi_StartAVI") + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_StartAVI ERROR") + + + elif camPar.vidFormat == 'bin': + camPar.punFileID = ueye.c_uint() + nRet = ueye_tools.israw_InitFile(camPar.punFileID, ueye_tools.IS_FILE_ACCESS_MODE_WRITE) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("INIT RAW FILE ERROR") + + nRet = ueye_tools.israw_SetImageInfo(camPar.punFileID, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("SET IMAGE INFO ERROR") + + if nRet == ueye.IS_SUCCESS: + # print('initFile ok') + # print('SetImageInfo ok') + nRet = ueye_tools.israw_OpenFile(camPar.punFileID, strFileName) + # if nRet == ueye.IS_SUCCESS: + # # print('OpenFile success') + + # --------------------------------------------------------- + # Activates the camera's live video mode (free run mode) + # --------------------------------------------------------- + nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) + + if nRet != ueye.IS_SUCCESS: + print("is_CaptureVideo ERROR") + else: + camPar.camActivated = 1 + + return camPar + + +def runCam_thread(camPar, start_chrono): + """Acquire video with the IDS monochrome camera in a thread + + Parameters: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + start_chrono : int + to save a delay for each acquisition frame of the video. + + Returns: + ------- + None. + """ + + imageinfo = ueye.UEYEIMAGEINFO() + current_buffer = ueye.c_mem_p() + current_id = ueye.int() + # inc = 0 + entier_old = 0 + # time.sleep(0.01) + while True: + nret = ueye.is_WaitForNextImage(camPar.hCam, camPar.timeout, current_buffer, current_id) + if nret == ueye.IS_SUCCESS: + check_ueye(ueye.is_GetImageInfo, camPar.hCam, current_id, imageinfo, ueye.sizeof(imageinfo)) + start_time = time.time() + counter = start_time - start_chrono + camPar.time_array.append(counter) + if camPar.vidFormat == 'avi': + nRet = ueye_tools.isavi_AddFrame(camPar.avi, current_buffer) + elif camPar.vidFormat == 'bin': + nRet = ueye_tools.israw_AddFrame(camPar.punFileID, current_buffer, imageinfo.u64TimestampDevice) + + check_ueye(ueye.is_UnlockSeqBuf, camPar.hCam, current_id, current_buffer) + else: + print('Thread finished') + break + + +def stopCam(camPar): + """To stop the acquisition of the video + + Parameters + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + + if camPar.vidFormat == 'avi': + ueye_tools.isavi_StopAVI(camPar.hCam) + ueye_tools.isavi_CloseAVI(camPar.hCam) + ueye_tools.isavi_ExitAVI(camPar.hCam) + elif camPar.vidFormat == 'bin': + ueye_tools.israw_CloseFile(camPar.punFileID) + ueye_tools.israw_ExitFile(camPar.punFileID) + camPar.punFileID = ueye.c_uint() + + return camPar + + +def disconnect(ava: Optional[Avantes]=None, + DMD: Optional[ALP4]=None): + """Disconnect spectrometer and DMD. + + Disconnects equipments trying to stop a running pattern sequence (possibly + blocking correct functioning) and trying to free DMD memory to avoid errors + in later acqusitions. + + Args: + ava (Avantes, optional): + Connected spectrometer (Avantes object). Defaults to None. + DMD (ALP4, optional): + Connected DMD. Defaults to None. + """ + + if ava is not None: + ava.disconnect() + print('Spectro disconnected') + + if DMD is not None: + + # Stop the sequence display + DMD.Halt() + + # Free the sequence from the onboard memory (if any is present) + if (DMD.Seqs): + DMD.FreeSeq() + + DMD.Free() + print('DMD disconnected') + + +def disconnect_2arms(ava: Optional[Avantes]=None, + DMD: Optional[ALP4]=None, + camPar=None): + """Disconnect spectrometer, DMD and the IDS monochrome camera. + + Disconnects equipments trying to stop a running pattern sequence (possibly + blocking correct functioning) and trying to free DMD memory to avoid errors + in later acqusitions. + + Args: + ava (Avantes, optional): + Connected spectrometer (Avantes object). Defaults to None. + DMD (ALP4, optional): + Connected DMD. Defaults to None. + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + + if ava is not None: + ava.disconnect() + print('Spectro disconnected') + + if DMD is not None: + # Stop the sequence display + try: + DMD.Halt() + # Free the sequence from the onboard memory (if any is present) + if (DMD.Seqs): + DMD.FreeSeq() + + DMD.Free() + print('DMD disconnected') + + except: + print('probelm to Halt the DMD') + + + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + else: + print('Problem to stop video, need to restart spyder') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + else: + print('Problem to deallocate camera memory, need to restart spyder') + + + if camPar.Exit == 1 or camPar.Exit == 2: + nRet = ueye.is_ExitCamera(camPar.hCam) + if nRet == ueye.IS_SUCCESS: + camPar.Exit = 0 + print('Camera disconnected') + else: + print('Problem to disconnect camera, need to restart spyder') + + +def _init_CAM(): + """ + Initialize and connect to the IDS camera. + + Returns: + CAM: a structure containing the parameters of the IDS camera + """ + camPar = CAM(hCam = ueye.HIDS(0), + sInfo = ueye.SENSORINFO(), + cInfo = ueye.CAMINFO(), + nBitsPerPixel = ueye.INT(8), + m_nColorMode = ueye.INT(), + bytes_per_pixel = int( ueye.INT(8)/ 8), + rectAOI = ueye.IS_RECT(), + pcImageMemory = ueye.c_mem_p(), + MemID = ueye.int(), + pitch = ueye.INT(), + fps = float(), + gain = int(), + gainBoost = str(), + gamma = float(), + exposureTime = float(), + blackLevel = int(), + camActivated = bool(), + pixelClock = ueye.uint(), + bandwidth = float(), + Memory = bool(), + Exit = int(), + vidFormat = str(), + gate_period = int(), + trigger_mode = str(), + avi = ueye.int(), + punFileID = ueye.c_uint(), + timeout = int(), + time_array = [], + int_time_spect = float(), + black_pattern_num = int(), + insert_patterns = bool(), + acq_mode = str(), + ) + + # # Camera Initialization --- + ### Starts the driver and establishes the connection to the camera + nRet = ueye.is_InitCamera(camPar.hCam, None) + if nRet != ueye.IS_SUCCESS: + print("is_InitCamera ERROR") + + ### Reads out the data hard-coded in the non-volatile camera memory and writes it to the data structure that cInfo points to + nRet = ueye.is_GetCameraInfo(camPar.hCam, camPar.cInfo) + if nRet != ueye.IS_SUCCESS: + print("is_GetCameraInfo ERROR") + + ### You can query additional information about the sensor type used in the camera + nRet = ueye.is_GetSensorInfo(camPar.hCam, camPar.sInfo) + if nRet != ueye.IS_SUCCESS: + print("is_GetSensorInfo ERROR") + + ### set camera parameters to default values + nRet = ueye.is_ResetToDefault(camPar.hCam) + if nRet != ueye.IS_SUCCESS: + print("is_ResetToDefault ERROR") + + ### Set display mode to DIB + nRet = ueye.is_SetDisplayMode(camPar.hCam, ueye.IS_SET_DM_DIB) + + ### Set the right color mode + if int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_BAYER: + # setup the color depth to the current windows setting + ueye.is_GetColorDepth(camPar.hCam, camPar.nBitsPerPixel, camPar.m_nColorMode) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_CBYCRY: + # for color camera models use RGB32 mode + camPar.m_nColorMode = ueye.IS_CM_BGRA8_PACKED + camPar.nBitsPerPixel = ueye.INT(32) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_MONOCHROME: + # for color camera models use RGB32 mode + camPar.m_nColorMode = ueye.IS_CM_MONO8 + camPar.nBitsPerPixel = ueye.INT(8) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + else: + # for monochrome camera models use Y8 mode + camPar.m_nColorMode = ueye.IS_CM_MONO8 + camPar.nBitsPerPixel = ueye.INT(8) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + # print("else") + + ### Get the AOI (Area Of Interest) + sizeofrectAOI = ueye.c_uint(4*4) + nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_GET_AOI, camPar.rectAOI, sizeofrectAOI) + if nRet != ueye.IS_SUCCESS: + print("AOI getting ERROR") + + camPar.camActivated = 0 + + # Get current pixel clock + getpixelclock = ueye.UINT(0) + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, + ueye.sizeof(getpixelclock)) + + camPar.pixelClock = getpixelclock + # print('pixel clock = ' + str(getpixelclock) + ' MHz') + + # get the bandwidth (in MByte/s) + camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) + + camPar.Exit = 1 + + print('IDS camera connected') + + return camPar + + +def captureVid(camPar): + """ + Allocate memory and begin video capture of the IDS camera + + Args: + camPar : a structure containing the parameters of the IDS camera. + + Returns: + camPar : a structure containing the parameters of the IDS camera. + """ + camPar = stopCapt_DeallocMem_ExitCam(camPar) + + if camPar.Exit == 0: + camPar = _init_CAM() + camPar.Exit = 1 + + + ### Set the AOI + sizeofrectAOI = ueye.c_uint(4*4) + nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_SET_AOI, camPar.rectAOI, sizeofrectAOI) + if nRet != ueye.IS_SUCCESS: + print("AOI setting ERROR") + + width = camPar.rectAOI.s32Width + height = camPar.rectAOI.s32Height + + ### Allocates an image memory for an image having its dimensions defined by width and height and its color depth defined by nBitsPerPixel + nRet = ueye.is_AllocImageMem(camPar.hCam, width, height, camPar.nBitsPerPixel, camPar.pcImageMemory, camPar.MemID) + if nRet != ueye.IS_SUCCESS: + print("is_AllocImageMem ERROR") + else: + # Makes the specified image memory the active memory + camPar.Memory = 1 + nRet = ueye.is_SetImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet != ueye.IS_SUCCESS: + print("is_SetImageMem ERROR") + else: + # Set the desired color mode + nRet = ueye.is_SetColorMode(camPar.hCam, camPar.m_nColorMode) + + + ### Activates the camera's live video mode (free run mode) + nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) + if nRet != ueye.IS_SUCCESS: + print("is_CaptureVideo ERROR") + + + ### Enables the queue mode for existing image memory sequences + nRet = ueye.is_InquireImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID, width, height, camPar.nBitsPerPixel, camPar.pitch) + if nRet != ueye.IS_SUCCESS: + print("is_InquireImageMem ERROR") + + camPar.camActivated = 1 + + return camPar + +def setup_cam(camPar, pixelClock, fps, Gain, gain_boost, nGamma, ExposureTime, black_level): + """ + Set and read the camera parameters + + Args: + pixelClock = [118, 237 or 474] (MHz) + fps: fps boundary => [1 - No Value] sup limit depend of image size (216 fps for 768x544 pixels for example) + Gain: Gain boundary => [0 100] + gain_boost: 'ON' set "ON" to activate gain boost, "OFF" to deactivate + nGamma: Gamma boundary => [1 - 2.2] + ExposureTime: Exposure time (ms) boundarye => [0.032 - 56.221] + black_level: Black Level boundary => [0 255] + + returns: + CAM: a structure containing the parameters of the IDS camera + """ + # It is necessary to execute twice this code to take account the parameter modification + for i in range(2): + ############################### Set Pixel Clock ############################### + ### Get range of pixel clock, result : range = [118 474] MHz (Inc = 0) + getpixelclock = ueye.UINT(0) + newpixelclock = ueye.UINT(0) + newpixelclock.value = pixelClock + PixelClockRange = (ueye.int * 3)() + + # Get pixel clock range + nRet = ueye.is_PixelClock(camPar.hCam, ueye.IS_PIXELCLOCK_CMD_GET_RANGE, PixelClockRange, ueye.sizeof(PixelClockRange)) + if nRet == ueye.IS_SUCCESS: + nPixelClockMin = PixelClockRange[0] + nPixelClockMax = PixelClockRange[1] + nPixelClockInc = PixelClockRange[2] + + # Set pixel clock + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_SET, newpixelclock, + ueye.sizeof(newpixelclock)) + # Get current pixel clock + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, + ueye.sizeof(getpixelclock)) + + camPar.pixelClock = getpixelclock.value + if i == 1: + print(' pixel clock = ' + str(getpixelclock) + ' MHz') + if getpixelclock == 118: + if i == 1: + print('Pixel clcok blocked to 118 MHz, it is necessary to unplug the camera if not desired') + # get the bandwidth (in MByte/s) + camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) + if i == 1: + print(' Bandwidth = ' + str(camPar.bandwidth) + ' MB/s') + ############################### Set FrameRate ################################# + ### Read current FrameRate + dblFPS_init = ueye.c_double() + nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_init) + if nRet != ueye.IS_SUCCESS: + print("FrameRate getting ERROR") + else: + dblFPS_eff = dblFPS_init + if i == 1: + print(' current FPS = '+str(round(dblFPS_init.value*100)/100) + ' fps') + if fps < 1: + fps = 1 + if i == 1: + print('FPS exceed lower limit >= 1') + + dblFPS = ueye.c_double(fps) + if (dblFPS.value < dblFPS_init.value-0.01) | (dblFPS.value > dblFPS_init.value+0.01): + newFPS = ueye.c_double() + nRet = ueye.is_SetFrameRate(camPar.hCam, dblFPS, newFPS) + time.sleep(1) + if nRet != ueye.IS_SUCCESS: + print("FrameRate setting ERROR") + else: + if i == 1: + print(' new FPS = '+str(round(newFPS.value*100)/100) + ' fps') + ### Read again the effective FPS / depend of the image size, 17.7 fps is not possible with the entire image size (ie 2076x3088) + dblFPS_eff = ueye.c_double() + nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_eff) + if nRet != ueye.IS_SUCCESS: + print("FrameRate getting ERROR") + else: + if i == 1: + print(' effective FPS = '+str(round(dblFPS_eff.value*100)/100) + ' fps') + ############################### Set GAIN ###################################### + #### Maximum gain is depending of the sensor. Convertion gain code to gain to limit values from 0 to 100 + # gain_code = gain * slope + b + gain_max_code = 1450 + gain_min_code = 100 + gain_max = 100 + gain_min = 0 + slope = (gain_max_code-gain_min_code)/(gain_max-gain_min) + b = gain_min_code + #### Read gain setting + current_gain_code = ueye.c_int() + current_gain_code = ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_GET_MASTER_GAIN_FACTOR, current_gain_code) + current_gain = round((current_gain_code-b)/slope) + + if i == 1: + print(' current GAIN = '+str(current_gain)) + gain_eff = current_gain + + ### Set new gain value + gain = ueye.c_int(Gain) + if gain.value != current_gain: + if gain.value < 0: + gain = ueye.c_int(0) + if i == 1: + print('Gain exceed lower limit >= 0') + elif gain.value > 100: + gain = ueye.c_int(100) + if i == 1: + print('Gain exceed upper limit <= 100') + gain_code = ueye.c_int(round(slope*gain.value+b)) + + ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_SET_MASTER_GAIN_FACTOR, gain_code) + new_gain = round((gain_code-b)/slope) + + if i == 1: + print(' new GAIN = '+str(new_gain)) + gain_eff = new_gain + ############################### Set GAIN Boost ################################ + ### Read current state of the gain boost + current_gain_boost_bool = ueye.is_SetGainBoost(camPar.hCam, ueye.IS_GET_GAINBOOST) + if nRet != ueye.IS_SUCCESS: + print("Gain boost ERROR") + if current_gain_boost_bool == 0: + current_gain_boost = 'OFF' + elif current_gain_boost_bool == 1: + current_gain_boost = 'ON' + + if i == 1: + print('current Gain boost mode = ' + current_gain_boost) + + ### Set the state of the gain boost + if gain_boost != current_gain_boost: + if gain_boost == 'OFF': + nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_OFF) + print(' new Gain Boost : OFF') + + elif gain_boost == 'ON': + nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_ON) + print(' new Gain Boost : ON') + + if nRet != ueye.IS_SUCCESS: + print("Gain boost setting ERROR") + ############################### Set Gamma ##################################### + ### Check boundary of Gamma + if nGamma > 2.2: + nGamma = 2.2 + if i == 1: + print('Gamma exceed upper limit <= 2.2') + elif nGamma < 1: + nGamma = 1 + if i == 1: + print('Gamma exceed lower limit >= 1') + ### Read current Gamma + c_nGamma_init = ueye.c_void_p() + sizeOfnGamma = ueye.c_uint(4) + nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_GET, c_nGamma_init, sizeOfnGamma) + if nRet != ueye.IS_SUCCESS: + print("Gamma getting ERROR") + else: + if i == 1: + print(' current Gamma = ' + str(c_nGamma_init.value/100)) + ### Set Gamma + c_nGamma = ueye.c_void_p(round(nGamma*100)) # need to multiply by 100 [100 - 220] + if c_nGamma_init.value != c_nGamma.value: + nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_SET, c_nGamma, sizeOfnGamma) + if nRet != ueye.IS_SUCCESS: + print("Gamma setting ERROR") + else: + if i == 1: + print(' new Gamma = '+str(c_nGamma.value/100)) + ############################### Set Exposure time ############################# + ### Read current Exposure Time + getExposure = ueye.c_double() + sizeOfpParam = ueye.c_uint(8) + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE, getExposure, sizeOfpParam) + if nRet == ueye.IS_SUCCESS: + getExposure.value = round(getExposure.value*1000)/1000 + + if i == 1: + print(' current Exposure Time = ' + str(getExposure.value) + ' ms') + ### Get minimum Exposure Time + minExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MIN, minExposure, sizeOfpParam) + ### Get maximum Exposure Time + maxExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MAX, maxExposure, sizeOfpParam) + ### Get increment Exposure Time + incExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_INC, incExposure, sizeOfpParam) + ### Set new Exposure Time + setExposure = ueye.c_double(ExposureTime) + if setExposure.value > maxExposure.value: + setExposure.value = maxExposure.value + if i == 1: + print('Exposure Time exceed upper limit <= ' + str(maxExposure.value)) + elif setExposure.value < minExposure.value: + setExposure.value = minExposure.value + if i == 1: + print('Exposure Time exceed lower limit >= ' + str(minExposure.value)) + + if (setExposure.value < getExposure.value-incExposure.value/2) | (setExposure.value > getExposure.value+incExposure.value/2): + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_SET_EXPOSURE, setExposure, sizeOfpParam) + if nRet != ueye.IS_SUCCESS: + print("Exposure Time ERROR") + else: + if i == 1: + print(' new Exposure Time = ' + str(round(setExposure.value*1000)/1000) + ' ms') + ############################### Set Black Level ############################### + current_black_level_c = ueye.c_uint() + sizeOfBlack_level = ueye.c_uint(4) + ### Read current Black Level + nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_GET_OFFSET, current_black_level_c, sizeOfBlack_level) + if nRet != ueye.IS_SUCCESS: + print("Black Level getting ERROR") + else: + if i == 1: + print(' current Black Level = ' + str(current_black_level_c.value)) + + ### Set Black Level + if black_level > 255: + black_level = 255 + if i == 1: + print('Black Level exceed upper limit <= 255') + if black_level < 0: + black_level = 0 + if i == 1: + print('Black Level exceed lower limit >= 0') + + black_level_c = ueye.c_uint(black_level) + if black_level != current_black_level_c.value : + nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_SET_OFFSET, black_level_c, sizeOfBlack_level) + if nRet != ueye.IS_SUCCESS: + print("Black Level setting ERROR") + else: + if i == 1: + print(' new Black Level = ' + str(black_level_c.value)) + + + camPar.fps = round(dblFPS_eff.value*100)/100 + camPar.gain = gain_eff + camPar.gainBoost = gain_boost + camPar.gamma = c_nGamma.value/100 + camPar.exposureTime = round(setExposure.value*1000)/1000 + camPar.blackLevel = black_level_c.value + + return camPar + + +def snapshot(camPar, pathIDSsnapshot, pathIDSsnapshot_overview): + """ + Snapshot of the IDS camera + + Args: + CAM: a structure containing the parameters of the IDS camera + """ + array = ueye.get_data(camPar.pcImageMemory, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel, camPar.pitch, copy=False) + + # ...reshape it in an numpy array... + frame = np.reshape(array,(camPar.rectAOI.s32Height.value, camPar.rectAOI.s32Width.value))#, camPar.bytes_per_pixel)) + + with pathIDSsnapshot.open('wb') as f: #('ab') as f: #(pathname, mode='w', encoding='utf-8') as f: #('ab') as f: + np.save(f,frame) + + maxi = np.amax(frame) + if maxi == 0: + maxi = 1 + im = Image.fromarray(frame*math.floor(255/maxi)) + im.save(pathIDSsnapshot_overview) + + maxi = np.amax(frame) + # print() + # print('frame max = ' + str(maxi)) + # print('frame min = ' + str(np.amin(frame))) + if maxi >= 255: + print('Saturation detected') + + plt.figure + plt.imshow(frame)#, cmap='gray', vmin=mini, vmax=maxi) + plt.colorbar(); + plt.show() + + \ No newline at end of file diff --git a/spas/metadata_SPC1D.py b/spas/metadata_SPC1D.py new file mode 100644 index 0000000..344b1dd --- /dev/null +++ b/spas/metadata_SPC1D.py @@ -0,0 +1,1118 @@ +# -*- coding: utf-8 -*- +__author__ = 'Guilherme Beneti Martins' + +"""Metadata classes and utilities. + +Metadata classes to keep and save all relevant data during an acquisition. +Utility functions to recreate objects from JSON files, save them to JSON and to +improve readability. +""" + +import json +from datetime import datetime +from enum import IntEnum +from dataclasses import dataclass, InitVar, field +from typing import Optional, Union, List, Tuple, Optional +from pathlib import Path +import os +from dataclasses_json import dataclass_json +import numpy as np +import ctypes as ct +import pickle +##### DLL for the DMD +try: + import ALP4 +except: # in the cas the DLL of the DMD is not installed + class ALP4: + pass + setattr(ALP4, 'ALP4', None) + print('DLL of the DMD not installed') +##### DLL for the spectrometer Avantes +try: + from msl.equipment.resources.avantes import MeasConfigType +except: # in the cas the DLL of the spectrometer is not installed + class MeasConfigType: + pass + MeasConfigType = None + print('DLL of the spectrometer not installed !!!') + +##### DLL for the camera +try: + from pyueye import ueye + dll_pyueye_installed = 1 +except: + dll_pyueye_installed = 0 + print('DLL of the cam not installed !!') + +class DMDTypes(IntEnum): + """Enumeration of DMD types and respective codes.""" + ALP_DMDTYPE_XGA = 1 + ALP_DMDTYPE_SXGA_PLUS = 2 + ALP_DMDTYPE_1080P_095A = 3 + ALP_DMDTYPE_XGA_07A = 4 + ALP_DMDTYPE_XGA_055A = 5 + ALP_DMDTYPE_XGA_055X = 6 + ALP_DMDTYPE_WUXGA_096A = 7 + ALP_DMDTYPE_WQXGA_400MHZ_090A = 8 + ALP_DMDTYPE_WQXGA_480MHZ_090A = 9 + ALP_DMDTYPE_WXGA_S450 = 12 + ALP_DMDTYPE_DISCONNECT = 255 + + +@dataclass_json +@dataclass +class MetaData: + """ Class containing overall acquisition parameters and description. + + Metadata concerning the experiment, paths, file inputs and file outputs. + This class is adapted to be reconstructed from a JSON file. + + Attributes: + output_directory (Union[str, Path], optional): + Directory where multiple related acquisitions will be stored. + pattern_order_source (Union[str, Path], optional): + File where the order of patterns to be sent to DMD is specified. It + can be a text file containing a list of pattern indeces or a numpy + file containing a covariance matrix from which the pattern order is + calculated. + pattern_source (Union[str, Path], optional): + Pattern source folder. + pattern_prefix (str): + Prefix used in pattern naming. + experiment_name (str): + Prefix of all files related to a single acquisition. Files will + appear with the following string pattern: + experiment_name + '_' + filename. + light_source (str): + Light source used to illuminate an object during acquisition. + object (str): + Object imaged during acquisition. + filter (str): + Light filter used. + description (str): + Acqusition experiment description. + date (str, optional): + Acquisition date. Automatically set when object is created. Default + is None. + time (str, optional): + Time when metadata object is created. Set automatically by + __post_init__(). Default is None. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'Metadata'. + """ + + pattern_prefix: str + experiment_name: str + + light_source: str + object: str + filter: str + description: str + + output_directory: Union[str, Path] + pattern_order_source: Union[str, Path] + pattern_source: Union[str, Path] + + date: Optional[str] = None + time: Optional[str] = None + + class_description: str = 'Metadata' + + def __post_init__(self): + """Sets time and date of object cretion and deals with paths""" + + today = datetime.today() + self.date = '--/--/----' #today.strftime('%d/%m/%Y') + self.time = today.strftime('%I:%M:%S %p') + + # If parameter is str, turn it into Path + if isinstance(self.output_directory, str): + self.output_directory = Path(self.output_directory) + + # If parameter is Path or was turned into a Path, resolve it and get the + # str format. + if issubclass(self.output_directory.__class__, Path): + self.output_directory = str(self.output_directory.resolve()) + + + if isinstance(self.pattern_order_source, str): + self.pattern_order_source = Path(self.pattern_order_source) + + if issubclass(self.pattern_order_source.__class__, Path): + self.pattern_order_source = str( + self.pattern_order_source.resolve()) + + + if isinstance(self.pattern_source, str): + self.pattern_source = Path(self.pattern_source) + + if issubclass(self.pattern_source.__class__, Path): + self.pattern_source = str(self.pattern_source.resolve()) + + +@dataclass_json +@dataclass +class CAM: + """Class containing IDS camera configurations. + + Further information: https://en.ids-imaging.com/manuals/ids-software-suite/ueye-manual/4.95/en/c_programmierung.html. + + Attributes: + hCam (ueye.c_uint): Handle of the camera. + sInfo (ueye.SENSORINFO):sensor information : [SensorID [c_ushort] = 566; + strSensorName [c_char_Array_32] = b'UI388xCP-M'; + nColorMode [c_char] = b'\x01'; + nMaxWidth [c_uint] = 3088; + nMaxHeight [c_uint] = 2076; + bMasterGain [c_int] = 1; + bRGain [c_int] = 0; + bGGain [c_int] = 0; + bBGain [c_int] = 0; + bGlobShutter [c_int] = 0; + wPixelSize [c_ushort] = 240; + nUpperLeftBayerPixel [c_char] = b'\x00'; + Reserved]. + cInfo (ueye.BOARDINFO):Camera information: [SerNo [c_char_Array_12] = b'4103219888'; + ID [c_char_Array_20] = b'IDS GmbH'; + Version [c_char_Array_10] = b''; + Date [c_char_Array_12] = b'30.11.2017'; + Select [c_ubyte] = 1; + Type [c_ubyte] = 100; + Reserved [c_char_Array_8] = b'';] + nBitsPerPixel (ueye.c_int): number of bits per pixel (8 for monochrome, 24 for color). + m_nColorMode (ueye.c_int): color mode : Y8/RGB16/RGB24/REG32. + bytes_per_pixel (int): bytes_per_pixel = int(nBitsPerPixel / 8). + rectAOI (ueye.IS_RECT()): rectangle of the Area Of Interest (AOI): s32X [c_int] = 0; + s32Y [c_int] = 0; + s32Width [c_int] = 3088; + s32Height [c_int] = 2076; + pcImageMemory (ueye.c_mem_p()): memory allocation. + MemID (ueye.int()): memory identifier. + pitch (ueye.INT()): ???. + fps (float): set frame per second. + gain (int): Set gain between [0 - 100]. + gainBoost (str): Activate gain boosting ("ON") or deactivate ("OFF"). + gamma (float): Set Gamma between [1 - 2.5] to change the image contrast + exposureTime (float): Set the exposure time between [0.032 - 56.221] + blackLevel (int): Set the black level between [0 - 255] to set an offset in the image. It is adviced to put 5 for noise measurement + camActivated (bool) : need to to know if the camera is ready to acquire (1: yes, 0: No) + pixelClock (int) : the pixel clock, three values possible : [118, 237, 474] (MHz) + bandwidth (float) the bandwidth (in MByte/s) is an approximate value which is calculated based on the pixel clock + Memory (bool) : a boolean to know if the memory inside the camera is busy [1] or free [0] + Exit (int) : if Exit = 2 => excute is_ExitCamera function (disables the hCam camera handle and releases the memory) | if Exit = 0 => allow to init cam, after that, Exit = 1 + vidFormat (str) : save video in the format avi or bin (for binary) + gate_period (int) : a second TTL is sent by the DMD to trigg the camera, and based on the fisrt TTL to trigg the spectrometer. camera trigger period = gate_period*(spectrometer trigger period) + trigger_mode (str) : hard or soft + avi (ueye.int) : A pointer that returns the instance ID which is needed for calling the other uEye AVI functions + punFileID (ueye.c_int) : a pointer in which the instance ID is returned. This ID is needed for calling other functions. + timeout (int) : a time which stop the camera that waiting for a TTL + time_array (List[float]) : the time array saved after each frame received on the camera + int_time_spect (float) : is egal to the integration time of the spectrometer, it is need to know this value because of the rolling shutter of the monochrome IDS camera + black_pattern_num (int) : is number inside the image name of the black pattern (for the hyperspectral arm, or white pattern for the camera arm) to be inserted betweem the Hadamard patterns + insert_patterns (int) : 0 => no insertion / 1=> insert white patterns for the camera + acq_mode (str) : mode of the acquisition => 'video' or 'snapshot' mode + """ + if dll_pyueye_installed: + hCam: Optional[ueye.c_uint] = None + sInfo: Optional[ueye.SENSORINFO] = None + cInfo: Optional[ueye.BOARDINFO] = None + nBitsPerPixel: Optional[ueye.c_int] = None + m_nColorMode: Optional[ueye.c_int] = None + bytes_per_pixel: Optional[int] = None + rectAOI: Optional[ueye.IS_RECT] = None + pcImageMemory: Optional[ueye.c_mem_p] = None + MemID: Optional[ueye.c_int] = None + pitch: Optional[ueye.c_int] = None + fps: Optional[float] = None + gain: Optional[int] = None + gainBoost: Optional[str] = None + gamma: Optional[float] = None + exposureTime: Optional[float] = None + blackLevel: Optional[int] = None + camActivated : Optional[bool] = None + pixelClock : Optional[int] = None + bandwidth : Optional[float] = None + Memory : Optional[bool] = None + Exit : Optional[int] = None + vidFormat : Optional[str] = None + gate_period : Optional[int] = None + trigger_mode : Optional[str] = None + avi : Optional[ueye.int] = None + punFileID : Optional[ueye.c_int] = None + timeout : Optional[int] = None + time_array : Optional[Union[List[float], str]] = field(default=None, repr=False) + int_time_spect : Optional[float] = None + black_pattern_num : Optional[int] = None + insert_patterns : Optional[int] = None + acq_mode : Optional[str] = None + + class_description: str = 'IDS camera configuration' + + def undo_readable_class_CAM(self) -> None: + """Changes the time_array attribute from `str` to `List` of `int`.""" + + def to_float(str_arr): + arr = [] + for s in str_arr: + try: + num = float(s) + arr.append(num) + except ValueError: + pass + return arr + + if self.time_array: + self.time_array = ( + self.time_array.strip('[').strip(']').split(', ')) + self.time_array = to_float(self.time_array) + self.time_array = np.asarray(self.time_array) + + @staticmethod + def readable_class_CAM(cam_params_dict: dict) -> dict: + # pass + """Turns list of time_array into a string. + convert the c_type structure (sInfo, cInfo and rectAOI) into a nested dict + change the bytes type item into str + change the c_types item into their value + """ + + readable_cam_dict = {} + readable_cam_dict_temp = cam_params_dict#camPar.to_dict()# + inc = 0 + for item in readable_cam_dict_temp: + stri = str(type(readable_cam_dict_temp[item])) + # print('----- item : ' + item) + if item == 'sInfo' or item == 'cInfo' or item == 'rectAOI': + readable_cam_dict[item] = dict() + try: + for sub_item in readable_cam_dict_temp[item]._fields_: + new_item = item + '-' + sub_item[0] + try: + att = getattr(readable_cam_dict_temp[item], sub_item[0]).value + except: + att = getattr(readable_cam_dict_temp[item], sub_item[0]) + + if type(att) == bytes: + att = str(att) + + readable_cam_dict[item][sub_item[0]] = att + except: + try: + for sub_item in readable_cam_dict_temp[item]: + # print('----- sub_item : ' + sub_item) + new_item = item + '-' + sub_item + att = readable_cam_dict_temp[item][sub_item] + + if type(att) == bytes: + att = str(att) + + readable_cam_dict[item][sub_item] = att + except: + print('warning, impossible to read the subitem of readable_cam_dict_temp[item]') + + elif stri.find('pyueye') >=0: + try: + readable_cam_dict[item] = readable_cam_dict_temp[item].value + except: + readable_cam_dict[item] = readable_cam_dict_temp[item] + elif item == 'time_array': + readable_cam_dict[item] = str(readable_cam_dict_temp[item]) + else: + readable_cam_dict[item] = readable_cam_dict_temp[item] + + return readable_cam_dict + + +@dataclass_json +@dataclass +class AcquisitionParameters: + """Class containing acquisition specifications and timing results. + + This class is adapted to be reconstructed from a JSON file. + + Attributes: + pattern_compression (float): + Percentage of total available patterns to be present in an + acquisition sequence. + pattern_dimension_x (int): + Length of reconstructed image that defines pattern length. + pattern_dimension_y (int): + Width of reconstructed image that defines pattern width. + zoom (int): + numerical zoom of the patterns + xw_offset (int): + offset of the pattern in the DMD for zoom > 1 in the width (x) direction + yh_offset (int): + offset of the pattern in the DMD for zoom > 1 in the heihgt (y) direction + mask_index (Union[np.ndarray, str], optional): + Array of `int` type corresponding to the index of the mask vector where + the value is egal to 1 + x_mask_coord (Union[np.ndarray, str], optional): + coordinates of the mask in the x direction x[0] and x[1] are the first + and last points respectively + y_mask_coord (Union[np.ndarray, str], optional): + coordinates of the mask in the y direction y[0] and y[1] are the first + and last points respectively + pattern_amount (int, optional): + Quantity of patterns sent to DMD for an acquisition. This value is + calculated by an external function. Default in None. + acquired_spectra (int, optional): + Amount of spectra actually read from the spectrometer. This value is + calculated by an external function. Default in None. + mean_callback_acquisition_time_ms (float, optional): + Mean time between 2 callback executions during an acquisition. This + value is calculated by an external function. Default in None. + total_callback_acquisition_time_s (float, optional): + Total time of callback executions during an acquisition. This value + is calculated by an external function. Default in None. + mean_spectrometer_acquisition_time_ms (float, optional): + Mean time between 2 spectrometer measurements during an acquisition + based on its own internal clock. This value is calculated by an + external function. Default in None. + total_spectrometer_acquisition_time_s (float, optional): + Total time of spectrometer measurements during an acquisition + based on its own internal clock. This value is calculated by an + external function. Default in None. + saturation_detected (bool, optional): + Boolean incating if saturation was detected during acquisition. + Default is None. + patterns (Union[List[int],str], optional) = None + List `int` or `str` containing all patterns sent to the DMD for an + acquisition sequence. This value is set by an external function and + its type can be modified by multiple functions during object + creation, manipulation, when dumping to a JSON file or + when reconstructing an AcquisitionParameters object from a JSON + file. It is intended to be of type List[int] most of the execution + List[int]time. Default is None. + wavelengths (Union[np.ndarray, str], optional): + Array of `float` type corresponding to the wavelengths associated + with spectrometer's start and stop pixels. + timestamps (Union[List[float], str], optional): + List of `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. Units in + milliseconds. Default is None. + measurement_time (Union[List[float], str], optional): + List of `float` type elapsed times between each callback. Units in + milliseconds. Default is None. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'Acquisition parameters'. + """ + + pattern_compression: float + pattern_dimension_x: int + pattern_dimension_y: int + zoom: Optional[int] = field(default=None) + xw_offset: Optional[int] = field(default=None) + yh_offset: Optional[int] = field(default=None) + mask_index: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + x_mask_coord: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + y_mask_coord: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + + pattern_amount: Optional[int] = None + acquired_spectra: Optional[int] = None + + mean_callback_acquisition_time_ms: Optional[float] = None + total_callback_acquisition_time_s: Optional[float] = None + mean_spectrometer_acquisition_time_ms: Optional[float] = None + total_spectrometer_acquisition_time_s: Optional[float] = None + + saturation_detected: Optional[bool] = None + + patterns: Optional[Union[List[int], str]] = field(default=None, repr=False) + patterns_wp: Optional[Union[List[int], str]] = field(default=None, repr=False) + wavelengths: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + timestamps: Optional[Union[List[float], str]] = field(default=None, + repr=False) + measurement_time: Optional[Union[List[float], str]] = field(default=None, + repr=False) + + class_description: str = 'Acquisition parameters' + + + def undo_readable_pattern_order(self) -> None: + """Changes the patterns attribute from `str` to `List` of `int`. + + When reconstructing an AcquisitionParameters object from a JSON file, + this method turns the patterns, wavelengths, timestamps and + measurement_time attributes from a string to a list of integers + containing the pattern indices used in that acquisition. + """ + + def to_float(str_arr): + arr = [] + for s in str_arr: + try: + num = float(s) + arr.append(num) + except ValueError: + pass + return arr + + self.patterns = self.patterns.strip('[').strip(']').split(', ') + self.patterns = [int(s) for s in self.patterns if s.isdigit()] + try: + self.patterns_wp = self.patterns_wp.text.strip('[').strip(']').split(', ') + self.patterns_wp = [int(s) for s in self.patterns_wp if s.isdigit()] + except: + print('patterns_wp has no attribute ''strip''') + + if self.wavelengths: + self.wavelengths = ( + self.wavelengths.strip('[').strip(']').split(', ')) + self.wavelengths = to_float(self.wavelengths) + self.wavelengths = np.asarray(self.wavelengths) + else: + print('wavelenghts not present in metadata.' + ' Reading data in legacy mode.') + + if self.timestamps: + self.timestamps = self.timestamps.strip('[').strip(']').split(', ') + self.timestamps = to_float(self.timestamps) + else: + print('timestamps not present in metadata.' + ' Reading data in legacy mode.') + + if self.measurement_time: + self.measurement_time = ( + self.measurement_time.strip('[').strip(']').split(', ')) + self.measurement_time = to_float(self.measurement_time) + else: + print('measurement_time not present in metadata.' + ' Reading data in legacy mode.') + + if self.mask_index: + self.mask_index = ( + self.mask_index.strip('[').strip(']').split(', ')) + self.mask_index = to_float(self.mask_index) + self.mask_index = np.asarray(self.mask_index) + else: + print('mask_index not present in metadata.' + ' Reading data in legacy mode.') + + if self.x_mask_coord: + self.x_mask_coord = ( + self.x_mask_coord.strip('[').strip(']').split(', ')) + self.x_mask_coord = to_float(self.x_mask_coord) + self.x_mask_coord = np.asarray(self.x_mask_coord) + else: + print('x_mask_coord not present in metadata.' + ' Reading data in legacy mode.') + + if self.y_mask_coord: + self.y_mask_coord = ( + self.y_mask_coord.strip('[').strip(']').split(', ')) + self.y_mask_coord = to_float(self.y_mask_coord) + self.y_mask_coord = np.asarray(self.y_mask_coord) + else: + print('y_mask_coord not present in metadata.' + ' Reading data in legacy mode.') + + @staticmethod + def readable_pattern_order(acquisition_params_dict: dict) -> dict: + """Turns list of patterns into a string. + + Turns the list of pattern attributes from an AcquisitionParameters + object (turned into a dictionary) into a string that will improve + readability once all metadata is dumped into a JSON file. + This function must be called before dumping. + + Args: + acquisition_params_dict (dict): Dictionary obtained from converting + an AcquisitionParameters object. + + Returns: + [dict]: Modified dictionary with acquisition parameters metadata. + """ + + def _hard_coded_conversion(data): + s = '[' + for value in data: + s += f'{value:.4f}, ' + s = s[:-2] + s += ']' + + return s + + readable_dict = acquisition_params_dict + readable_dict['patterns'] = str(readable_dict['patterns']) + readable_dict['patterns_wp'] = str(readable_dict['patterns_wp']) + + readable_dict['wavelengths'] = _hard_coded_conversion( + readable_dict['wavelengths']) + + readable_dict['timestamps'] = _hard_coded_conversion( + readable_dict['timestamps']) + + readable_dict['measurement_time'] = _hard_coded_conversion( + readable_dict['measurement_time']) + + readable_dict['mask_index'] = _hard_coded_conversion( + readable_dict['mask_index']) + + readable_dict['x_mask_coord'] = _hard_coded_conversion( + readable_dict['x_mask_coord']) + + readable_dict['y_mask_coord'] = _hard_coded_conversion( + readable_dict['y_mask_coord']) + + return readable_dict + + + def update_timings(self, timestamps: np.ndarray, + measurement_time: np.ndarray): + """Updates acquisition timings. + + Args: + timestamps (ndarray): + Array of `float` type elapsed time between each measurement made + by the spectrometer based on its internal clock. Units in + milliseconds. + measurement_time (ndarray): + Array of `float` type elapsed times between each callback. Units + in milliseconds. + """ + self.mean_callback_acquisition_time_ms = np.mean(measurement_time) + self.total_callback_acquisition_time_s = np.sum(measurement_time) / 1000 + self.mean_spectrometer_acquisition_time_ms = np.mean( + timestamps, dtype=float) + self.total_spectrometer_acquisition_time_s = np.sum(timestamps) / 1000 + + self.timestamps = timestamps + self.measurement_time = measurement_time + + + +@dataclass_json +@dataclass +class SpectrometerParameters: + """Class containing spectrometer configurations. + + Further information: AvaSpec Library Manual (Version 9.10.2.0). + + Attributes: + high_resolution (bool): + True if 16-bit AD Converter is used. False if 14-bit ADC is used. + initial_available_pixels (int): + Number of pixels available in spectrometer. + detector (str): + Name of the light detector. + firmware_version (str, optional): + Spectrometer firmware version. + dll_version (str, optional): + Spectrometer dll version. + fpga_version (str, optional): + Internal FPGA version. + integration_delay_ns (int, optional): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + integration_time_ms (float, optional): + Spectrometer exposure time during one scan in miliseconds. + start_pixel (int, optional): + Initial pixel data received from spectrometer. + stop_pixel (int, optional): + Last pixel data received from spectrometer. + averages (int, optional): + Number of averages in a single measurement. + dark_correction_enable (bool, optional): + Enable dynamic dark current correction. + dark_correction_forget_percentage (int, optional): + Percentage of the new dark value pixels that has to be used. e.g., + a percentage of 100 means only new dark values are used. A + percentage of 10 means that 10 percent of the new dark values is + used and 90 percent of the old values is used for drift correction. + smooth_pixels (int, optional): + Number of neighbor pixels used for smoothing, max. has to be smaller + than half the selected pixel range because both the pixels on the + left and on the right are used. + smooth_model (int, optional): + Smoothing model. Currently a single model is supported in which the + spectral data is averaged over a number of pixels on the detector + array. For example, if the smoothpix parameter is set to 2, the + spectral data for all pixels x(n) on the detector array will be + averaged with their neighbor pixels x(n-2), x(n-1), x(n+1) and + x(n+2). + saturation_detection (bool, optional): + Enable detection of saturation/overexposition in pixels. + trigger_mode (int, optional): + Trigger mode (0 = Software, 1 = Hardware, 2 = Single Scan). + trigger_source (int, optional): + Trigger source (0 = external trigger, 1 = sync input). + trigger_source_type (int, optional): + Trigger source type (0 = edge trigger, 1 = level trigger). + store_to_ram (int, optional): + Define how many scans can be stored in RAM. In DynamicRAM mode, can + be set to 0 to indicate infinite measurements. + configs: InitVar[MeasConfigType]: + Initialization object containing data to create SpectrometerData + object. Unnecessary if reconstructing object from JSON file Defaut + is None. + version_info: InitVar[Tuple[str]]: + Initialization variable used for receiving firmware, dll and FPGA + version data. Unnecessary if reconstructing object from JSON file. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'Spectrometer parameters'. + """ + + high_resolution: bool + initial_available_pixels: int + detector: str + firmware_version: Optional[str] = None + dll_version: Optional[str] = None + fpga_version: Optional[str] = None + + integration_delay_ns: Optional[int] = None + integration_time_ms: Optional[float] = None + + start_pixel: Optional[int] = None + stop_pixel: Optional[int] = None + averages: Optional[int] = None + + dark_correction_enable: Optional[bool] = None + dark_correction_forget_percentage: Optional[int] = None + + smooth_pixels: Optional[int] = None + smooth_model: Optional[int] = None + + saturation_detection: Optional[bool] = None + + trigger_mode: Optional[int] = None + trigger_source: Optional[int] = None + trigger_source_type: Optional[int] = None + + store_to_ram: Optional[int] = None + + configs: InitVar[MeasConfigType] = None + version_info: InitVar[Tuple[str]] = None + + class_description: str = 'Spectrometer parameters' + + + def __post_init__(self, configs: Optional[MeasConfigType] = None, + version_info: Optional[Tuple[str, str, str]] = None): + """Post initialization of attributes. + + Receives the data sent to spectrometer and some version data and unwraps + everything to set the majority of SpectrometerParameters's attributes. + During reconstruction from JSON, arguments of type InitVar (configs and + version_info) are set to None and the function does nothing, letting + initialization for the standard __init__ function. + + Args: + configs (MeasConfigType, optional): + Object containing configurations sent to spectrometer. + Defaults to None. + version_info (Tuple[str, str, str], optional): + Tuple containing firmware, dll and FPGA version data. Defaults + to None. + """ + if configs is None or version_info is None: + pass + + else: + self.fpga_version, self.firmware_version, self.dll_version = ( + version_info) + + self.integration_delay_ns = configs.m_IntegrationDelay + self.integration_time_ms = configs.m_IntegrationTime + + self.start_pixel = configs.m_StartPixel + self.stop_pixel = configs.m_StopPixel + self.averages = configs.m_NrAverages + + self.dark_correction_enable = configs.m_CorDynDark.m_Enable + self.dark_correction_forget_percentage = ( + configs.m_CorDynDark.m_ForgetPercentage) + + self.smooth_pixels = configs.m_Smoothing.m_SmoothPix + self.smooth_model = configs.m_Smoothing.m_SmoothModel + + self.saturation_detection = configs.m_SaturationDetection + + self.trigger_mode = configs.m_Trigger.m_Mode + self.trigger_source = configs.m_Trigger.m_Source + self.trigger_source_type = configs.m_Trigger.m_SourceType + + self.store_to_ram = configs.m_Control.m_StoreToRam + + +@dataclass_json +@dataclass +class DMDParameters: + """Class containing DMD configurations and status. + + Further information: ALP-4.2 API Description (14/04/2020). + + Attributes: + add_illumination_time_us (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + initial_memory (int): + Initial memory available before sending patterns to DMD. + dark_phase_time_us (int, optional): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. + illumination_time_us (int, optional): + Duration of the display of one pattern in a DMD sequence. Units in + microseconds. + picture_time_us (int, optional): + Time between the start of two consecutive pictures (i.e. this + parameter defines the image display rate). Units in microseconds. + synch_pulse_width_us (int, optional): + Duration of DMD's frame synch output pulse. Units in microseconds. + synch_pulse_delay (int, optional): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). + device_number (int, optional): + Serial number of the ALP device. + ALP_version (int, optional): + Version number of the ALP device. + id (int, optional): + ALP device identifier for a DMD provided by the API. + synch_polarity (str, optional): + Frame synch output signal polarity: 'High' or 'Low.' + trigger_edge (str, optional): + Trigger input signal slope. Can be a 'Falling' or 'Rising' edge. + type (str, optional): + Digital light processing (DLP) chip present in DMD. + usb_connection (bool, optional): + True if USB connection is ok. + ddc_fpga_temperature (float, optional): + Temperature of the DDC FPGA (IC4) at DMD connection. Units in °C. + apps_fpga_temperature (float, optional): + Temperature of the Applications FPGA (IC3) at DMD connection. Units + in °C. + pcb_temperature (float, optional): + Internal temperature of the temperature sensor IC (IC2) at DMD + connection. Units in °C. + display_height (int, optional): + DMD display height in pixels. + display_width (int, optional): + DMD display width in pixels. + patterns (int, optional): + Number of patterns uploaded to DMD. + unused_memory (int, optional): + Memory available after sending patterns to DMD. + bitplanes (int, optional): + Bit depth of the patterns to be displayed. Values supported from 1 + to 8. + DMD (InitVar[ALP4.ALP4], optional): + Initialization DMD object. Can be used to automatically fill most of + the DMDParameters' attributes. Unnecessary if reconstructing object + from JSON file. Defaut is None. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'DMD parameters'. + """ + + add_illumination_time_us: int + initial_memory: int + + dark_phase_time_us: Optional[int] = None + illumination_time_us: Optional[int] = None + picture_time_us: Optional[int] = None + synch_pulse_width_us: Optional[int] = None + synch_pulse_delay: Optional[int] = None + + device_number: Optional[int] = None + ALP_version: Optional[int] = None + id: Optional[int] = None + + synch_polarity: Optional[str] = None + trigger_edge: Optional[str] = None + + # synch_polarity_OUT1: Optional[str] = None + # synch_period_OUT1: Optional[str] = None + # synch_gate_OUT1: Optional[str] = None + + type: Optional[str] = None + usb_connection: Optional[bool] = None + + ddc_fpga_temperature: Optional[float] = None + apps_fpga_temperature: Optional[float] = None + pcb_temperature: Optional[float] = None + + display_height: Optional[int] = None + display_width: Optional[int] = None + + patterns: Optional[int] = None + patterns_wp: Optional[int] = None + unused_memory: Optional[int] = None + bitplanes: Optional[int] = None + + DMD: InitVar[ALP4.ALP4] = None + + class_description: str = 'DMD parameters' + + + def __post_init__(self, DMD: Optional[ALP4.ALP4] = None): + """ Post initialization of attributes. + + Receives a DMD object and directly asks it for its configurations and + status, then sets the majority of SpectrometerParameters's attributes. + During reconstruction from JSON, DMD is set to None and the function + does nothing, letting initialization for the standard __init__ function. + + Args: + DMD (ALP4.ALP4, optional): + Connected DMD. Defaults to None. + """ + if DMD == None: + pass + + else: + self.device_number = DMD.DevInquire(ALP4.ALP_DEVICE_NUMBER) + self.ALP_version = DMD.DevInquire(ALP4.ALP_VERSION) + self.id = DMD.ALP_ID.value + + polarity = DMD.DevInquire(ALP4.ALP_SYNCH_POLARITY) + if polarity == 2006: + self.synch_polarity = 'High' + elif polarity == 2007: + self.synch_polarity = 'Low' + + edge = DMD.DevInquire(ALP4.ALP_TRIGGER_EDGE) + if edge == 2008: + self.trigger_edge = 'Falling' + elif edge == 2009: + self.trigger_edge = 'Rising' + + # synch_polarity_OUT1 = + + self.type = DMDTypes(DMD.DevInquire(ALP4.ALP_DEV_DMDTYPE)) + + if DMD.DevInquire(ALP4.ALP_USB_CONNECTION) == 0: + self.usb_connection = True + else: + self.usb_connection = False + + # Temperatures converted to °C + self.ddc_fpga_temperature = DMD.DevInquire( + ALP4.ALP_DDC_FPGA_TEMPERATURE)/256 + self.apps_fpga_temperature = DMD.DevInquire( + ALP4.ALP_APPS_FPGA_TEMPERATURE)/256 + self.pcb_temperature = DMD.DevInquire( + ALP4.ALP_PCB_TEMPERATURE)/256 + + self.display_width = DMD.nSizeX + self.display_height = DMD.nSizeY + + + def update_memory(self, unused_memory: int): + + self.unused_memory = unused_memory + self.patterns = self.initial_memory - unused_memory + + + def update_sequence_parameters(self, add_illumination_time, + DMD: Optional[ALP4.ALP4] = None): + + self.bitplanes = DMD.SeqInquire(ALP4.ALP_BITPLANES) + self.illumination_time_us = DMD.SeqInquire(ALP4.ALP_ILLUMINATE_TIME) + self.picture_time_us = DMD.SeqInquire(ALP4.ALP_PICTURE_TIME) + self.dark_phase_time_us = self.picture_time_us - self.illumination_time_us + self.synch_pulse_width_us = DMD.SeqInquire(ALP4.ALP_SYNCH_PULSEWIDTH) + self.synch_pulse_delay = DMD.SeqInquire(ALP4.ALP_SYNCH_DELAY) + self.add_illumination_time_us = add_illumination_time + + + + + +def read_metadata(file_path: str) -> Tuple[MetaData, + AcquisitionParameters, + SpectrometerParameters, + DMDParameters]: + """Reads metadata of a previous acquisition from JSON file. + + Args: + file_path (str): + Name of JSON file containing all metadata. + + Returns: + Tuple[MetaData, AcquisitionParameters, SpectrometerParameters, + DMDParameters]: + saved_metadata (MetaData): + Metadata object read from JSON. + saved_acquisition_params(AcquisitionParameters): + AcquisitionParameters object read from JSON. + saved_spectrometer_params(SpectrometerParameters): + SpectrometerParameters object read from JSON. + saved_dmd_params(DMDParameters): + DMDParameters object read from JSON. + """ + + file = open(file_path,'r') + data = json.load(file) + file.close() + + for object in data: + if object['class_description'] == 'Metadata': + saved_metadata = MetaData.from_dict(object) + if object['class_description'] == 'Acquisition parameters': + saved_acquisition_params = AcquisitionParameters.from_dict(object) + saved_acquisition_params.undo_readable_pattern_order() + if object['class_description'] == 'Spectrometer parameters': + saved_spectrometer_params = SpectrometerParameters.from_dict(object) + if object['class_description'] == 'DMD parameters': + saved_dmd_params = DMDParameters.from_dict(object) + + return (saved_metadata, saved_acquisition_params, + saved_spectrometer_params, saved_dmd_params) + +def read_metadata_2arms(file_path: str) -> Tuple[MetaData, + AcquisitionParameters, + SpectrometerParameters, + DMDParameters, + CAM]: + """Reads metadata of a previous acquisition from JSON file. + + Args: + file_path (str): + Name of JSON file containing all metadata. + + Returns: + Tuple[MetaData, AcquisitionParameters, SpectrometerParameters, + DMDParameters]: + saved_metadata (MetaData): + Metadata object read from JSON. + saved_acquisition_params(AcquisitionParameters): + AcquisitionParameters object read from JSON. + saved_spectrometer_params(SpectrometerParameters): + SpectrometerParameters object read from JSON. + saved_dmd_params(DMDParameters): + DMDParameters object read from JSON. + """ + + file = open(file_path,'r') + data = json.load(file) + file.close() + + for object in data: + if object['class_description'] == 'Metadata': + saved_metadata = MetaData.from_dict(object) + if object['class_description'] == 'Acquisition parameters': + saved_acquisition_params = AcquisitionParameters.from_dict(object) + saved_acquisition_params.undo_readable_pattern_order() + if object['class_description'] == 'Spectrometer parameters': + saved_spectrometer_params = SpectrometerParameters.from_dict(object) + if object['class_description'] == 'DMD parameters': + saved_dmd_params = DMDParameters.from_dict(object) + if object['class_description'] == 'IDS camera configuration': + saved_cam_params = CAM.from_dict(object) + saved_cam_params.undo_readable_class_CAM() + + + return (saved_metadata, saved_acquisition_params, + saved_spectrometer_params, saved_dmd_params, saved_cam_params) + +def save_metadata(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + acquisition_parameters: AcquisitionParameters) -> None: + """Saves metadata to JSON file. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + Class containing DMD configurations and status. + spectrometer_params (SpectrometerParameters): + Object containing spectrometer configurations. + acquisition_parameters (AcquisitionParameters): + Object containing acquisition specifications and timing results. + """ + + path = Path(metadata.output_directory) + with open( + path / f'{metadata.experiment_name}_metadata.json', + 'w', encoding='utf8') as output: + + output_params = [ + metadata.to_dict(), + DMD_params.to_dict(), + spectrometer_params.to_dict(), + AcquisitionParameters.readable_pattern_order( + acquisition_parameters.to_dict())] + + json.dump(output_params,output,ensure_ascii=False,indent=4) + +def save_metadata_2arms(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + camPar : CAM, + acquisition_parameters: AcquisitionParameters) -> None: + """Saves metadata to JSON file. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + Class containing DMD configurations and status. + spectrometer_params (SpectrometerParameters): + Object containing spectrometer configurations. + acquisition_parameters (AcquisitionParameters): + Object containing acquisition specifications and timing results. + """ + + path = Path(metadata.output_directory) + with open( + path / f'{metadata.experiment_name}_metadata.json', + 'w', encoding='utf8') as output: + + output_params = [ + metadata.to_dict(), + DMD_params.to_dict(), + spectrometer_params.to_dict(), + CAM.readable_class_CAM(camPar.to_dict()), + AcquisitionParameters.readable_pattern_order(acquisition_parameters.to_dict())] + + json.dump(output_params, output, ensure_ascii=False, indent=4)#, default=convert) + + # with open(path / f'{metadata.experiment_name}_metadata_cam.pkl', 'wb') as f: + # pickle.dump(camPar.__dict__, f) + +@dataclass_json +@dataclass +class func_path: + def __init__(self, data_folder_name, data_name, ask_overwrite=False): + if not os.path.exists('../data/' + data_folder_name): + os.makedirs('../data/' + data_folder_name) + + if not os.path.exists('../data/' + data_folder_name + '/' + data_name): + os.makedirs('../data/' + data_folder_name + '/' + data_name) + aborted = False + elif ask_overwrite == True: + res = input('Acquisition already exists, overwrite it ?[y/n]') + if res == 'n': + aborted = True + else: + aborted = False + else: + aborted = True + + self.aborted = aborted + self.subfolder_path = '../data/' + data_folder_name + '/' + data_name + self.overview_path = self.subfolder_path + '/overview' + if not os.path.exists(self.overview_path): + os.makedirs(self.overview_path) + + self.data_name = data_name + self.data_path = self.subfolder_path + '/' + data_name + self.had_reco_path = self.data_path + '_had_reco.npz' + self.fig_had_reco_path = self.overview_path + '/' + data_name + self.pathIDSsnapshot = Path(self.data_path + '_IDScam_before_acq.npy') + self.pathIDSsnapshot_overview = self.overview_path + '/' + data_name + '_IDScam_before_acq.png' + self.nn_reco_path = self.data_path + '_nn_reco.npz' + self.fig_nn_reco_path = self.overview_path + '/' + data_name + + diff --git a/spas/metadata_SPC2D.py b/spas/metadata_SPC2D.py new file mode 100644 index 0000000..344b1dd --- /dev/null +++ b/spas/metadata_SPC2D.py @@ -0,0 +1,1118 @@ +# -*- coding: utf-8 -*- +__author__ = 'Guilherme Beneti Martins' + +"""Metadata classes and utilities. + +Metadata classes to keep and save all relevant data during an acquisition. +Utility functions to recreate objects from JSON files, save them to JSON and to +improve readability. +""" + +import json +from datetime import datetime +from enum import IntEnum +from dataclasses import dataclass, InitVar, field +from typing import Optional, Union, List, Tuple, Optional +from pathlib import Path +import os +from dataclasses_json import dataclass_json +import numpy as np +import ctypes as ct +import pickle +##### DLL for the DMD +try: + import ALP4 +except: # in the cas the DLL of the DMD is not installed + class ALP4: + pass + setattr(ALP4, 'ALP4', None) + print('DLL of the DMD not installed') +##### DLL for the spectrometer Avantes +try: + from msl.equipment.resources.avantes import MeasConfigType +except: # in the cas the DLL of the spectrometer is not installed + class MeasConfigType: + pass + MeasConfigType = None + print('DLL of the spectrometer not installed !!!') + +##### DLL for the camera +try: + from pyueye import ueye + dll_pyueye_installed = 1 +except: + dll_pyueye_installed = 0 + print('DLL of the cam not installed !!') + +class DMDTypes(IntEnum): + """Enumeration of DMD types and respective codes.""" + ALP_DMDTYPE_XGA = 1 + ALP_DMDTYPE_SXGA_PLUS = 2 + ALP_DMDTYPE_1080P_095A = 3 + ALP_DMDTYPE_XGA_07A = 4 + ALP_DMDTYPE_XGA_055A = 5 + ALP_DMDTYPE_XGA_055X = 6 + ALP_DMDTYPE_WUXGA_096A = 7 + ALP_DMDTYPE_WQXGA_400MHZ_090A = 8 + ALP_DMDTYPE_WQXGA_480MHZ_090A = 9 + ALP_DMDTYPE_WXGA_S450 = 12 + ALP_DMDTYPE_DISCONNECT = 255 + + +@dataclass_json +@dataclass +class MetaData: + """ Class containing overall acquisition parameters and description. + + Metadata concerning the experiment, paths, file inputs and file outputs. + This class is adapted to be reconstructed from a JSON file. + + Attributes: + output_directory (Union[str, Path], optional): + Directory where multiple related acquisitions will be stored. + pattern_order_source (Union[str, Path], optional): + File where the order of patterns to be sent to DMD is specified. It + can be a text file containing a list of pattern indeces or a numpy + file containing a covariance matrix from which the pattern order is + calculated. + pattern_source (Union[str, Path], optional): + Pattern source folder. + pattern_prefix (str): + Prefix used in pattern naming. + experiment_name (str): + Prefix of all files related to a single acquisition. Files will + appear with the following string pattern: + experiment_name + '_' + filename. + light_source (str): + Light source used to illuminate an object during acquisition. + object (str): + Object imaged during acquisition. + filter (str): + Light filter used. + description (str): + Acqusition experiment description. + date (str, optional): + Acquisition date. Automatically set when object is created. Default + is None. + time (str, optional): + Time when metadata object is created. Set automatically by + __post_init__(). Default is None. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'Metadata'. + """ + + pattern_prefix: str + experiment_name: str + + light_source: str + object: str + filter: str + description: str + + output_directory: Union[str, Path] + pattern_order_source: Union[str, Path] + pattern_source: Union[str, Path] + + date: Optional[str] = None + time: Optional[str] = None + + class_description: str = 'Metadata' + + def __post_init__(self): + """Sets time and date of object cretion and deals with paths""" + + today = datetime.today() + self.date = '--/--/----' #today.strftime('%d/%m/%Y') + self.time = today.strftime('%I:%M:%S %p') + + # If parameter is str, turn it into Path + if isinstance(self.output_directory, str): + self.output_directory = Path(self.output_directory) + + # If parameter is Path or was turned into a Path, resolve it and get the + # str format. + if issubclass(self.output_directory.__class__, Path): + self.output_directory = str(self.output_directory.resolve()) + + + if isinstance(self.pattern_order_source, str): + self.pattern_order_source = Path(self.pattern_order_source) + + if issubclass(self.pattern_order_source.__class__, Path): + self.pattern_order_source = str( + self.pattern_order_source.resolve()) + + + if isinstance(self.pattern_source, str): + self.pattern_source = Path(self.pattern_source) + + if issubclass(self.pattern_source.__class__, Path): + self.pattern_source = str(self.pattern_source.resolve()) + + +@dataclass_json +@dataclass +class CAM: + """Class containing IDS camera configurations. + + Further information: https://en.ids-imaging.com/manuals/ids-software-suite/ueye-manual/4.95/en/c_programmierung.html. + + Attributes: + hCam (ueye.c_uint): Handle of the camera. + sInfo (ueye.SENSORINFO):sensor information : [SensorID [c_ushort] = 566; + strSensorName [c_char_Array_32] = b'UI388xCP-M'; + nColorMode [c_char] = b'\x01'; + nMaxWidth [c_uint] = 3088; + nMaxHeight [c_uint] = 2076; + bMasterGain [c_int] = 1; + bRGain [c_int] = 0; + bGGain [c_int] = 0; + bBGain [c_int] = 0; + bGlobShutter [c_int] = 0; + wPixelSize [c_ushort] = 240; + nUpperLeftBayerPixel [c_char] = b'\x00'; + Reserved]. + cInfo (ueye.BOARDINFO):Camera information: [SerNo [c_char_Array_12] = b'4103219888'; + ID [c_char_Array_20] = b'IDS GmbH'; + Version [c_char_Array_10] = b''; + Date [c_char_Array_12] = b'30.11.2017'; + Select [c_ubyte] = 1; + Type [c_ubyte] = 100; + Reserved [c_char_Array_8] = b'';] + nBitsPerPixel (ueye.c_int): number of bits per pixel (8 for monochrome, 24 for color). + m_nColorMode (ueye.c_int): color mode : Y8/RGB16/RGB24/REG32. + bytes_per_pixel (int): bytes_per_pixel = int(nBitsPerPixel / 8). + rectAOI (ueye.IS_RECT()): rectangle of the Area Of Interest (AOI): s32X [c_int] = 0; + s32Y [c_int] = 0; + s32Width [c_int] = 3088; + s32Height [c_int] = 2076; + pcImageMemory (ueye.c_mem_p()): memory allocation. + MemID (ueye.int()): memory identifier. + pitch (ueye.INT()): ???. + fps (float): set frame per second. + gain (int): Set gain between [0 - 100]. + gainBoost (str): Activate gain boosting ("ON") or deactivate ("OFF"). + gamma (float): Set Gamma between [1 - 2.5] to change the image contrast + exposureTime (float): Set the exposure time between [0.032 - 56.221] + blackLevel (int): Set the black level between [0 - 255] to set an offset in the image. It is adviced to put 5 for noise measurement + camActivated (bool) : need to to know if the camera is ready to acquire (1: yes, 0: No) + pixelClock (int) : the pixel clock, three values possible : [118, 237, 474] (MHz) + bandwidth (float) the bandwidth (in MByte/s) is an approximate value which is calculated based on the pixel clock + Memory (bool) : a boolean to know if the memory inside the camera is busy [1] or free [0] + Exit (int) : if Exit = 2 => excute is_ExitCamera function (disables the hCam camera handle and releases the memory) | if Exit = 0 => allow to init cam, after that, Exit = 1 + vidFormat (str) : save video in the format avi or bin (for binary) + gate_period (int) : a second TTL is sent by the DMD to trigg the camera, and based on the fisrt TTL to trigg the spectrometer. camera trigger period = gate_period*(spectrometer trigger period) + trigger_mode (str) : hard or soft + avi (ueye.int) : A pointer that returns the instance ID which is needed for calling the other uEye AVI functions + punFileID (ueye.c_int) : a pointer in which the instance ID is returned. This ID is needed for calling other functions. + timeout (int) : a time which stop the camera that waiting for a TTL + time_array (List[float]) : the time array saved after each frame received on the camera + int_time_spect (float) : is egal to the integration time of the spectrometer, it is need to know this value because of the rolling shutter of the monochrome IDS camera + black_pattern_num (int) : is number inside the image name of the black pattern (for the hyperspectral arm, or white pattern for the camera arm) to be inserted betweem the Hadamard patterns + insert_patterns (int) : 0 => no insertion / 1=> insert white patterns for the camera + acq_mode (str) : mode of the acquisition => 'video' or 'snapshot' mode + """ + if dll_pyueye_installed: + hCam: Optional[ueye.c_uint] = None + sInfo: Optional[ueye.SENSORINFO] = None + cInfo: Optional[ueye.BOARDINFO] = None + nBitsPerPixel: Optional[ueye.c_int] = None + m_nColorMode: Optional[ueye.c_int] = None + bytes_per_pixel: Optional[int] = None + rectAOI: Optional[ueye.IS_RECT] = None + pcImageMemory: Optional[ueye.c_mem_p] = None + MemID: Optional[ueye.c_int] = None + pitch: Optional[ueye.c_int] = None + fps: Optional[float] = None + gain: Optional[int] = None + gainBoost: Optional[str] = None + gamma: Optional[float] = None + exposureTime: Optional[float] = None + blackLevel: Optional[int] = None + camActivated : Optional[bool] = None + pixelClock : Optional[int] = None + bandwidth : Optional[float] = None + Memory : Optional[bool] = None + Exit : Optional[int] = None + vidFormat : Optional[str] = None + gate_period : Optional[int] = None + trigger_mode : Optional[str] = None + avi : Optional[ueye.int] = None + punFileID : Optional[ueye.c_int] = None + timeout : Optional[int] = None + time_array : Optional[Union[List[float], str]] = field(default=None, repr=False) + int_time_spect : Optional[float] = None + black_pattern_num : Optional[int] = None + insert_patterns : Optional[int] = None + acq_mode : Optional[str] = None + + class_description: str = 'IDS camera configuration' + + def undo_readable_class_CAM(self) -> None: + """Changes the time_array attribute from `str` to `List` of `int`.""" + + def to_float(str_arr): + arr = [] + for s in str_arr: + try: + num = float(s) + arr.append(num) + except ValueError: + pass + return arr + + if self.time_array: + self.time_array = ( + self.time_array.strip('[').strip(']').split(', ')) + self.time_array = to_float(self.time_array) + self.time_array = np.asarray(self.time_array) + + @staticmethod + def readable_class_CAM(cam_params_dict: dict) -> dict: + # pass + """Turns list of time_array into a string. + convert the c_type structure (sInfo, cInfo and rectAOI) into a nested dict + change the bytes type item into str + change the c_types item into their value + """ + + readable_cam_dict = {} + readable_cam_dict_temp = cam_params_dict#camPar.to_dict()# + inc = 0 + for item in readable_cam_dict_temp: + stri = str(type(readable_cam_dict_temp[item])) + # print('----- item : ' + item) + if item == 'sInfo' or item == 'cInfo' or item == 'rectAOI': + readable_cam_dict[item] = dict() + try: + for sub_item in readable_cam_dict_temp[item]._fields_: + new_item = item + '-' + sub_item[0] + try: + att = getattr(readable_cam_dict_temp[item], sub_item[0]).value + except: + att = getattr(readable_cam_dict_temp[item], sub_item[0]) + + if type(att) == bytes: + att = str(att) + + readable_cam_dict[item][sub_item[0]] = att + except: + try: + for sub_item in readable_cam_dict_temp[item]: + # print('----- sub_item : ' + sub_item) + new_item = item + '-' + sub_item + att = readable_cam_dict_temp[item][sub_item] + + if type(att) == bytes: + att = str(att) + + readable_cam_dict[item][sub_item] = att + except: + print('warning, impossible to read the subitem of readable_cam_dict_temp[item]') + + elif stri.find('pyueye') >=0: + try: + readable_cam_dict[item] = readable_cam_dict_temp[item].value + except: + readable_cam_dict[item] = readable_cam_dict_temp[item] + elif item == 'time_array': + readable_cam_dict[item] = str(readable_cam_dict_temp[item]) + else: + readable_cam_dict[item] = readable_cam_dict_temp[item] + + return readable_cam_dict + + +@dataclass_json +@dataclass +class AcquisitionParameters: + """Class containing acquisition specifications and timing results. + + This class is adapted to be reconstructed from a JSON file. + + Attributes: + pattern_compression (float): + Percentage of total available patterns to be present in an + acquisition sequence. + pattern_dimension_x (int): + Length of reconstructed image that defines pattern length. + pattern_dimension_y (int): + Width of reconstructed image that defines pattern width. + zoom (int): + numerical zoom of the patterns + xw_offset (int): + offset of the pattern in the DMD for zoom > 1 in the width (x) direction + yh_offset (int): + offset of the pattern in the DMD for zoom > 1 in the heihgt (y) direction + mask_index (Union[np.ndarray, str], optional): + Array of `int` type corresponding to the index of the mask vector where + the value is egal to 1 + x_mask_coord (Union[np.ndarray, str], optional): + coordinates of the mask in the x direction x[0] and x[1] are the first + and last points respectively + y_mask_coord (Union[np.ndarray, str], optional): + coordinates of the mask in the y direction y[0] and y[1] are the first + and last points respectively + pattern_amount (int, optional): + Quantity of patterns sent to DMD for an acquisition. This value is + calculated by an external function. Default in None. + acquired_spectra (int, optional): + Amount of spectra actually read from the spectrometer. This value is + calculated by an external function. Default in None. + mean_callback_acquisition_time_ms (float, optional): + Mean time between 2 callback executions during an acquisition. This + value is calculated by an external function. Default in None. + total_callback_acquisition_time_s (float, optional): + Total time of callback executions during an acquisition. This value + is calculated by an external function. Default in None. + mean_spectrometer_acquisition_time_ms (float, optional): + Mean time between 2 spectrometer measurements during an acquisition + based on its own internal clock. This value is calculated by an + external function. Default in None. + total_spectrometer_acquisition_time_s (float, optional): + Total time of spectrometer measurements during an acquisition + based on its own internal clock. This value is calculated by an + external function. Default in None. + saturation_detected (bool, optional): + Boolean incating if saturation was detected during acquisition. + Default is None. + patterns (Union[List[int],str], optional) = None + List `int` or `str` containing all patterns sent to the DMD for an + acquisition sequence. This value is set by an external function and + its type can be modified by multiple functions during object + creation, manipulation, when dumping to a JSON file or + when reconstructing an AcquisitionParameters object from a JSON + file. It is intended to be of type List[int] most of the execution + List[int]time. Default is None. + wavelengths (Union[np.ndarray, str], optional): + Array of `float` type corresponding to the wavelengths associated + with spectrometer's start and stop pixels. + timestamps (Union[List[float], str], optional): + List of `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. Units in + milliseconds. Default is None. + measurement_time (Union[List[float], str], optional): + List of `float` type elapsed times between each callback. Units in + milliseconds. Default is None. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'Acquisition parameters'. + """ + + pattern_compression: float + pattern_dimension_x: int + pattern_dimension_y: int + zoom: Optional[int] = field(default=None) + xw_offset: Optional[int] = field(default=None) + yh_offset: Optional[int] = field(default=None) + mask_index: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + x_mask_coord: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + y_mask_coord: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + + pattern_amount: Optional[int] = None + acquired_spectra: Optional[int] = None + + mean_callback_acquisition_time_ms: Optional[float] = None + total_callback_acquisition_time_s: Optional[float] = None + mean_spectrometer_acquisition_time_ms: Optional[float] = None + total_spectrometer_acquisition_time_s: Optional[float] = None + + saturation_detected: Optional[bool] = None + + patterns: Optional[Union[List[int], str]] = field(default=None, repr=False) + patterns_wp: Optional[Union[List[int], str]] = field(default=None, repr=False) + wavelengths: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + timestamps: Optional[Union[List[float], str]] = field(default=None, + repr=False) + measurement_time: Optional[Union[List[float], str]] = field(default=None, + repr=False) + + class_description: str = 'Acquisition parameters' + + + def undo_readable_pattern_order(self) -> None: + """Changes the patterns attribute from `str` to `List` of `int`. + + When reconstructing an AcquisitionParameters object from a JSON file, + this method turns the patterns, wavelengths, timestamps and + measurement_time attributes from a string to a list of integers + containing the pattern indices used in that acquisition. + """ + + def to_float(str_arr): + arr = [] + for s in str_arr: + try: + num = float(s) + arr.append(num) + except ValueError: + pass + return arr + + self.patterns = self.patterns.strip('[').strip(']').split(', ') + self.patterns = [int(s) for s in self.patterns if s.isdigit()] + try: + self.patterns_wp = self.patterns_wp.text.strip('[').strip(']').split(', ') + self.patterns_wp = [int(s) for s in self.patterns_wp if s.isdigit()] + except: + print('patterns_wp has no attribute ''strip''') + + if self.wavelengths: + self.wavelengths = ( + self.wavelengths.strip('[').strip(']').split(', ')) + self.wavelengths = to_float(self.wavelengths) + self.wavelengths = np.asarray(self.wavelengths) + else: + print('wavelenghts not present in metadata.' + ' Reading data in legacy mode.') + + if self.timestamps: + self.timestamps = self.timestamps.strip('[').strip(']').split(', ') + self.timestamps = to_float(self.timestamps) + else: + print('timestamps not present in metadata.' + ' Reading data in legacy mode.') + + if self.measurement_time: + self.measurement_time = ( + self.measurement_time.strip('[').strip(']').split(', ')) + self.measurement_time = to_float(self.measurement_time) + else: + print('measurement_time not present in metadata.' + ' Reading data in legacy mode.') + + if self.mask_index: + self.mask_index = ( + self.mask_index.strip('[').strip(']').split(', ')) + self.mask_index = to_float(self.mask_index) + self.mask_index = np.asarray(self.mask_index) + else: + print('mask_index not present in metadata.' + ' Reading data in legacy mode.') + + if self.x_mask_coord: + self.x_mask_coord = ( + self.x_mask_coord.strip('[').strip(']').split(', ')) + self.x_mask_coord = to_float(self.x_mask_coord) + self.x_mask_coord = np.asarray(self.x_mask_coord) + else: + print('x_mask_coord not present in metadata.' + ' Reading data in legacy mode.') + + if self.y_mask_coord: + self.y_mask_coord = ( + self.y_mask_coord.strip('[').strip(']').split(', ')) + self.y_mask_coord = to_float(self.y_mask_coord) + self.y_mask_coord = np.asarray(self.y_mask_coord) + else: + print('y_mask_coord not present in metadata.' + ' Reading data in legacy mode.') + + @staticmethod + def readable_pattern_order(acquisition_params_dict: dict) -> dict: + """Turns list of patterns into a string. + + Turns the list of pattern attributes from an AcquisitionParameters + object (turned into a dictionary) into a string that will improve + readability once all metadata is dumped into a JSON file. + This function must be called before dumping. + + Args: + acquisition_params_dict (dict): Dictionary obtained from converting + an AcquisitionParameters object. + + Returns: + [dict]: Modified dictionary with acquisition parameters metadata. + """ + + def _hard_coded_conversion(data): + s = '[' + for value in data: + s += f'{value:.4f}, ' + s = s[:-2] + s += ']' + + return s + + readable_dict = acquisition_params_dict + readable_dict['patterns'] = str(readable_dict['patterns']) + readable_dict['patterns_wp'] = str(readable_dict['patterns_wp']) + + readable_dict['wavelengths'] = _hard_coded_conversion( + readable_dict['wavelengths']) + + readable_dict['timestamps'] = _hard_coded_conversion( + readable_dict['timestamps']) + + readable_dict['measurement_time'] = _hard_coded_conversion( + readable_dict['measurement_time']) + + readable_dict['mask_index'] = _hard_coded_conversion( + readable_dict['mask_index']) + + readable_dict['x_mask_coord'] = _hard_coded_conversion( + readable_dict['x_mask_coord']) + + readable_dict['y_mask_coord'] = _hard_coded_conversion( + readable_dict['y_mask_coord']) + + return readable_dict + + + def update_timings(self, timestamps: np.ndarray, + measurement_time: np.ndarray): + """Updates acquisition timings. + + Args: + timestamps (ndarray): + Array of `float` type elapsed time between each measurement made + by the spectrometer based on its internal clock. Units in + milliseconds. + measurement_time (ndarray): + Array of `float` type elapsed times between each callback. Units + in milliseconds. + """ + self.mean_callback_acquisition_time_ms = np.mean(measurement_time) + self.total_callback_acquisition_time_s = np.sum(measurement_time) / 1000 + self.mean_spectrometer_acquisition_time_ms = np.mean( + timestamps, dtype=float) + self.total_spectrometer_acquisition_time_s = np.sum(timestamps) / 1000 + + self.timestamps = timestamps + self.measurement_time = measurement_time + + + +@dataclass_json +@dataclass +class SpectrometerParameters: + """Class containing spectrometer configurations. + + Further information: AvaSpec Library Manual (Version 9.10.2.0). + + Attributes: + high_resolution (bool): + True if 16-bit AD Converter is used. False if 14-bit ADC is used. + initial_available_pixels (int): + Number of pixels available in spectrometer. + detector (str): + Name of the light detector. + firmware_version (str, optional): + Spectrometer firmware version. + dll_version (str, optional): + Spectrometer dll version. + fpga_version (str, optional): + Internal FPGA version. + integration_delay_ns (int, optional): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + integration_time_ms (float, optional): + Spectrometer exposure time during one scan in miliseconds. + start_pixel (int, optional): + Initial pixel data received from spectrometer. + stop_pixel (int, optional): + Last pixel data received from spectrometer. + averages (int, optional): + Number of averages in a single measurement. + dark_correction_enable (bool, optional): + Enable dynamic dark current correction. + dark_correction_forget_percentage (int, optional): + Percentage of the new dark value pixels that has to be used. e.g., + a percentage of 100 means only new dark values are used. A + percentage of 10 means that 10 percent of the new dark values is + used and 90 percent of the old values is used for drift correction. + smooth_pixels (int, optional): + Number of neighbor pixels used for smoothing, max. has to be smaller + than half the selected pixel range because both the pixels on the + left and on the right are used. + smooth_model (int, optional): + Smoothing model. Currently a single model is supported in which the + spectral data is averaged over a number of pixels on the detector + array. For example, if the smoothpix parameter is set to 2, the + spectral data for all pixels x(n) on the detector array will be + averaged with their neighbor pixels x(n-2), x(n-1), x(n+1) and + x(n+2). + saturation_detection (bool, optional): + Enable detection of saturation/overexposition in pixels. + trigger_mode (int, optional): + Trigger mode (0 = Software, 1 = Hardware, 2 = Single Scan). + trigger_source (int, optional): + Trigger source (0 = external trigger, 1 = sync input). + trigger_source_type (int, optional): + Trigger source type (0 = edge trigger, 1 = level trigger). + store_to_ram (int, optional): + Define how many scans can be stored in RAM. In DynamicRAM mode, can + be set to 0 to indicate infinite measurements. + configs: InitVar[MeasConfigType]: + Initialization object containing data to create SpectrometerData + object. Unnecessary if reconstructing object from JSON file Defaut + is None. + version_info: InitVar[Tuple[str]]: + Initialization variable used for receiving firmware, dll and FPGA + version data. Unnecessary if reconstructing object from JSON file. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'Spectrometer parameters'. + """ + + high_resolution: bool + initial_available_pixels: int + detector: str + firmware_version: Optional[str] = None + dll_version: Optional[str] = None + fpga_version: Optional[str] = None + + integration_delay_ns: Optional[int] = None + integration_time_ms: Optional[float] = None + + start_pixel: Optional[int] = None + stop_pixel: Optional[int] = None + averages: Optional[int] = None + + dark_correction_enable: Optional[bool] = None + dark_correction_forget_percentage: Optional[int] = None + + smooth_pixels: Optional[int] = None + smooth_model: Optional[int] = None + + saturation_detection: Optional[bool] = None + + trigger_mode: Optional[int] = None + trigger_source: Optional[int] = None + trigger_source_type: Optional[int] = None + + store_to_ram: Optional[int] = None + + configs: InitVar[MeasConfigType] = None + version_info: InitVar[Tuple[str]] = None + + class_description: str = 'Spectrometer parameters' + + + def __post_init__(self, configs: Optional[MeasConfigType] = None, + version_info: Optional[Tuple[str, str, str]] = None): + """Post initialization of attributes. + + Receives the data sent to spectrometer and some version data and unwraps + everything to set the majority of SpectrometerParameters's attributes. + During reconstruction from JSON, arguments of type InitVar (configs and + version_info) are set to None and the function does nothing, letting + initialization for the standard __init__ function. + + Args: + configs (MeasConfigType, optional): + Object containing configurations sent to spectrometer. + Defaults to None. + version_info (Tuple[str, str, str], optional): + Tuple containing firmware, dll and FPGA version data. Defaults + to None. + """ + if configs is None or version_info is None: + pass + + else: + self.fpga_version, self.firmware_version, self.dll_version = ( + version_info) + + self.integration_delay_ns = configs.m_IntegrationDelay + self.integration_time_ms = configs.m_IntegrationTime + + self.start_pixel = configs.m_StartPixel + self.stop_pixel = configs.m_StopPixel + self.averages = configs.m_NrAverages + + self.dark_correction_enable = configs.m_CorDynDark.m_Enable + self.dark_correction_forget_percentage = ( + configs.m_CorDynDark.m_ForgetPercentage) + + self.smooth_pixels = configs.m_Smoothing.m_SmoothPix + self.smooth_model = configs.m_Smoothing.m_SmoothModel + + self.saturation_detection = configs.m_SaturationDetection + + self.trigger_mode = configs.m_Trigger.m_Mode + self.trigger_source = configs.m_Trigger.m_Source + self.trigger_source_type = configs.m_Trigger.m_SourceType + + self.store_to_ram = configs.m_Control.m_StoreToRam + + +@dataclass_json +@dataclass +class DMDParameters: + """Class containing DMD configurations and status. + + Further information: ALP-4.2 API Description (14/04/2020). + + Attributes: + add_illumination_time_us (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + initial_memory (int): + Initial memory available before sending patterns to DMD. + dark_phase_time_us (int, optional): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. + illumination_time_us (int, optional): + Duration of the display of one pattern in a DMD sequence. Units in + microseconds. + picture_time_us (int, optional): + Time between the start of two consecutive pictures (i.e. this + parameter defines the image display rate). Units in microseconds. + synch_pulse_width_us (int, optional): + Duration of DMD's frame synch output pulse. Units in microseconds. + synch_pulse_delay (int, optional): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). + device_number (int, optional): + Serial number of the ALP device. + ALP_version (int, optional): + Version number of the ALP device. + id (int, optional): + ALP device identifier for a DMD provided by the API. + synch_polarity (str, optional): + Frame synch output signal polarity: 'High' or 'Low.' + trigger_edge (str, optional): + Trigger input signal slope. Can be a 'Falling' or 'Rising' edge. + type (str, optional): + Digital light processing (DLP) chip present in DMD. + usb_connection (bool, optional): + True if USB connection is ok. + ddc_fpga_temperature (float, optional): + Temperature of the DDC FPGA (IC4) at DMD connection. Units in °C. + apps_fpga_temperature (float, optional): + Temperature of the Applications FPGA (IC3) at DMD connection. Units + in °C. + pcb_temperature (float, optional): + Internal temperature of the temperature sensor IC (IC2) at DMD + connection. Units in °C. + display_height (int, optional): + DMD display height in pixels. + display_width (int, optional): + DMD display width in pixels. + patterns (int, optional): + Number of patterns uploaded to DMD. + unused_memory (int, optional): + Memory available after sending patterns to DMD. + bitplanes (int, optional): + Bit depth of the patterns to be displayed. Values supported from 1 + to 8. + DMD (InitVar[ALP4.ALP4], optional): + Initialization DMD object. Can be used to automatically fill most of + the DMDParameters' attributes. Unnecessary if reconstructing object + from JSON file. Defaut is None. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'DMD parameters'. + """ + + add_illumination_time_us: int + initial_memory: int + + dark_phase_time_us: Optional[int] = None + illumination_time_us: Optional[int] = None + picture_time_us: Optional[int] = None + synch_pulse_width_us: Optional[int] = None + synch_pulse_delay: Optional[int] = None + + device_number: Optional[int] = None + ALP_version: Optional[int] = None + id: Optional[int] = None + + synch_polarity: Optional[str] = None + trigger_edge: Optional[str] = None + + # synch_polarity_OUT1: Optional[str] = None + # synch_period_OUT1: Optional[str] = None + # synch_gate_OUT1: Optional[str] = None + + type: Optional[str] = None + usb_connection: Optional[bool] = None + + ddc_fpga_temperature: Optional[float] = None + apps_fpga_temperature: Optional[float] = None + pcb_temperature: Optional[float] = None + + display_height: Optional[int] = None + display_width: Optional[int] = None + + patterns: Optional[int] = None + patterns_wp: Optional[int] = None + unused_memory: Optional[int] = None + bitplanes: Optional[int] = None + + DMD: InitVar[ALP4.ALP4] = None + + class_description: str = 'DMD parameters' + + + def __post_init__(self, DMD: Optional[ALP4.ALP4] = None): + """ Post initialization of attributes. + + Receives a DMD object and directly asks it for its configurations and + status, then sets the majority of SpectrometerParameters's attributes. + During reconstruction from JSON, DMD is set to None and the function + does nothing, letting initialization for the standard __init__ function. + + Args: + DMD (ALP4.ALP4, optional): + Connected DMD. Defaults to None. + """ + if DMD == None: + pass + + else: + self.device_number = DMD.DevInquire(ALP4.ALP_DEVICE_NUMBER) + self.ALP_version = DMD.DevInquire(ALP4.ALP_VERSION) + self.id = DMD.ALP_ID.value + + polarity = DMD.DevInquire(ALP4.ALP_SYNCH_POLARITY) + if polarity == 2006: + self.synch_polarity = 'High' + elif polarity == 2007: + self.synch_polarity = 'Low' + + edge = DMD.DevInquire(ALP4.ALP_TRIGGER_EDGE) + if edge == 2008: + self.trigger_edge = 'Falling' + elif edge == 2009: + self.trigger_edge = 'Rising' + + # synch_polarity_OUT1 = + + self.type = DMDTypes(DMD.DevInquire(ALP4.ALP_DEV_DMDTYPE)) + + if DMD.DevInquire(ALP4.ALP_USB_CONNECTION) == 0: + self.usb_connection = True + else: + self.usb_connection = False + + # Temperatures converted to °C + self.ddc_fpga_temperature = DMD.DevInquire( + ALP4.ALP_DDC_FPGA_TEMPERATURE)/256 + self.apps_fpga_temperature = DMD.DevInquire( + ALP4.ALP_APPS_FPGA_TEMPERATURE)/256 + self.pcb_temperature = DMD.DevInquire( + ALP4.ALP_PCB_TEMPERATURE)/256 + + self.display_width = DMD.nSizeX + self.display_height = DMD.nSizeY + + + def update_memory(self, unused_memory: int): + + self.unused_memory = unused_memory + self.patterns = self.initial_memory - unused_memory + + + def update_sequence_parameters(self, add_illumination_time, + DMD: Optional[ALP4.ALP4] = None): + + self.bitplanes = DMD.SeqInquire(ALP4.ALP_BITPLANES) + self.illumination_time_us = DMD.SeqInquire(ALP4.ALP_ILLUMINATE_TIME) + self.picture_time_us = DMD.SeqInquire(ALP4.ALP_PICTURE_TIME) + self.dark_phase_time_us = self.picture_time_us - self.illumination_time_us + self.synch_pulse_width_us = DMD.SeqInquire(ALP4.ALP_SYNCH_PULSEWIDTH) + self.synch_pulse_delay = DMD.SeqInquire(ALP4.ALP_SYNCH_DELAY) + self.add_illumination_time_us = add_illumination_time + + + + + +def read_metadata(file_path: str) -> Tuple[MetaData, + AcquisitionParameters, + SpectrometerParameters, + DMDParameters]: + """Reads metadata of a previous acquisition from JSON file. + + Args: + file_path (str): + Name of JSON file containing all metadata. + + Returns: + Tuple[MetaData, AcquisitionParameters, SpectrometerParameters, + DMDParameters]: + saved_metadata (MetaData): + Metadata object read from JSON. + saved_acquisition_params(AcquisitionParameters): + AcquisitionParameters object read from JSON. + saved_spectrometer_params(SpectrometerParameters): + SpectrometerParameters object read from JSON. + saved_dmd_params(DMDParameters): + DMDParameters object read from JSON. + """ + + file = open(file_path,'r') + data = json.load(file) + file.close() + + for object in data: + if object['class_description'] == 'Metadata': + saved_metadata = MetaData.from_dict(object) + if object['class_description'] == 'Acquisition parameters': + saved_acquisition_params = AcquisitionParameters.from_dict(object) + saved_acquisition_params.undo_readable_pattern_order() + if object['class_description'] == 'Spectrometer parameters': + saved_spectrometer_params = SpectrometerParameters.from_dict(object) + if object['class_description'] == 'DMD parameters': + saved_dmd_params = DMDParameters.from_dict(object) + + return (saved_metadata, saved_acquisition_params, + saved_spectrometer_params, saved_dmd_params) + +def read_metadata_2arms(file_path: str) -> Tuple[MetaData, + AcquisitionParameters, + SpectrometerParameters, + DMDParameters, + CAM]: + """Reads metadata of a previous acquisition from JSON file. + + Args: + file_path (str): + Name of JSON file containing all metadata. + + Returns: + Tuple[MetaData, AcquisitionParameters, SpectrometerParameters, + DMDParameters]: + saved_metadata (MetaData): + Metadata object read from JSON. + saved_acquisition_params(AcquisitionParameters): + AcquisitionParameters object read from JSON. + saved_spectrometer_params(SpectrometerParameters): + SpectrometerParameters object read from JSON. + saved_dmd_params(DMDParameters): + DMDParameters object read from JSON. + """ + + file = open(file_path,'r') + data = json.load(file) + file.close() + + for object in data: + if object['class_description'] == 'Metadata': + saved_metadata = MetaData.from_dict(object) + if object['class_description'] == 'Acquisition parameters': + saved_acquisition_params = AcquisitionParameters.from_dict(object) + saved_acquisition_params.undo_readable_pattern_order() + if object['class_description'] == 'Spectrometer parameters': + saved_spectrometer_params = SpectrometerParameters.from_dict(object) + if object['class_description'] == 'DMD parameters': + saved_dmd_params = DMDParameters.from_dict(object) + if object['class_description'] == 'IDS camera configuration': + saved_cam_params = CAM.from_dict(object) + saved_cam_params.undo_readable_class_CAM() + + + return (saved_metadata, saved_acquisition_params, + saved_spectrometer_params, saved_dmd_params, saved_cam_params) + +def save_metadata(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + acquisition_parameters: AcquisitionParameters) -> None: + """Saves metadata to JSON file. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + Class containing DMD configurations and status. + spectrometer_params (SpectrometerParameters): + Object containing spectrometer configurations. + acquisition_parameters (AcquisitionParameters): + Object containing acquisition specifications and timing results. + """ + + path = Path(metadata.output_directory) + with open( + path / f'{metadata.experiment_name}_metadata.json', + 'w', encoding='utf8') as output: + + output_params = [ + metadata.to_dict(), + DMD_params.to_dict(), + spectrometer_params.to_dict(), + AcquisitionParameters.readable_pattern_order( + acquisition_parameters.to_dict())] + + json.dump(output_params,output,ensure_ascii=False,indent=4) + +def save_metadata_2arms(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + camPar : CAM, + acquisition_parameters: AcquisitionParameters) -> None: + """Saves metadata to JSON file. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + Class containing DMD configurations and status. + spectrometer_params (SpectrometerParameters): + Object containing spectrometer configurations. + acquisition_parameters (AcquisitionParameters): + Object containing acquisition specifications and timing results. + """ + + path = Path(metadata.output_directory) + with open( + path / f'{metadata.experiment_name}_metadata.json', + 'w', encoding='utf8') as output: + + output_params = [ + metadata.to_dict(), + DMD_params.to_dict(), + spectrometer_params.to_dict(), + CAM.readable_class_CAM(camPar.to_dict()), + AcquisitionParameters.readable_pattern_order(acquisition_parameters.to_dict())] + + json.dump(output_params, output, ensure_ascii=False, indent=4)#, default=convert) + + # with open(path / f'{metadata.experiment_name}_metadata_cam.pkl', 'wb') as f: + # pickle.dump(camPar.__dict__, f) + +@dataclass_json +@dataclass +class func_path: + def __init__(self, data_folder_name, data_name, ask_overwrite=False): + if not os.path.exists('../data/' + data_folder_name): + os.makedirs('../data/' + data_folder_name) + + if not os.path.exists('../data/' + data_folder_name + '/' + data_name): + os.makedirs('../data/' + data_folder_name + '/' + data_name) + aborted = False + elif ask_overwrite == True: + res = input('Acquisition already exists, overwrite it ?[y/n]') + if res == 'n': + aborted = True + else: + aborted = False + else: + aborted = True + + self.aborted = aborted + self.subfolder_path = '../data/' + data_folder_name + '/' + data_name + self.overview_path = self.subfolder_path + '/overview' + if not os.path.exists(self.overview_path): + os.makedirs(self.overview_path) + + self.data_name = data_name + self.data_path = self.subfolder_path + '/' + data_name + self.had_reco_path = self.data_path + '_had_reco.npz' + self.fig_had_reco_path = self.overview_path + '/' + data_name + self.pathIDSsnapshot = Path(self.data_path + '_IDScam_before_acq.npy') + self.pathIDSsnapshot_overview = self.overview_path + '/' + data_name + '_IDScam_before_acq.png' + self.nn_reco_path = self.data_path + '_nn_reco.npz' + self.fig_nn_reco_path = self.overview_path + '/' + data_name + + diff --git a/spas/reconstruction_SPC1D.py b/spas/reconstruction_SPC1D.py new file mode 100644 index 0000000..291649e --- /dev/null +++ b/spas/reconstruction_SPC1D.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +__author__ = 'Guilherme Beneti Martins' + +import numpy as np +from spas.metadata import AcquisitionParameters + +def reconstruction_hadamard(acquisition_parameters: AcquisitionParameters, + mode: str, + Q: np.ndarray, + M: np.ndarray, + N: int = 64) -> np.ndarray: + """Reconstruct an image acquired with Hadamard patterns. + + Args: + acquisition_parameters (AcquisitionParameters): + Object containing acquisition specifications + mode (str): + Select if reconstruction is based on MATLAB, fht or Walsh generated + patterns. + Q (np.ndarray): + Acquisition matrix used to generate Hadamard patterns. + M (np.ndarray): + Spectral data matrix containing acquired spectra. + N (int, optional): + Reconstructed image dimension. Defaults to 64. + + Returns: + [np.ndarray]: + Reconstructed matrix of size NxN pixels. + """ + + patterns = acquisition_parameters.patterns + + if mode == 'matlab': + ind_opt = patterns[1::2] + if mode == 'fht' or mode == 'walsh': + ind_opt = patterns[0::2] + + ind_opt = np.array(ind_opt)/2 + + if mode == 'matlab': + ind_opt = ind_opt - 1 + + ind_opt = ind_opt.astype('int') + M_breve = M[0::2,:] - M[1::2,:] + M_Had = np.zeros((N*N, M.shape[1])) + M_Had[ind_opt,:] = M_breve + + f = np.matmul(Q,M_Had) # Q.T = Q + frames = np.reshape(f,(N,N,M.shape[1])) + frames /= N*N + + mask_index = acquisition_parameters.mask_index + if len(mask_index) > 0: + x_mask_coord = acquisition_parameters.x_mask_coord + y_mask_coord = acquisition_parameters.y_mask_coord + x_mask_length = x_mask_coord[1] - x_mask_coord[0] + y_mask_length = y_mask_coord[1] - y_mask_coord[0] + + GTnew_vec = np.zeros((x_mask_length*y_mask_length, frames.shape[2])) + GT_vec = frames.reshape(-1, frames.shape[-1]) + + GTnew_vec[mask_index,:] = GT_vec[:len(mask_index),:] + frames = np.reshape(GTnew_vec, (y_mask_length, x_mask_length, frames.shape[2])) + + return frames + + +def reconstruction_raster(M: np.ndarray, N: int = 64) -> np.ndarray: + """Reconstruct an image obtained via Raster scan. + + Args: + M (np.ndarray): + Spectral data matrix containing acquired spectra. + N (int, optional): + Reconstructed image dimension. Defaults to 64. + + Returns: + np.ndarray: + Reconstructed matrix of size NxN pixels. + """ + return np.reshape(M,(N,N,M.shape[1])) \ No newline at end of file diff --git a/spas/reconstruction_SPC2D.py b/spas/reconstruction_SPC2D.py new file mode 100644 index 0000000..291649e --- /dev/null +++ b/spas/reconstruction_SPC2D.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +__author__ = 'Guilherme Beneti Martins' + +import numpy as np +from spas.metadata import AcquisitionParameters + +def reconstruction_hadamard(acquisition_parameters: AcquisitionParameters, + mode: str, + Q: np.ndarray, + M: np.ndarray, + N: int = 64) -> np.ndarray: + """Reconstruct an image acquired with Hadamard patterns. + + Args: + acquisition_parameters (AcquisitionParameters): + Object containing acquisition specifications + mode (str): + Select if reconstruction is based on MATLAB, fht or Walsh generated + patterns. + Q (np.ndarray): + Acquisition matrix used to generate Hadamard patterns. + M (np.ndarray): + Spectral data matrix containing acquired spectra. + N (int, optional): + Reconstructed image dimension. Defaults to 64. + + Returns: + [np.ndarray]: + Reconstructed matrix of size NxN pixels. + """ + + patterns = acquisition_parameters.patterns + + if mode == 'matlab': + ind_opt = patterns[1::2] + if mode == 'fht' or mode == 'walsh': + ind_opt = patterns[0::2] + + ind_opt = np.array(ind_opt)/2 + + if mode == 'matlab': + ind_opt = ind_opt - 1 + + ind_opt = ind_opt.astype('int') + M_breve = M[0::2,:] - M[1::2,:] + M_Had = np.zeros((N*N, M.shape[1])) + M_Had[ind_opt,:] = M_breve + + f = np.matmul(Q,M_Had) # Q.T = Q + frames = np.reshape(f,(N,N,M.shape[1])) + frames /= N*N + + mask_index = acquisition_parameters.mask_index + if len(mask_index) > 0: + x_mask_coord = acquisition_parameters.x_mask_coord + y_mask_coord = acquisition_parameters.y_mask_coord + x_mask_length = x_mask_coord[1] - x_mask_coord[0] + y_mask_length = y_mask_coord[1] - y_mask_coord[0] + + GTnew_vec = np.zeros((x_mask_length*y_mask_length, frames.shape[2])) + GT_vec = frames.reshape(-1, frames.shape[-1]) + + GTnew_vec[mask_index,:] = GT_vec[:len(mask_index),:] + frames = np.reshape(GTnew_vec, (y_mask_length, x_mask_length, frames.shape[2])) + + return frames + + +def reconstruction_raster(M: np.ndarray, N: int = 64) -> np.ndarray: + """Reconstruct an image obtained via Raster scan. + + Args: + M (np.ndarray): + Spectral data matrix containing acquired spectra. + N (int, optional): + Reconstructed image dimension. Defaults to 64. + + Returns: + np.ndarray: + Reconstructed matrix of size NxN pixels. + """ + return np.reshape(M,(N,N,M.shape[1])) \ No newline at end of file From 49731f647c02365800ad5b7e66d5bd47b204bb38 Mon Sep 17 00:00:00 2001 From: Varvara Chiliaeva Date: Tue, 18 Feb 2025 17:03:42 +0100 Subject: [PATCH 3/5] deleted unnecessary copies --- spas/acquisition_SP1D.py | 2878 ---------------------------------- spas/metadata_SPC1D.py | 1118 ------------- spas/reconstruction_SPC1D.py | 82 - 3 files changed, 4078 deletions(-) delete mode 100644 spas/acquisition_SP1D.py delete mode 100644 spas/metadata_SPC1D.py delete mode 100644 spas/reconstruction_SPC1D.py diff --git a/spas/acquisition_SP1D.py b/spas/acquisition_SP1D.py deleted file mode 100644 index 1c39899..0000000 --- a/spas/acquisition_SP1D.py +++ /dev/null @@ -1,2878 +0,0 @@ -# -*- coding: utf-8 -*- -__author__ = 'Guilherme Beneti Martins' - -"""Acquisition utility functions. - - Acquisition module is a generic module that call function in different setup (SPC2D_1arm, SPC2D_2arms, SCP1D and SPIM) - -""" - -import warnings -from time import sleep, perf_counter_ns -from typing import NamedTuple, Tuple, List, Optional -from collections import namedtuple -from pathlib import Path -from multiprocessing import Process, Queue -import shutil -import math - -import numpy as np -from PIL import Image -##### DLL for the DMD -try: - from ALP4 import ALP4, ALP_FIRSTFRAME, ALP_LASTFRAME - from ALP4 import ALP_AVAIL_MEMORY, ALP_DEV_DYN_SYNCH_OUT1_GATE, tAlpDynSynchOutGate - # print('ALP4 is ok in Acquisition file') -except: - class ALP4: - pass -##### DLL for the spectrometer Avantes -try: - from msl.equipment import EquipmentRecord, ConnectionRecord, Backend - from msl.equipment.resources.avantes import MeasureCallback, Avantes -except: - pass - -from tqdm import tqdm -from spas.metadata import DMDParameters, MetaData, AcquisitionParameters -from spas.metadata import SpectrometerParameters, save_metadata, CAM, save_metadata_2arms -from spas.reconstruction_nn import reconstruct_process, plot_recon, ReconstructionParameters - -# DLL for the IDS CAMERA -try: - from pyueye import ueye, ueye_tools -except: - print('ueye DLL not installed') - -from matplotlib import pyplot as plt -from IPython import get_ipython -import ctypes as ct -import logging -import time -import threading - - -def _init_spectrometer() -> Avantes: - """Initialize and connect to an Avantes Spectrometer. - - Returns: - Avantes: Avantes spectrometer. - """ - - dll_path = Path(__file__).parent.parent.joinpath( - 'lib/avaspec3/avaspecx64.dll') - - record = EquipmentRecord( - manufacturer='Avantes', - model='AvaSpec-UCLS2048BCL-EVO-RS', # update for your device - serial='2011126U1', # update for your device - connection=ConnectionRecord( - address=f'SDK::{dll_path}', - backend=Backend.MSL)) - - # Initialize Avantes SDK and establish the connection to the spectrometer - ava = record.connect() - print('Spectrometer connected') - - return ava - - -def _init_DMD(dmd_lib_version: str = '4.2') -> Tuple[ALP4, int]: - """Initialize a DMD and clean its allocated memory from a previous use. - - Args: - dmd_lib_version [str]: the version of the DMD library - - Returns: - Tuple[ALP4, int]: Tuple containing initialized DMD object and DMD - initial available memory. - """ - - # Initializing DMD - stop_init = False - if dmd_lib_version == '4.1': - print('dmd lib version = ' + dmd_lib_version + ' not installed, please, install it at the location : "openspyrit/spas/alpV41"') - stop_init = True - elif dmd_lib_version == '4.2': - dll_path = Path(__file__).parent.parent.joinpath('lib/alpV42').__str__() - DMD = ALP4(version='4.2',libDir=dll_path) - elif dmd_lib_version == '4.3': - dll_path = Path(__file__).parent.parent.joinpath('lib/alpV43').__str__() - DMD = ALP4(version='4.3',libDir=dll_path) - else: - print('unknown version of dmd library') - stop_init = True - - if stop_init == False: - DMD.Initialize(DeviceNum=None) - - #print(f'DMD initial available memory: {DMD.DevInquire(ALP_AVAIL_MEMORY)}') - print('DMD connected') - - return DMD, DMD.DevInquire(ALP_AVAIL_MEMORY) - else: - print('DMD initialisation aborted') - - -def init(dmd_lib_version: str = '4.2') -> Tuple[Avantes, ALP4, int]: - """Call functions to initialize spectrometer and DMD. - - Args: - dmd_lib_version [str]: the version of the DMD library - - Returns: - Tuple[Avantes, ALP4, int]: Tuple containing equipments and DMD initial - available memory: - Avantes: - Connected spectrometer object. - ALP4: - Connected DMD object. - DMD_initial_memory (int): - Initial memory available in DMD after initialization. - """ - - DMD, DMD_initial_memory = _init_DMD(dmd_lib_version) - return _init_spectrometer(), DMD, DMD_initial_memory - - -def init_2arms(dmd_lib_version: str = '4.2') -> Tuple[Avantes, ALP4, int]: - """Call functions to initialize spectrometer and DMD. - - Args: - dmd_lib_version [str]: the version of the DMD library - - Returns: - Tuple[Avantes, ALP4, int]: Tuple containing equipments and DMD initial - available memory: - Avantes: - Connected spectrometer object. - ALP4: - Connected DMD object. - DMD_initial_memory (int): - Initial memory available in DMD after initialization. - """ - - DMD, DMD_initial_memory = _init_DMD(dmd_lib_version) - camPar = _init_CAM() - return _init_spectrometer(), DMD, DMD_initial_memory, camPar - - -def _calculate_timings(integration_time: float = 1, - integration_delay: int = 0, - add_illumination_time: int = 300, - synch_pulse_delay: int = 0, - dark_phase_time: int = 44, - ) -> Tuple[int, int, int]: - """Calculate spectrometer and DMD dependant timings. - - Args: - integration_time (float): - Spectrometer exposure time during one scan in miliseconds. - Default is 1 ms. - integration_delay (int): - Parameter used to start the integration time not immediately after - the measurement request (or on an external hardware trigger), but - after a specified delay. Unit is based on internal FPGA clock cycle. - Default is 0 us. - add_illumination_time (int): - Extra time in microseconds to account for the spectrometer's - "dead time". Default is 365 us. - synch_pulse_delay (int): - Time in microseconds between start of the frame synch output pulse - and the start of the pattern display (in master mode). Default is - 0 us. - dark_phase_time (int): - Time in microseconds taken by the DMD mirrors to completely tilt. - Minimum time for XGA type DMD is 44 us. Default is 44 us. - - Returns: - [Tuple]: DMD timings which depend on spectrometer's parameters. - synch_pulse_width: Duration of DMD's frame synch output pulse. Units - in microseconds. - illumination_time: Duration of the display of one pattern in a DMD - sequence. Units in microseconds. - picture_time: Time between the start of two consecutive pictures - (i.e. this parameter defines the image display rate). Units in - microseconds. - """ - - illumination_time = (integration_delay/1000 + integration_time*1000 + - add_illumination_time) - picture_time = illumination_time + dark_phase_time - synch_pulse_width = round(illumination_time/2 + synch_pulse_delay) - illumination_time = round(illumination_time) - picture_time = round(picture_time) - - return synch_pulse_width, illumination_time, picture_time - - -def _setup_spectrometer(ava: Avantes, - integration_time: float, - integration_delay: int, - start_pixel: int, - stop_pixel: int, - ) -> Tuple[SpectrometerParameters, List[float]]: - """Sets configurations in the spectrometer. - - Set all necessary configurations in the spectrometer preparing it for a - measurement. Creates SpectrometerData containing its metadata. Gets the - correct wavelengths depending on the selected pixels to be used. - - Args: - ava (Avantes): - Avantes spectrometer. - integration_time (float): - Spectrometer exposure time during one scan in miliseconds. - integration_delay (int): - Parameter used to start the integration time not immediately after - the measurement request (or on an external hardware trigger), but - after a specified delay. Unit is based on internal FPGA clock cycle. - start_pixel (int): - Initial pixel data received from spectrometer. - stop_pixel (int, optional): - Last pixel data received from spectrometer. If None, then its value - will be determined from the amount of available pixels in the - spectrometer. - Returns: - Tuple[SpectrometerParameters, List[float]]: Metadata and wavelengths. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata. - wavelengths (List): - List of float corresponding to the wavelengths associated with - spectrometer's start and stop pixels. - """ - - spectrometer_detector = ava.SensType( - ava.get_parameter().m_Detector.m_SensorType).name - - # Get the number of pixels that the spectrometer has - initial_available_pixels = ava.get_num_pixels() - - # print(f'\nThe spectrometer has {initial_available_pixels} pixels') - - # Enable the 16-bit AD converter for High-Resolution - ava.use_high_res_adc(True) - - # Creating configuration block - measconfig = ava.MeasConfigType() - - measconfig.m_StartPixel = start_pixel - - if stop_pixel is None: - measconfig.m_StopPixel = initial_available_pixels - 1 - else: - measconfig.m_StopPixel = stop_pixel - - measconfig.m_IntegrationTime = integration_time - measconfig.m_IntegrationDelay = integration_delay - measconfig.m_NrAverages = 1 - - dark_correction = ava.DarkCorrectionType() - dark_correction.m_Enable = 0 - dark_correction.m_ForgetPercentage = 100 - measconfig.m_CorDynDark = dark_correction - - smoothing = ava.SmoothingType() - smoothing.m_SmoothPix = 0 - smoothing.m_SmoothModel = 0 - measconfig.m_Smoothing = smoothing - - measconfig.m_SaturationDetection = 1 - - trigger = ava.TriggerType() - trigger.m_Mode = 2 - trigger.m_Source = 0 - trigger.m_SourceType = 0 - measconfig.m_Trigger = trigger - - control_settings = ava.ControlSettingsType() - control_settings.m_StrobeControl = 0 - control_settings.m_LaserDelay = 0 - control_settings.m_LaserWidth = 0 - control_settings.LaserWaveLength = 0.00 - control_settings.m_StoreToRam = 0 - measconfig.m_Control = control_settings - - ava.prepare_measure(measconfig) - - spectrometer_params = SpectrometerParameters( - high_resolution=True, - initial_available_pixels=initial_available_pixels, - detector=spectrometer_detector, - configs=measconfig, - version_info=ava.get_version_info()) - - # Get the wavelength corresponding to each pixel - wavelengths = ava.get_lambda()[ - spectrometer_params.start_pixel:spectrometer_params.stop_pixel+1] - - return spectrometer_params, np.asarray(wavelengths) - - -def _setup_DMD(DMD: ALP4, - add_illumination_time: int, - initial_memory: int - ) -> DMDParameters: - """Create DMD metadata. - - Creates basic DMD metadata, but leaves most of its fields empty to be set - later. Sets up the initial free memory present in the DMD. - This function's name is used to create cohesion between spectrometer and DMD - related functions. - - Args: - DMD (ALP4): - Connected DMD object. - add_illumination_time (int): - Extra time in microseconds to account for the spectrometer's - "dead time". - initial_memory (int): - Initial memory available in DMD after initialization. - - Returns: - DMDParameters: - DMD metadata object. - """ - - return DMDParameters( - add_illumination_time_us=add_illumination_time, - initial_memory=initial_memory, - DMD=DMD) - - -def _sequence_limits(DMD: ALP4, - pattern_compression: int, - sequence_lenght: int, - pos_neg: bool = True) -> int: - """Set sequence limits based on a sequence already uploaded to DMD. - - Args: - DMD (ALP4): - Connected DMD object. - pattern_compression (int): - Percentage of total available patterns to be present in an - acquisition sequence. - sequence_lenght (int): - Amount of patterns present in DMD memory. - pos_neg (bool): - Boolean indicating if sequence is formed by positive and negative - patterns. Default is True. - - Returns: - frames (int): - Amount of patterns to be used from a sequence based on the pattern - compression. - """ - - # Choosing beggining of the sequence - # DMD.SeqControl(ALP_BITNUM, 1) - DMD.SeqControl(ALP_FIRSTFRAME, 0) - - # Choosing the end of the sequence - if (round(pattern_compression * sequence_lenght) % 2 == 0) or not (pos_neg): - frames = round(pattern_compression * sequence_lenght) - else: - frames = round(pattern_compression * sequence_lenght) + 1 - - DMD.SeqControl(ALP_LASTFRAME, frames - 1) - - return frames - - -def _update_sequence(DMD: ALP4, - DMD_params: DMDParameters, - acquisition_params: AcquisitionParameters, - pattern_source: str, - pattern_prefix: str, - pattern_order: List[int], - bitplanes: int = 1): - """Send new complete pattern sequence to DMD. - - Args: - DMD (ALP4): - Connected DMD object. - DMD_params (DMDParameters): - DMD metadata object to be updated with pattern related data and with - memory available after patterns are sent to DMD. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. User must partially fill up - with pattern_compression, pattern_dimension_x, pattern_dimension_y, - zoom, x and y offest of patterns displayed on the DMD. - pattern_source (str): - Pattern source folder. - pattern_preffix (str): - Prefix used in pattern naming. - pattern_order (List[int]): - List of the pattern indices in a certain order for upload to DMD. - bitplanes (int, optional): - Pattern bitplanes. Defaults to 1. - """ - - import cv2 - - path_base = Path(pattern_source) - - seqId = DMD.SeqAlloc(nbImg=len(pattern_order), - bitDepth=bitplanes) - - zoom = acquisition_params.zoom - x_offset = acquisition_params.xw_offset - y_offset = acquisition_params.yh_offset - Np = acquisition_params.pattern_dimension_x - - dmd_height = DMD_params.display_height - dmd_width = DMD_params.display_width - len_im = int(dmd_height / zoom) - - t = perf_counter_ns() - - # for adaptative patterns into a ROI - apply_mask = False - mask_index = acquisition_params.mask_index - - if len(mask_index) > 0: - apply_mask = True - Npx = acquisition_params.pattern_dimension_x - Npy = acquisition_params.pattern_dimension_y - mask_element_nbr = len(mask_index) - x_mask_coord = acquisition_params.x_mask_coord - y_mask_coord = acquisition_params.y_mask_coord - x_mask_length = x_mask_coord[1] - x_mask_coord[0] - y_mask_length = y_mask_coord[1] - y_mask_coord[0] - - first_pass = True - for index,pattern_name in enumerate(tqdm(pattern_order, unit=' patterns', total=len(pattern_order))): - # read numpy patterns - path = path_base.joinpath(f'{pattern_prefix}_{pattern_name}.npy') - im = np.load(path) - - patterns = np.zeros((dmd_height, dmd_width), dtype=np.uint8) - - if apply_mask == True: # for adaptative patterns into a ROI - pat_mask_all = np.zeros(y_mask_length*x_mask_length) # initialize a vector of lenght = size of the cropped mask - pat_mask_all[mask_index] = im[:mask_element_nbr] #pat_re_vec[:mask_element_nbr] # put the pattern into the vector - pat_mask_all_mat = np.reshape(pat_mask_all, [y_mask_length, x_mask_length]) # reshape the vector into a matrix of the 2d cropped mask - # resize the matrix to the DMD size - pat_mask_all_mat_DMD = cv2.resize(pat_mask_all_mat, (int(dmd_height*x_mask_length/(Npx*zoom)), int(dmd_height*y_mask_length/(Npy*zoom))), interpolation = cv2.INTER_NEAREST) - - if first_pass == True: - first_pass = False - len_im3 = pat_mask_all_mat_DMD.shape - - patterns[y_offset:y_offset+len_im3[0], x_offset:x_offset+len_im3[1]] = pat_mask_all_mat_DMD - else: # send the entire square pattern without the mask - im_mat = np.reshape(im, [Np,Np]) - im_HD = cv2.resize(im_mat, (int(dmd_height/zoom), int(dmd_height/zoom)), interpolation = cv2.INTER_NEAREST) - - if first_pass == True: - len_im = im_HD.shape - first_pass = False - - patterns[y_offset:y_offset+len_im[0], x_offset:x_offset+len_im[1]] = im_HD - - # if pattern_name == 800: - # plt.figure() - # # plt.imshow(pat_c_re) - # # plt.imshow(pat_mask_all_mat) - # # plt.imshow(pat_mask_all_mat_DMD) - # plt.imshow(np.rot90(patterns,2)) - # plt.colorbar() - # plt.title('pattern n°' + str(pattern_name)) - - patterns = patterns.ravel() - - DMD.SeqPut( - imgData=patterns.copy(), - PicOffset=index, - PicLoad=1) - - print(f'\nTime for sending all patterns: ' - f'{(perf_counter_ns() - t)/1e+9} s') - - -def _setup_patterns(DMD: ALP4, - metadata: MetaData, - DMD_params: DMDParameters, - acquisition_params: AcquisitionParameters, - cov_path: str = None, - pattern_to_display: str = 'white', - loop: bool = False) -> None: - """Read and send patterns to DMD. - - Reads patterns from a file and sends a percentage of them to the DMD, - considering positve and negative Hadamard patterns, which should be even in - number. - Prints time taken to read all patterns and send the requested ones - to DMD. - Updates available memory in DMD metadata object (DMD_params). - - Args: - DMD (ALP4): - Connected DMD object. - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - DMD_params (DMDParameters): - DMD metadata object to be updated with pattern related data and with - memory available after patterns are sent to DMD. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. User must partially fill up - with pattern_compression, pattern_dimension_x, pattern_dimension_y, - zoom, x and y offest of patterns displayed on the DMD. - loop (bool): - is to projet in loop, one or few patterns continously (see AlpProjStartCont - in the doc for more detail). Default is False - """ - - file = np.load(Path(metadata.pattern_order_source)) - pattern_order = file['pattern_order'] - pos_neg = file['pos_neg'] - - if loop == True: - pos_neg = False - if pattern_to_display == 'white': - pattern_order = np.array(pattern_order[0:1], dtype=np.int16) - elif pattern_to_display == 'black': - pattern_order = np.array(pattern_order[1:2], dtype=np.int16) - elif pattern_to_display == 'gray': - index = int(np.where(pattern_order == 1953)[0]) - print(index) - pattern_order = np.array(pattern_order[index:index+1], dtype=np.int16) - - bitplanes = 1 - DMD_params.bitplanes = bitplanes - - if (DMD_params.initial_memory - DMD.DevInquire(ALP_AVAIL_MEMORY) == - len(pattern_order)) and loop == False: - print('Reusing patterns from previous acquisition') - acquisition_params.pattern_amount = _sequence_limits( - DMD, - acquisition_params.pattern_compression, - len(pattern_order), - pos_neg=pos_neg) - - else: - if (DMD.Seqs): - DMD.FreeSeq() - - _update_sequence(DMD, DMD_params, acquisition_params, metadata.pattern_source, metadata.pattern_prefix, - pattern_order, bitplanes) - print(f'DMD available memory after sequence allocation: ' - f'{DMD.DevInquire(ALP_AVAIL_MEMORY)}') - acquisition_params.pattern_amount = _sequence_limits( - DMD, - acquisition_params.pattern_compression, - len(pattern_order), - pos_neg=pos_neg) - - acquisition_params.patterns = ( - pattern_order[0:acquisition_params.pattern_amount]) - - # Confirm memory allocated in DMD - DMD_params.update_memory(DMD.DevInquire(ALP_AVAIL_MEMORY)) - - -def _setup_patterns_2arms(DMD: ALP4, - metadata: MetaData, - DMD_params: DMDParameters, - acquisition_params: AcquisitionParameters, - camPar: CAM, - cov_path: str = None) -> None: - """Read and send patterns to DMD. - - Reads patterns from a file and sends a percentage of them to the DMD, - considering positve and negative Hadamard patterns, which should be even in - number. - Prints time taken to read all patterns and send the requested ones - to DMD. - Updates available memory in DMD metadata object (DMD_params). - - Args: - DMD (ALP4): - Connected DMD object. - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - DMD_params (DMDParameters): - DMD metadata object to be updated with pattern related data and with - memory available after patterns are sent to DMD. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. User must partially fill up - with pattern_compression, pattern_dimension_x, pattern_dimension_y, - zoom, x and y offest of patterns displayed on the DMD. - camPar (CAM): - Metadata object of the IDS monochrome camera - cov_path (str): - Path to the covariance matrix used for reconstruction. - It must be a .npy (numpy) or .pt (pytorch) file. It is converted to - a torch tensor for reconstruction. - - """ - - file = np.load(Path(metadata.pattern_order_source)) - pattern_order = file['pattern_order'] - pattern_order = pattern_order.astype('int32') - - # copy the black pattern image (png) to the number = -1 - # black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.png' ) - black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.npy' ) - - if black_pattern_dest_path.is_file() == False: - # black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + - # str(camPar.black_pattern_num) + '.png' ) - black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + - str(camPar.black_pattern_num) + '.npy' ) - shutil.copyfile(black_pattern_orig_path, black_pattern_dest_path) - - - # add white patterns for the camera - if camPar.insert_patterns == 1: - inc = 0 - while True: - try: - pattern_order[inc] # except error from the end of array to stop the loop - if (inc % camPar.gate_period) == 0:#16) == 0: - pattern_order = np.insert(pattern_order, inc, -1) # double white pattern is required if integration time is shorter than 3.85 ms - if camPar.int_time_spect < 3.85: - pattern_order = np.insert(pattern_order, inc+1, -1) - if camPar.int_time_spect < 1.65: - pattern_order = np.insert(pattern_order, inc+2, -1) - if camPar.int_time_spect < 1: - pattern_order = np.insert(pattern_order, inc+1, -1) - if camPar.int_time_spect <= 0.6: - pattern_order = np.insert(pattern_order, inc+1, -1) - inc = inc + 1 - except: - # print('while loop finished') - break - - # if camPar.int_time_spect < 1.75: # add one pattern at the beginning of the sequence when the integration time of the spectrometer is shorter than 1.75 ms - # print('no interleaving') - # #pattern_order = np.insert(pattern_order, 0, -1) - # # pattern_order = np.insert(pattern_order, 0, -1) - if (len(pattern_order)%2) != 0: # Add one pattern at the end of the sequence if the pattern number is even - pattern_order = np.insert(pattern_order, len(pattern_order), -1) - print('pattern order is odd => a black image is automaticly insert, need to be deleted in the case for tuning the spectrometer') - - pos_neg = file['pos_neg'] - - bitplanes = 1 - DMD_params.bitplanes = bitplanes - - if (DMD_params.initial_memory - DMD.DevInquire(ALP_AVAIL_MEMORY) == - len(pattern_order)): - print('Reusing patterns from previous acquisition') - acquisition_params.pattern_amount = _sequence_limits( - DMD, - acquisition_params.pattern_compression, - len(pattern_order), - pos_neg=pos_neg) - - else: - if (DMD.Seqs): - DMD.FreeSeq() - - _update_sequence(DMD, DMD_params, acquisition_params, metadata.pattern_source, metadata.pattern_prefix, - pattern_order, bitplanes) - print(f'DMD available memory after sequence allocation: ' - f'{DMD.DevInquire(ALP_AVAIL_MEMORY)}') - acquisition_params.pattern_amount = _sequence_limits( - DMD, - acquisition_params.pattern_compression, - len(pattern_order), - pos_neg=pos_neg) - - acquisition_params.patterns = ( - pattern_order[0:acquisition_params.pattern_amount]) - - acquisition_params.patterns_wp = acquisition_params.patterns - - # Confirm memory allocated in DMD - DMD_params.update_memory(DMD.DevInquire(ALP_AVAIL_MEMORY)) - -def _setup_timings(DMD: ALP4, - DMD_params: DMDParameters, - picture_time: int, - illumination_time: int, - synch_pulse_delay: int, - synch_pulse_width: int, - trigger_in_delay: int, - add_illumination_time: int) -> None: - """Setup pattern sequence timings in DMD. - - Send previously user-defined plus calculated timings to DMD. - Updates DMD metadata with sequence and timing related data. - This function has no default values for timings and lets the burden of - setting them to the setup function. - - Args: - DMD (ALP4): - Connected DMD object. - DMD_params (DMDParameters): - DMD metadata object to be updated with pattern related data and with - memory available after patterns are sent to DMD. - picture_time (int): - Time between the start of two consecutive pictures (i.e. this - parameter defines the image display rate). Units in microseconds. - illumination_time (int): - Duration of the display of one pattern in a DMD sequence. - Units in microseconds. - synch_pulse_delay (int): - Time in microseconds between start of the frame synch output pulse - and the start of the pattern display (in master mode). - synch_pulse_width (int): - Duration of DMD's frame synch output pulse. Units in microseconds. - trigger_in_delay (int): - Time in microseconds between the incoming trigger edge and the start - of the pattern display on DMD (slave mode). - add_illumination_time (int): - Extra time in microseconds to account for the spectrometer's - "dead time". - """ - - DMD.SetTiming(illuminationTime=illumination_time, - pictureTime=picture_time, - synchDelay=synch_pulse_delay, - synchPulseWidth=synch_pulse_width, - triggerInDelay=trigger_in_delay) - - DMD_params.update_sequence_parameters(add_illumination_time, DMD=DMD) - - -def setup(spectrometer: Avantes, - DMD: ALP4, - DMD_initial_memory: int, - metadata: MetaData, - acquisition_params: AcquisitionParameters, - start_pixel: int = 0, - stop_pixel: Optional[int] = None, - integration_time: float = 1, - integration_delay: int = 0, - DMD_output_synch_pulse_delay: int = 0, - add_illumination_time: int = 356, - dark_phase_time: int = 44, - DMD_trigger_in_delay: int = 0, - pattern_to_display: str = 'white', - loop: bool = False - ) -> Tuple[SpectrometerParameters, DMDParameters]: - """Setup everything needed to start an acquisition. - - Sets all parameters for DMD, spectrometer, DMD patterns and DMD timings. - Must be called before every acquisition. - - Args: - spectrometer (Avantes): - Connected spectrometer (Avantes object). - DMD (ALP4): - Connected DMD. - DMD_initial_memory (int): - Initial memory available in DMD after initialization. - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. User must partially fill up - with pattern_compression, pattern_dimension_x, pattern_dimension_y. - start_pixel (int): - Initial pixel data received from spectrometer. Default is 0. - stop_pixel (int, optional): - Last pixel data received from spectrometer. Default is None if it - should be determined from the amount of available pixels in the - spectrometer. - integration_time (float): - Spectrometer exposure time during one scan in miliseconds. Default - is 1 ms. - integration_delay (int): - Parameter used to start the integration time not immediately after - the measurement request (or on an external hardware trigger), but - after a specified delay. Unit is based on internal FPGA clock cycle. - Default is 0 us. - DMD_output_synch_pulse_delay (int): - Time in microseconds between start of the frame synch output pulse - and the start of the pattern display (in master mode). Default is - 0 us. - add_illumination_time (int): - Extra time in microseconds to account for the spectrometer's - "dead time". Default is 365 us. - dark_phase_time (int): - Time in microseconds taken by the DMD mirrors to completely tilt. - Minimum time for XGA type DMD is 44 us. Default is 44 us. - DMD_trigger_in_delay (int): - Time in microseconds between the incoming trigger edge and the start - of the pattern display on DMD (slave mode). Default is 0 us. - pattern_to_display (string): - display one pattern on the DMD to tune the spectrometer. Default is white - pattern - loop (bool): - is to projet in loop, one or few patterns continuously (see AlpProjStartCont - in the doc for more detail). Default is False - Raises: - ValueError: Sum of dark phase and additional illumination time is lower - than 400 us. - - Returns: - Tuple[SpectrometerParameters, DMDParameters, List]: Tuple containing DMD - and spectrometer relate metadata, as well as wavelengths. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - """ - - if loop == False: - path = Path(metadata.output_directory) - if not path.exists(): - path.mkdir() - - if dark_phase_time + add_illumination_time < 350: - raise ValueError(f'Sum of dark phase and additional illumination time ' - f'is {dark_phase_time + add_illumination_time}.' - f' Must be greater than 350 µs.') - - elif dark_phase_time + add_illumination_time < 400: - warnings.warn(f'Sum of dark phase and additional illumination time ' - f'is {dark_phase_time + add_illumination_time}.' - f' It is recomended to choose at least 400 µs.') - - synch_pulse_width, illumination_time, picture_time = _calculate_timings( - integration_time, - integration_delay, - add_illumination_time, - DMD_output_synch_pulse_delay, - dark_phase_time) - - spectrometer_params, wavelenghts = _setup_spectrometer( - spectrometer, - integration_time, - integration_delay, - start_pixel, - stop_pixel) - - acquisition_params.wavelengths = np.asarray(wavelenghts, dtype=np.float64) - - DMD_params = _setup_DMD(DMD, add_illumination_time, DMD_initial_memory) - - _setup_patterns(DMD=DMD, metadata=metadata, DMD_params=DMD_params, - acquisition_params=acquisition_params, loop=loop, - pattern_to_display=pattern_to_display) - - _setup_timings(DMD, DMD_params, picture_time, illumination_time, - DMD_output_synch_pulse_delay, synch_pulse_width, - DMD_trigger_in_delay, add_illumination_time) - - return spectrometer_params, DMD_params - - -def change_patterns(DMD: ALP4, - acquisition_params: AcquisitionParameters, - zoom: int = 1, - xw_offset: int = 0, - yh_offset: int = 0, - force_change: bool = False - ): - """ - Delete patterns in the memory of the DMD in the case where the zoom or (x,y) offset change - - DMD (ALP4): - Connected DMD. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. User must partially fill up - with pattern_compression, pattern_dimension_x, pattern_dimension_y, - zoom, x and y offest of patterns displayed on the DMD. - zoom (int): - digital zoom. Deafult is x1. - xw_offset (int): - offset int he width direction of the patterns for zoom > 1. - Default is 0. - yh_offset (int): - offset int he height direction of the patterns for zoom > 1. - Default is 0. - force_change (bool): - to force the changement of the pattern sequence. Default is False. - """ - - if acquisition_params.zoom != zoom or acquisition_params.xw_offset != xw_offset or acquisition_params.yh_offset != yh_offset or force_change == True: - if (DMD.Seqs): - DMD.FreeSeq() - - -def setup_2arms(spectrometer: Avantes, - DMD: ALP4, - camPar: CAM, - DMD_initial_memory: int, - metadata: MetaData, - acquisition_params: AcquisitionParameters, - start_pixel: int = 0, - stop_pixel: Optional[int] = None, - integration_time: float = 1, - integration_delay: int = 0, - DMD_output_synch_pulse_delay: int = 0, - add_illumination_time: int = 356, - dark_phase_time: int = 44, - DMD_trigger_in_delay: int = 0 - ) -> Tuple[SpectrometerParameters, DMDParameters]: - """Setup everything needed to start an acquisition. - - Sets all parameters for DMD, spectrometer, DMD patterns and DMD timings. - Must be called before every acquisition. - - Args: - spectrometer (Avantes): - Connected spectrometer (Avantes object). - DMD (ALP4): - Connected DMD. - camPar (CAM): - Metadata object of the IDS monochrome camera - DMD_initial_memory (int): - Initial memory available in DMD after initialization. - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. User must partially fill up - with pattern_compression, pattern_dimension_x, pattern_dimension_y, - zoom, x and y offest of patterns displayed on the DMD. - start_pixel (int): - Initial pixel data received from spectrometer. Default is 0. - stop_pixel (int, optional): - Last pixel data received from spectrometer. Default is None if it - should be determined from the amount of available pixels in the - spectrometer. - integration_time (float): - Spectrometer exposure time during one scan in miliseconds. Default - is 1 ms. - integration_delay (int): - Parameter used to start the integration time not immediately after - the measurement request (or on an external hardware trigger), but - after a specified delay. Unit is based on internal FPGA clock cycle. - Default is 0 us. - DMD_output_synch_pulse_delay (int): - Time in microseconds between start of the frame synch output pulse - and the start of the pattern display (in master mode). Default is - 0 us. - add_illumination_time (int): - Extra time in microseconds to account for the spectrometer's - "dead time". Default is 365 us. - dark_phase_time (int): - Time in microseconds taken by the DMD mirrors to completely tilt. - Minimum time for XGA type DMD is 44 us. Default is 44 us. - DMD_trigger_in_delay (int): - Time in microseconds between the incoming trigger edge and the start - of the pattern display on DMD (slave mode). Default is 0 us. - - Raises: - ValueError: Sum of dark phase and additional illumination time is lower - than 400 us. - - Returns: - Tuple[SpectrometerParameters, DMDParameters, List]: Tuple containing DMD - and spectrometer relate metadata, as well as wavelengths. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - """ - - path = Path(metadata.output_directory) - if not path.exists(): - path.mkdir() - - if dark_phase_time + add_illumination_time < 350: - raise ValueError(f'Sum of dark phase and additional illumination time ' - f'is {dark_phase_time + add_illumination_time}.' - f' Must be greater than 350 µs.') - - elif dark_phase_time + add_illumination_time < 400: - warnings.warn(f'Sum of dark phase and additional illumination time ' - f'is {dark_phase_time + add_illumination_time}.' - f' It is recomended to choose at least 400 µs.') - - synch_pulse_width, illumination_time, picture_time = _calculate_timings( - integration_time, - integration_delay, - add_illumination_time, - DMD_output_synch_pulse_delay, - dark_phase_time) - - spectrometer_params, wavelenghts = _setup_spectrometer( - spectrometer, - integration_time, - integration_delay, - start_pixel, - stop_pixel) - - if camPar.gate_period > 16: - gate_period = 16 - print('Warning, gate period is ' + str(camPar.gate_period) + ' > than the max: 16.') - print('Try to increase the FPS of the camera, or the integration time of the spectrometer.') - print('Check the Pixel clock which must be = 474 MHz') - print('Otherwise some frames will be lost.') - elif camPar.gate_period <1: - print('Warning, gate period is ' + str(camPar.gate_period) + ' < than the min: 1.') - gate_period = 1 - else: - gate_period = camPar.gate_period - - camPar.gate_period = gate_period - Gate = tAlpDynSynchOutGate() - Gate.byref[0] = ct.c_ubyte(gate_period) # Period [1 to 16] (it is a multiple of the trig period which go to the spectro) - Gate.byref[1] = ct.c_ubyte(1) # Polarity => 0: active pulse is low, 1: high - Gate.byref[2] = ct.c_ubyte(1) # Gate1 ok to send TTL - Gate.byref[3] = ct.c_ubyte(0) # Gate2 do not send TTL - Gate.byref[4] = ct.c_ubyte(0) # Gate3 do not send TTL - DMD.DevControlEx(ALP_DEV_DYN_SYNCH_OUT1_GATE, Gate) - camPar.gate_period = gate_period - camPar.int_time_spect = integration_time - - acquisition_params.wavelengths = np.asarray(wavelenghts, dtype=np.float64) - - DMD_params = _setup_DMD(DMD, add_illumination_time, DMD_initial_memory) - - _setup_patterns_2arms(DMD=DMD, metadata=metadata, DMD_params=DMD_params, - acquisition_params=acquisition_params, camPar=camPar) - - _setup_timings(DMD, DMD_params, picture_time, illumination_time, - DMD_output_synch_pulse_delay, synch_pulse_width, - DMD_trigger_in_delay, add_illumination_time) - - return spectrometer_params, DMD_params, camPar - - -def _calculate_elapsed_time(start_measurement_time: int, - measurement_time: np.ndarray, - timestamps: List[int], - ) -> Tuple[np.ndarray, np.ndarray]: - """Calculate acquisition timings. - - Calculates elapsed time between each callback measurement taking into - account the moment when the DMD started running a sequence. - Calculates elapsed time between each spectrum acquired by the spectrometer - based on the spectrometer's internal clock. - - Args: - start_measurement_time (int): - Time in nanoseconds when DMD is set to start running a sequence. - measurement_time (np.ndarray): - 1D array with `int` type timings in nanoseconds when each callbacks - starts. - timestamps (List[int]): - 1D array with measurement timestamps from spectrometer. - Timestamps count ticks for the last pixel of the spectrum was - received by the spectrometer microcontroller. Ticks are in 10 - microsecond units since the spectrometer started. - - Returns: - Tuple[np.ndarray, np.ndarray]: Tuple with measurement timings. - measurement_time (np.ndarray): - 1D array with `float` type elapsed times between each callback. - Units in milliseconds. - timestamps (np.ndarray): - 1D array with `float` type elapsed time between each measurement - made by the spectrometer based on its internal clock. - Units in milliseconds. - """ - - measurement_time = np.concatenate( - (start_measurement_time,measurement_time),axis=None) - - measurement_time = np.diff(measurement_time)/1e+6 # In ms - timestamps = np.diff(timestamps)/100 # In ms - - return measurement_time, timestamps - - -def _save_acquisition(metadata: MetaData, - DMD_params: DMDParameters, - spectrometer_params: SpectrometerParameters, - acquisition_parameters: AcquisitionParameters, - spectral_data: np.ndarray) -> None: - """Save all acquisition data and metadata. - - Args: - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - acquisition_parameters (AcquisitionParameters): - Acquisition related metadata object. - spectral_data (ndarray): - 1D array with `float` type spectrometer measurements. Array size - depends on start and stop pixels previously set to the spectrometer. - """ - - # Saving collected data and timings - path = Path(metadata.output_directory) - path = path / f'{metadata.experiment_name}_spectraldata.npz' - np.savez_compressed(path, spectral_data=spectral_data) - - # 'save_metadata' function is commented because the 'save_metadata_2arms' function is executed after the 'acquire' function in the "main_seq_2arms.py" prog - # # Saving metadata - # save_metadata(metadata, - # DMD_params, - # spectrometer_params, - # acquisition_parameters) - -def _save_acquisition_2arms(metadata: MetaData, - DMD_params: DMDParameters, - spectrometer_params: SpectrometerParameters, - camPar: CAM, - acquisition_parameters: AcquisitionParameters, - spectral_data: np.ndarray) -> None: - """Save all acquisition data and metadata. - - Args: - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - camPar (CAM): - Metadata object of the IDS monochrome camera - acquisition_parameters (AcquisitionParameters): - Acquisition related metadata object. - spectral_data (ndarray): - 1D array with `float` type spectrometer measurements. Array size - depends on start and stop pixels previously set to the spectrometer. - """ - - # Saving collected data and timings - path = Path(metadata.output_directory) - path = path / f'{metadata.experiment_name}_spectraldata.npz' - np.savez_compressed(path, spectral_data=spectral_data) - - # Saving metadata - save_metadata_2arms(metadata, - DMD_params, - spectrometer_params, - camPar, - acquisition_parameters) - - -def _acquire_raw(ava: Avantes, - DMD: ALP4, - spectrometer_params: SpectrometerParameters, - DMD_params: DMDParameters, - acquisition_params: AcquisitionParameters, - loop: bool = False - ) -> NamedTuple: - """Raw data acquisition. - - Setups a callback function to receive messages from spectrometer whenever a - measurement is ready to be read. Reads a measurement via a callback. - - Args: - ava (Avantes): - Connected spectrometer (Avantes object). - DMD (ALP4): - Connected DMD. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. - loop (bool): - if True, projet continuously the pattern, see the AlpProjStartCont function - if False, projet one time the seq of the patterns, see the AlpProjStart function (Default) - - Returns: - NamedTuple: NamedTuple containig spectral data and measurement timings. - spectral_data (ndarray): - 2D array of `float` of size (pattern_amount x pixel_amount) - containing measurements received from the spectrometer for each - pattern of a sequence. - spectrum_index (int): - Index of the last acquired spectrum. - timestamps (np.ndarray): - 1D array with `float` type elapsed time between each measurement - made by the spectrometer based on its internal clock. - Units in milliseconds. - measurement_time (np.ndarray): - 1D array with `float` type elapsed times between each callback. - Units in milliseconds. - start_measurement_time (float): - Time when acquisition started. - saturation_detected (bool): - Boolean incating if saturation was detected during acquisition. - """ - def register_callback(measurement_time, timestamps, - spectral_data, ava): - - def measurement_callback(handle, info): # If we want to reconstruct during callback; can use it in here. Add function as parameter. - nonlocal spectrum_index - nonlocal saturation_detected - - measurement_time[spectrum_index] = perf_counter_ns() - - if info.contents.value >= 0: - timestamp,spectrum = ava.get_data() - spectral_data[spectrum_index,:] = ( - np.ctypeslib.as_array(spectrum[0:pixel_amount])) - - if np.any(ava.get_saturated_pixels() > 0): - saturation_detected = True - - timestamps[spectrum_index] = np.ctypeslib.as_array(timestamp) - - else: # Set values to zero if an error occured - spectral_data[spectrum_index,:] = 0 - timestamps[spectrum_index] = 0 - - spectrum_index += 1 - - return measurement_callback - - - pixel_amount = (spectrometer_params.stop_pixel - - spectrometer_params.start_pixel + 1) - - measurement_time = np.zeros((acquisition_params.pattern_amount)) - timestamps = np.zeros((acquisition_params.pattern_amount),dtype=np.uint32) - spectral_data = np.zeros( - (acquisition_params.pattern_amount,pixel_amount),dtype=np.float64) - - # Boolean to indicate if saturation was detected during acquisition - saturation_detected = False - - spectrum_index = 0 # Accessed as nonlocal variable inside the callback - - if loop == False: - #spectro.register_callback(-2,acquisition_params.pattern_amount,pixel_amount) - callback = register_callback(measurement_time, timestamps, - spectral_data, ava) - measurement_callback = MeasureCallback(callback) - ava.measure_callback(-2, measurement_callback) - else: - ava.measure(-1) - - - DMD.Run(loop=loop) # if loop=False : Run the whole sequence only once, if loop=True : Run continuously one pattern - start_measurement_time = perf_counter_ns() - - if loop == False: - while(True): - if(spectrum_index >= acquisition_params.pattern_amount) and loop == False: - break - elif((perf_counter_ns() - start_measurement_time) / 1e+6 > - (2 * acquisition_params.pattern_amount * - DMD_params.picture_time_us / 1e+3)) and loop == False: - print('Stopping measurement. One of the equipments may be blocked ' - 'or disconnected.') - break - else: - sleep(acquisition_params.pattern_amount * - DMD_params.picture_time_us / 1e+6 / 10) - DMD.Halt() - else: - sleep(0.1) - - timestamp, spectrum = ava.get_data() - spectral_data_1 = (np.ctypeslib.as_array(spectrum[0:pixel_amount])) - - get_ipython().run_line_magic('matplotlib', 'qt') - plt.ion() # create GUI - figure, ax = plt.subplots(figsize=(10, 8)) - line1, = ax.plot(acquisition_params.wavelengths, spectral_data_1) - - plt.title("Tune the Spectrometer", fontsize=20) - plt.xlabel("Lambda (nm)") - plt.ylabel("counts") - plt.xticks(fontsize=14) - plt.yticks(fontsize=14) - plt.grid() - printed = False - while(True): - try: - timestamp, spectrum = ava.get_data() - spectral_data_1 = (np.ctypeslib.as_array(spectrum[0:pixel_amount])) - - line1.set_xdata(acquisition_params.wavelengths) - line1.set_ydata(spectral_data_1) # updating data values - - figure.canvas.draw() # drawing updated values - figure.canvas.flush_events() # flush prior plot - - if not printed: - print('Press "Ctrl + c" to exit') - if np.amax(spectral_data_1) >= 65535: - print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') - printed = True - - except KeyboardInterrupt: - if (DMD.Seqs): - DMD.Halt() - DMD.FreeSeq() - plt.close() - get_ipython().run_line_magic('matplotlib', 'inline') - break - - ava.stop_measure() - - AcquisitionResult = namedtuple('AcquisitionResult', [ - 'spectral_data', - 'spectrum_index', - 'timestamps', - 'measurement_time', - 'start_measurement_time', - 'saturation_detected']) - - return AcquisitionResult(spectral_data, - spectrum_index, - timestamps, - measurement_time, - start_measurement_time, - saturation_detected) - - -def acquire(ava: Avantes, - DMD: ALP4, - metadata: MetaData, - spectrometer_params: SpectrometerParameters, - DMD_params: DMDParameters, - acquisition_params: AcquisitionParameters, - repetitions: int = 1, - verbose: bool = False, - reconstruct: bool = False, - reconstruction_params: ReconstructionParameters = None - ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - """Perform a complete acquisition. - - Performs single or multiple acquisitions using the same setup configurations - previously chosen. - Finnaly saves all acqusition related data and metadata. - - Args: - ava (Avantes): - Connected spectrometer (Avantes object). - DMD (ALP4): - Connected DMD. - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. - wavelengths (List[float]): - List of float corresponding to the wavelengths associated with - spectrometer's start and stop pixels. - repetitions (int): - Number of times the acquisition will be repeated with the same - configurations. Default is 1, a single acquisition. - verbose (bool): - Chooses if data concerning each acquisition should be printed to - user. If False, only overall data regarding all repetitions is - printed. Default is False. - reconstruct (bool): - If True, will perform reconstruction alongside acquisition using - multiprocessing. - reconstruction_params (ReconstructionParameters): - Object containing parameters of the neural network to be loaded for - reconstruction. - - Returns: - Tuple[ndarray, ndarray, ndarray]: Tuple containig spectral data and - measurement timings. - spectral_data (ndarray): - 2D array of `float` of size (pattern_amount x pixel_amount) - containing measurements received from the spectrometer for each - pattern of a sequence. - timestamps (np.ndarray): - 1D array with `float` type elapsed time between each measurement - made by the spectrometer based on its internal clock. - Units in milliseconds. - measurement_time (np.ndarray): - 1D array with `float` type elapsed times between each callback. - Units in milliseconds. - """ - - loop = False # if true, is to projet continuously a unique pattern to tune the spectrometer - - if reconstruct == True: - print('Creating reconstruction processes') - - # Creating a Queue for sending spectral data to reconstruction process - queue_to_recon = Queue() - - # Creating a Queue for sending reconstructed images to plot - queue_reconstructed = Queue() - - sleep_time = (acquisition_params.pattern_amount * - DMD_params.picture_time_us/1e+6) - - # Creating reconstruction process - recon_process = Process(target=reconstruct_process, - args=(reconstruction_params.model, - reconstruction_params.device, - queue_to_recon, - queue_reconstructed, - reconstruction_params.batches, - reconstruction_params.noise, - sleep_time)) - - # Creating plot process - plot_process = Process(target=plot_recon, - args=(queue_reconstructed, sleep_time)) - - # Starting processes - recon_process.start() - plot_process.start() - - pixel_amount = (spectrometer_params.stop_pixel - - spectrometer_params.start_pixel + 1) - measurement_time = np.zeros( - (acquisition_params.pattern_amount * repetitions)) - timestamps = np.zeros( - ((acquisition_params.pattern_amount - 1) * repetitions), - dtype=np.float64) - spectral_data = np.zeros( - (acquisition_params.pattern_amount * repetitions,pixel_amount), - dtype=np.float64) - - acquisition_params.acquired_spectra = 0 - print() - - for repetition in range(repetitions): - if verbose: - print(f"Acquisition {repetition}") - - AcquisitionResults = _acquire_raw(ava, DMD, spectrometer_params, - DMD_params, acquisition_params, loop) - - (data, spectrum_index, timestamp, time, - start_measurement_time, saturation_detected) = AcquisitionResults - - print('Acquisition number : ' + str(repetition) + ' finished') - - if reconstruct == True: - queue_to_recon.put(data.T) - print('Data sent') - - time, timestamp = _calculate_elapsed_time( - start_measurement_time, time, timestamp) - - begin = repetition * acquisition_params.pattern_amount - end = (repetition + 1) * acquisition_params.pattern_amount - spectral_data[begin:end] = data - measurement_time[begin:end] = time - - begin = repetition * (acquisition_params.pattern_amount - 1) - end = (repetition + 1) * (acquisition_params.pattern_amount - 1) - timestamps[begin:end] = timestamp - - acquisition_params.acquired_spectra += spectrum_index - - acquisition_params.saturation_detected = saturation_detected - - if saturation_detected is True: - print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') - # Print data for each repetition - if (verbose): - print('Spectra acquired: {}'.format(spectrum_index)) - print('Mean callback acquisition time: {} ms'.format( - np.mean(time))) - print('Total callback acquisition time: {} s'.format( - np.sum(time)/1000)) - print('Mean spectrometer acquisition time: {} ms'.format( - np.mean(timestamp))) - print('Total spectrometer acquisition time: {} s'.format( - np.sum(timestamp)/1000)) - - # Print shape of acquisition matrix for one repetition - print(f'Partial acquisition matrix dimensions:' - f'{data.shape}') - print() - - acquisition_params.update_timings(timestamps, measurement_time) - # Real time between each spectrum acquisition by the spectrometer - print('Complete acquisition done') - print('Spectra acquired: {}'.format(acquisition_params.acquired_spectra)) - print('Total acquisition time: {0:.2f} s'.format(acquisition_params.total_spectrometer_acquisition_time_s)) - - _save_acquisition(metadata, DMD_params, spectrometer_params, - acquisition_params, spectral_data) - - # Joining processes and closing queues - if reconstruct == True: - queue_to_recon.put('kill') # Sends a message to stop reconstruction - recon_process.join() - queue_to_recon.close() - plot_process.join() - queue_reconstructed.close() - - maxi = np.amax(spectral_data[0,:]) - print('------------------------------------------------') - print('maximum in the spectrum = ' + str(maxi)) - print('------------------------------------------------') - if maxi >= 65535: - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') - print('!!!!! warning, spectrum saturation !!!!!!!!') - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') - - return spectral_data - -def _acquire_raw_2arms(ava: Avantes, - DMD: ALP4, - camPar: CAM, - spectrometer_params: SpectrometerParameters, - DMD_params: DMDParameters, - acquisition_params: AcquisitionParameters, - metadata, - repetition, - repetitions - ) -> NamedTuple: - """Raw data acquisition. - - Setups a callback function to receive messages from spectrometer whenever a - measurement is ready to be read. Reads a measurement via a callback. - - Args: - ava (Avantes): - Connected spectrometer (Avantes object). - DMD (ALP4): - Connected DMD. - camPar (CAM): - Metadata object of the IDS monochrome camera - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. - - Returns: - NamedTuple: NamedTuple containig spectral data and measurement timings. - spectral_data (ndarray): - 2D array of `float` of size (pattern_amount x pixel_amount) - containing measurements received from the spectrometer for each - pattern of a sequence. - spectrum_index (int): - Index of the last acquired spectrum. - timestamps (np.ndarray): - 1D array with `float` type elapsed time between each measurement - made by the spectrometer based on its internal clock. - Units in milliseconds. - measurement_time (np.ndarray): - 1D array with `float` type elapsed times between each callback. - Units in milliseconds. - start_measurement_time (float): - Time when acquisition started. - saturation_detected (bool): - Boolean incating if saturation was detected during acquisition. - """ - # def for spectrometer acquisition - def register_callback(measurement_time, timestamps, - spectral_data, ava): - - def measurement_callback(handle, info): # If we want to reconstruct during callback; can use it in here. Add function as parameter. - nonlocal spectrum_index - nonlocal saturation_detected - - measurement_time[spectrum_index] = perf_counter_ns() - - if info.contents.value >= 0: - timestamp,spectrum = ava.get_data() - spectral_data[spectrum_index,:] = ( - np.ctypeslib.as_array(spectrum[0:pixel_amount])) - - if np.any(ava.get_saturated_pixels() > 0): - saturation_detected = True - - timestamps[spectrum_index] = np.ctypeslib.as_array(timestamp) - - else: # Set values to zero if an error occured - spectral_data[spectrum_index,:] = 0 - timestamps[spectrum_index] = 0 - - spectrum_index += 1 - - return measurement_callback - - # def for camera acquisition - if repetition == 0: - camPar = stopCapt_DeallocMem(camPar) - camPar.trigger_mode = 'hard'#'soft'# - imageQueue(camPar) - camPar = prepareCam(camPar, metadata) - camPar.timeout = 1000 # time out in ms for the "is_WaitForNextImage" function - start_chrono = time.time() - x = threading.Thread(target = runCam_thread, args=(camPar, start_chrono)) - x.start() - - pixel_amount = (spectrometer_params.stop_pixel - - spectrometer_params.start_pixel + 1) - - measurement_time = np.zeros((acquisition_params.pattern_amount)) - timestamps = np.zeros((acquisition_params.pattern_amount),dtype=np.uint32) - spectral_data = np.zeros( - (acquisition_params.pattern_amount,pixel_amount),dtype=np.float64) - - # Boolean to indicate if saturation was detected during acquisition - saturation_detected = False - - spectrum_index = 0 # Accessed as nonlocal variable inside the callback - - #spectro.register_callback(-2,acquisition_params.pattern_amount,pixel_amount) - callback = register_callback(measurement_time, timestamps, - spectral_data, ava) - measurement_callback = MeasureCallback(callback) - ava.measure_callback(-2, measurement_callback) - - # time.sleep(0.5) - # Run the whole sequence only once - DMD.Run(loop=False) - start_measurement_time = perf_counter_ns() - #sleep(13) - - while(True): - if(spectrum_index >= acquisition_params.pattern_amount): - break - elif((perf_counter_ns() - start_measurement_time) / 1e+6 > - (2 * acquisition_params.pattern_amount * - DMD_params.picture_time_us / 1e+3)): - print('Stopping measurement. One of the equipments may be blocked ' - 'or disconnected.') - break - else: - time.sleep(acquisition_params.pattern_amount * - DMD_params.picture_time_us / 1e+6 / 10) - - ava.stop_measure() - DMD.Halt() - camPar.Exit = 2 - if repetition == repetitions-1: - camPar = stopCam(camPar) - #Yprint('MAIN :// camPar.camActivated = ' + str(camPar.camActivated)) - AcquisitionResult = namedtuple('AcquisitionResult', [ - 'spectral_data', - 'spectrum_index', - 'timestamps', - 'measurement_time', - 'start_measurement_time', - 'saturation_detected']) - - return AcquisitionResult(spectral_data, - spectrum_index, - timestamps, - measurement_time, - start_measurement_time, - saturation_detected) - - -def acquire_2arms(ava: Avantes, - DMD: ALP4, - camPar: CAM, - metadata: MetaData, - spectrometer_params: SpectrometerParameters, - DMD_params: DMDParameters, - acquisition_params: AcquisitionParameters, - repetitions: int = 1, - verbose: bool = False, - reconstruct: bool = False, - reconstruction_params: ReconstructionParameters = None - ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - """Perform a complete acquisition. - - Performs single or multiple acquisitions using the same setup configurations - previously chosen. - Finnaly saves all acqusition related data and metadata. - - Args: - ava (Avantes): - Connected spectrometer (Avantes object). - DMD (ALP4): - Connected DMD. - camPar (CAM): - Metadata object of the IDS monochrome camera - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. - wavelengths (List[float]): - List of float corresponding to the wavelengths associated with - spectrometer's start and stop pixels. - repetitions (int): - Number of times the acquisition will be repeated with the same - configurations. Default is 1, a single acquisition. - verbose (bool): - Chooses if data concerning each acquisition should be printed to - user. If False, only overall data regarding all repetitions is - printed. Default is False. - reconstruct (bool): - If True, will perform reconstruction alongside acquisition using - multiprocessing. - reconstruction_params (ReconstructionParameters): - Object containing parameters of the neural network to be loaded for - reconstruction. - - Returns: - Tuple[ndarray, ndarray, ndarray]: Tuple containig spectral data and - measurement timings. - spectral_data (ndarray): - 2D array of `float` of size (pattern_amount x pixel_amount) - containing measurements received from the spectrometer for each - pattern of a sequence. - timestamps (np.ndarray): - 1D array with `float` type elapsed time between each measurement - made by the spectrometer based on its internal clock. - Units in milliseconds. - measurement_time (np.ndarray): - 1D array with `float` type elapsed times between each callback. - Units in milliseconds. - """ - - if reconstruct == True: - print('Creating reconstruction processes') - - # Creating a Queue for sending spectral data to reconstruction process - queue_to_recon = Queue() - - # Creating a Queue for sending reconstructed images to plot - queue_reconstructed = Queue() - - sleep_time = (acquisition_params.pattern_amount * - DMD_params.picture_time_us/1e+6) - - # Creating reconstruction process - recon_process = Process(target=reconstruct_process, - args=(reconstruction_params.model, - reconstruction_params.device, - queue_to_recon, - queue_reconstructed, - reconstruction_params.batches, - reconstruction_params.noise, - sleep_time)) - - # Creating plot process - plot_process = Process(target=plot_recon, - args=(queue_reconstructed, sleep_time)) - - # Starting processes - recon_process.start() - plot_process.start() - - pixel_amount = (spectrometer_params.stop_pixel - - spectrometer_params.start_pixel + 1) - measurement_time = np.zeros( - (acquisition_params.pattern_amount * repetitions)) - timestamps = np.zeros( - ((acquisition_params.pattern_amount - 1) * repetitions), - dtype=np.float64) - spectral_data = np.zeros( - (acquisition_params.pattern_amount * repetitions,pixel_amount), - dtype=np.float64) - - acquisition_params.acquired_spectra = 0 - print() - - for repetition in range(repetitions): - if verbose: - print(f"Acquisition {repetition}") - - AcquisitionResults = _acquire_raw_2arms(ava, DMD, camPar, spectrometer_params, - DMD_params, acquisition_params, metadata, repetition, repetitions) - - (data, spectrum_index, timestamp, time, - start_measurement_time, saturation_detected) = AcquisitionResults - - print('Acquisition number : ' + str(repetition) + ' finished') - - if reconstruct == True: - queue_to_recon.put(data.T) - print('Data sent') - - time, timestamp = _calculate_elapsed_time( - start_measurement_time, time, timestamp) - - begin = repetition * acquisition_params.pattern_amount - end = (repetition + 1) * acquisition_params.pattern_amount - spectral_data[begin:end] = data - measurement_time[begin:end] = time - - begin = repetition * (acquisition_params.pattern_amount - 1) - end = (repetition + 1) * (acquisition_params.pattern_amount - 1) - timestamps[begin:end] = timestamp - - acquisition_params.acquired_spectra += spectrum_index - - acquisition_params.saturation_detected = saturation_detected - - if saturation_detected is True: - print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') - # Print data for each repetition - if (verbose): - print('Spectra acquired: {}'.format(spectrum_index)) - print('Mean callback acquisition time: {} ms'.format( - np.mean(time))) - print('Total callback acquisition time: {} s'.format( - np.sum(time)/1000)) - print('Mean spectrometer acquisition time: {} ms'.format( - np.mean(timestamp))) - print('Total spectrometer acquisition time: {} s'.format( - np.sum(timestamp)/1000)) - - # Print shape of acquisition matrix for one repetition - print(f'Partial acquisition matrix dimensions:' - f'{data.shape}') - print() - - acquisition_params.update_timings(timestamps, measurement_time) - # Real time between each spectrum acquisition by the spectrometer - print('Complete acquisition done') - print('Spectra acquired: {}'.format(acquisition_params.acquired_spectra)) - print('Total acquisition time: {0:.2f} s'.format(acquisition_params.total_spectrometer_acquisition_time_s)) - - # delete acquisition with black pattern (white for the camera) - if camPar.insert_patterns == 1: - black_pattern_index = np.where(acquisition_params.patterns_wp == -1) - # print('index of white patterns :') - # print(black_pattern_index[0:38]) - if acquisition_params.patterns_wp.shape == acquisition_params.patterns.shape: - acquisition_params.patterns = np.delete(acquisition_params.patterns, black_pattern_index) - spectral_data = np.delete(spectral_data, black_pattern_index, axis = 0) - acquisition_params.timestamps = np.delete(acquisition_params.timestamps, black_pattern_index[1:]) - acquisition_params.measurement_time = np.delete(acquisition_params.measurement_time, black_pattern_index) - acquisition_params.acquired_spectra = len(acquisition_params.patterns) - - _save_acquisition_2arms(metadata, DMD_params, spectrometer_params, camPar, - acquisition_params, spectral_data) - - # Joining processes and closing queues - if reconstruct == True: - queue_to_recon.put('kill') # Sends a message to stop reconstruction - recon_process.join() - queue_to_recon.close() - plot_process.join() - queue_reconstructed.close() - - maxi = np.amax(spectral_data[0,:]) - print('------------------------------------------------') - print('maximum in the spectrum = ' + str(maxi)) - print('------------------------------------------------') - if maxi >= 65535: - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') - print('!!!!! warning, spectrum saturation !!!!!!!!') - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') - - return spectral_data - - -def setup_tuneSpectro(spectrometer, - DMD, - DMD_initial_memory, - pattern_to_display, - ti : float = 1, - zoom : int = 1, - xw_offset: int = 128, - yh_offset: int = 0, - mask_index : np.array = []): - """ Setup the hadrware to tune the spectrometer in live. The goal is to find - the integration time of the spectrometer, noise is around 700 counts, - saturation is equal to 2**16=65535 - - Args: - spectrometer (Avantes): - Connected spectrometer (Avantes object). - DMD (ALP4): - Connected DMD. - DMD_initial_memory (int): - Initial memory available in DMD after initialization. - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. User must partially fill up - with pattern_compression, pattern_dimension_x, pattern_dimension_y. - pattern_to_display (string): - display one pattern on the DMD to tune the spectrometer. Default is - white pattern - ti (float): - The integration time of the spectrometer during one scan in miliseconds. - Default is 1 ms. - zoom (int): - digital zoom on the DMD. Default is 1 - xw_offset (int): - offset of the pattern in the DMD for zoom > 1 in the width (x) direction - yh_offset (int): - offset of the pattern in the DMD for zoom > 1 in the heihgt (y) direction - mask_index (Union[np.ndarray, str], optional): - Array of `int` type corresponding to the index of the mask vector where - the value is egal to 1 - - return: - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - """ - - data_folder_name = 'Tune' - data_name = 'test' - # all_path = func_path(data_folder_name, data_name) - - scan_mode = 'Walsh' - Np = 16 - source = '' - object_name = '' - - metadata = MetaData( - output_directory = '',#all_path.subfolder_path, - pattern_order_source = 'C:/openspyrit/spas/stats/pattern_order_' + scan_mode + '_' + str(Np) + 'x' + str(Np) + '.npz', - pattern_source = 'C:/openspyrit/spas/Patterns/' + scan_mode + '_' + str(Np) + 'x' + str(Np), - pattern_prefix = scan_mode + '_' + str(Np) + 'x' + str(Np), - experiment_name = data_name, - light_source = source, - object = object_name, - filter = '', - description = '' - ) - - acquisition_parameters = AcquisitionParameters( - pattern_compression = 1, - pattern_dimension_x = 16, - pattern_dimension_y = 16, - zoom = zoom, - xw_offset = xw_offset, - yh_offset = yh_offset, - mask_index = [] ) - - acquisition_parameters.pattern_amount = 1 - - spectrometer_params, DMD_params = setup( - spectrometer = spectrometer, - DMD = DMD, - DMD_initial_memory = DMD_initial_memory, - metadata = metadata, - acquisition_params = acquisition_parameters, - pattern_to_display = pattern_to_display, - integration_time = ti, - loop = True ) - - return metadata, spectrometer_params, DMD_params, acquisition_parameters - - -def displaySpectro(ava: Avantes, - DMD: ALP4, - metadata: MetaData, - spectrometer_params: SpectrometerParameters, - DMD_params: DMDParameters, - acquisition_params: AcquisitionParameters, - reconstruction_params: ReconstructionParameters = None - ): - """Perform a continousely acquisition on the spectrometer for optical tuning. - - Send a pattern on the DMD to project light on the spectrometer. The goal is - to have a look on the amplitude of the spectrum to tune the illumination to - avoid saturation (sat >= 65535) and noisy signal (amp <= 650). - - Args: - ava (Avantes): - Connected spectrometer (Avantes object). - DMD (ALP4): - Connected DMD. - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - spectrometer_params (SpectrometerParameters): - Spectrometer metadata object with spectrometer configurations. - DMD_params (DMDParameters): - DMD metadata object with DMD configurations. - acquisition_params (AcquisitionParameters): - Acquisition related metadata object. - wavelengths (List[float]): - List of float corresponding to the wavelengths associated with - spectrometer's start and stop pixels. - reconstruction_params (ReconstructionParameters): - Object containing parameters of the neural network to be loaded for - reconstruction. - """ - - loop = True # is to project continuously a unique pattern to tune the spectrometer - - pixel_amount = (spectrometer_params.stop_pixel - - spectrometer_params.start_pixel + 1) - - spectral_data = np.zeros( - (acquisition_params.pattern_amount,pixel_amount), - dtype=np.float64) - - acquisition_params.acquired_spectra = 0 - - AcquisitionResults = _acquire_raw(ava, DMD, spectrometer_params, - DMD_params, acquisition_params, loop) - - (data, spectrum_index, timestamp, time, - start_measurement_time, saturation_detected) = AcquisitionResults - - time, timestamp = _calculate_elapsed_time( - start_measurement_time, time, timestamp) - - begin = acquisition_params.pattern_amount - end = 2 * acquisition_params.pattern_amount - spectral_data[begin:end] = data - - acquisition_params.acquired_spectra += spectrum_index - - acquisition_params.saturation_detected = saturation_detected - - -def check_ueye(func, *args, exp=0, raise_exc=True, txt=None): - """Check for bad input value - - Args: - ---------- - func : TYPE - the ueye function. - *args : TYPE - the input value. - exp : TYPE, optional - DESCRIPTION. The default is 0. - raise_exc : TYPE, optional - DESCRIPTION. The default is True. - txt : TYPE, optional - DESCRIPTION. The default is None. - - Raises - ------ - RuntimeError - DESCRIPTION. - - Returns - ------- - None. - """ - - ret = func(*args) - if not txt: - txt = "{}: Expected {} but ret={}!".format(str(func), exp, ret) - if ret != exp: - if raise_exc: - raise RuntimeError(txt) - else: - logging.critical(txt) - - -def stopCapt_DeallocMem(camPar): - """Stop capture and deallocate camera memory if need to change AOI - - Args: - ---------- - camPar (CAM): - Metadata object of the IDS monochrome camera - - Returns: - ------- - camPar (CAM): - Metadata object of the IDS monochrome camera - """ - - if camPar.camActivated == 1: - nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) - if nRet == ueye.IS_SUCCESS: - camPar.camActivated = 0 - print('video stop successful') - else: - print('problem to stop the video') - - if camPar.Memory == 1: - nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) - if nRet == ueye.IS_SUCCESS: - camPar.Memory = 0 - print('deallocate memory successful') - else: - print('Problem to deallocate memory of the camera') - - return camPar - - -def stopCapt_DeallocMem_ExitCam(camPar): - """Stop capture, deallocate camera memory if need to change AOI and disconnect the camera - - Args: - ---------- - camPar (CAM): - Metadata object of the IDS monochrome camera - - Returns: - ------- - camPar (CAM): - Metadata object of the IDS monochrome camera - """ - if camPar.camActivated == 1: - nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) - if nRet == ueye.IS_SUCCESS: - camPar.camActivated = 0 - print('video stop successful') - else: - print('problem to stop the video') - - if camPar.Memory == 1: - nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) - if nRet == ueye.IS_SUCCESS: - camPar.Memory = 0 - print('deallocate memory successful') - else: - print('Problem to deallocate memory of the camera') - - if camPar.Exit == 2: - nRet = ueye.is_ExitCamera(camPar.hCam) - if nRet == ueye.IS_SUCCESS: - camPar.Exit = 0 - print('Camera disconnected') - else: - print('Problem to disconnect camera, need to restart spyder') - - return camPar - - -class ImageBuffer: - """A class to allocate buffer in the camera memory - """ - - pcImageMemory = None - MemID = None - width = None - height = None - nbitsPerPixel = None - - -def imageQueue(camPar): - """Create Imagequeue / Allocate 3 ore more buffers depending on the framerate / Initialize Image queue - - Args: - ---------- - camPar (CAM): - Metadata object of the IDS monochrome camera - - Returns: - ------- - None. - - """ - - sleep(1) # is required (delay of 1s was not optimized!!) - buffers = [] - for y in range(10): - buffers.append(ImageBuffer()) - - for x in range(len(buffers)): - buffers[x].nbitsPerPixel = camPar.nBitsPerPixel # RAW8 - buffers[x].height = camPar.rectAOI.s32Height # sensorinfo.nMaxHeight - buffers[x].width = camPar.rectAOI.s32Width # sensorinfo.nMaxWidth - buffers[x].MemID = ueye.int(0) - buffers[x].pcImageMemory = ueye.c_mem_p() - check_ueye(ueye.is_AllocImageMem, camPar.hCam, buffers[x].width, buffers[x].height, buffers[x].nbitsPerPixel, - buffers[x].pcImageMemory, buffers[x].MemID) - check_ueye(ueye.is_AddToSequence, camPar.hCam, buffers[x].pcImageMemory, buffers[x].MemID) - - check_ueye(ueye.is_InitImageQueue, camPar.hCam, ueye.c_int(0)) - if camPar.trigger_mode == 'soft': - check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_SOFTWARE) - elif camPar.trigger_mode == 'hard': - check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_LO_HI) - - -def prepareCam(camPar, metadata): - """Prepare the IDS monochrome camera before acquisition - - Args: - ---------- - camPar (CAM): - Metadata object of the IDS monochrome camera - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. Must be created and filled up by the user. - - Returns: - ------- - camPar (CAM): - Metadata object of the IDS monochrome camera - - """ - cam_path = metadata.output_directory + '\\' + metadata.experiment_name + '_video.' + camPar.vidFormat - strFileName = ueye.c_char_p(cam_path.encode('utf-8')) - - if camPar.vidFormat == 'avi': - # print('Video format : AVI') - camPar.avi = ueye.int() - nRet = ueye_tools.isavi_InitAVI(camPar.avi, camPar.hCam) - # print("isavi_InitAVI") - if nRet != ueye_tools.IS_AVI_NO_ERR: - print("isavi_InitAVI ERROR") - - nRet = ueye_tools.isavi_SetImageSize(camPar.avi, camPar.m_nColorMode, camPar.rectAOI.s32Width , camPar.rectAOI.s32Height, 0, 0, 0) - nRet = ueye_tools.isavi_SetImageQuality(camPar.avi, 100) - if nRet != ueye_tools.IS_AVI_NO_ERR: - print("isavi_SetImageQuality ERROR") - - nRet = ueye_tools.isavi_OpenAVI(camPar.avi, strFileName) - if nRet != ueye_tools.IS_AVI_NO_ERR: - print("isavi_OpenAVI ERROR") - print('Error code = ' + str(nRet)) - print('Certainly, it is a problem with the file name, Avoid special character like "µ" or try to redcue its size') - - nRet = ueye_tools.isavi_SetFrameRate(camPar.avi, camPar.fps) - if nRet != ueye_tools.IS_AVI_NO_ERR: - print("isavi_SetFrameRate ERROR") - nRet = ueye_tools.isavi_StartAVI(camPar.avi) - # print("isavi_StartAVI") - if nRet != ueye_tools.IS_AVI_NO_ERR: - print("isavi_StartAVI ERROR") - - - elif camPar.vidFormat == 'bin': - camPar.punFileID = ueye.c_uint() - nRet = ueye_tools.israw_InitFile(camPar.punFileID, ueye_tools.IS_FILE_ACCESS_MODE_WRITE) - if nRet != ueye_tools.IS_AVI_NO_ERR: - print("INIT RAW FILE ERROR") - - nRet = ueye_tools.israw_SetImageInfo(camPar.punFileID, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel) - if nRet != ueye_tools.IS_AVI_NO_ERR: - print("SET IMAGE INFO ERROR") - - if nRet == ueye.IS_SUCCESS: - # print('initFile ok') - # print('SetImageInfo ok') - nRet = ueye_tools.israw_OpenFile(camPar.punFileID, strFileName) - # if nRet == ueye.IS_SUCCESS: - # # print('OpenFile success') - - # --------------------------------------------------------- - # Activates the camera's live video mode (free run mode) - # --------------------------------------------------------- - nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) - - if nRet != ueye.IS_SUCCESS: - print("is_CaptureVideo ERROR") - else: - camPar.camActivated = 1 - - return camPar - - -def runCam_thread(camPar, start_chrono): - """Acquire video with the IDS monochrome camera in a thread - - Parameters: - ---------- - camPar (CAM): - Metadata object of the IDS monochrome camera - start_chrono : int - to save a delay for each acquisition frame of the video. - - Returns: - ------- - None. - """ - - imageinfo = ueye.UEYEIMAGEINFO() - current_buffer = ueye.c_mem_p() - current_id = ueye.int() - # inc = 0 - entier_old = 0 - # time.sleep(0.01) - while True: - nret = ueye.is_WaitForNextImage(camPar.hCam, camPar.timeout, current_buffer, current_id) - if nret == ueye.IS_SUCCESS: - check_ueye(ueye.is_GetImageInfo, camPar.hCam, current_id, imageinfo, ueye.sizeof(imageinfo)) - start_time = time.time() - counter = start_time - start_chrono - camPar.time_array.append(counter) - if camPar.vidFormat == 'avi': - nRet = ueye_tools.isavi_AddFrame(camPar.avi, current_buffer) - elif camPar.vidFormat == 'bin': - nRet = ueye_tools.israw_AddFrame(camPar.punFileID, current_buffer, imageinfo.u64TimestampDevice) - - check_ueye(ueye.is_UnlockSeqBuf, camPar.hCam, current_id, current_buffer) - else: - print('Thread finished') - break - - -def stopCam(camPar): - """To stop the acquisition of the video - - Parameters - ---------- - camPar (CAM): - Metadata object of the IDS monochrome camera - - Returns - ------- - camPar (CAM): - Metadata object of the IDS monochrome camera - """ - - if camPar.vidFormat == 'avi': - ueye_tools.isavi_StopAVI(camPar.hCam) - ueye_tools.isavi_CloseAVI(camPar.hCam) - ueye_tools.isavi_ExitAVI(camPar.hCam) - elif camPar.vidFormat == 'bin': - ueye_tools.israw_CloseFile(camPar.punFileID) - ueye_tools.israw_ExitFile(camPar.punFileID) - camPar.punFileID = ueye.c_uint() - - return camPar - - -def disconnect(ava: Optional[Avantes]=None, - DMD: Optional[ALP4]=None): - """Disconnect spectrometer and DMD. - - Disconnects equipments trying to stop a running pattern sequence (possibly - blocking correct functioning) and trying to free DMD memory to avoid errors - in later acqusitions. - - Args: - ava (Avantes, optional): - Connected spectrometer (Avantes object). Defaults to None. - DMD (ALP4, optional): - Connected DMD. Defaults to None. - """ - - if ava is not None: - ava.disconnect() - print('Spectro disconnected') - - if DMD is not None: - - # Stop the sequence display - DMD.Halt() - - # Free the sequence from the onboard memory (if any is present) - if (DMD.Seqs): - DMD.FreeSeq() - - DMD.Free() - print('DMD disconnected') - - -def disconnect_2arms(ava: Optional[Avantes]=None, - DMD: Optional[ALP4]=None, - camPar=None): - """Disconnect spectrometer, DMD and the IDS monochrome camera. - - Disconnects equipments trying to stop a running pattern sequence (possibly - blocking correct functioning) and trying to free DMD memory to avoid errors - in later acqusitions. - - Args: - ava (Avantes, optional): - Connected spectrometer (Avantes object). Defaults to None. - DMD (ALP4, optional): - Connected DMD. Defaults to None. - camPar (CAM): - Metadata object of the IDS monochrome camera - """ - - if ava is not None: - ava.disconnect() - print('Spectro disconnected') - - if DMD is not None: - # Stop the sequence display - try: - DMD.Halt() - # Free the sequence from the onboard memory (if any is present) - if (DMD.Seqs): - DMD.FreeSeq() - - DMD.Free() - print('DMD disconnected') - - except: - print('probelm to Halt the DMD') - - - if camPar.camActivated == 1: - nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) - if nRet == ueye.IS_SUCCESS: - camPar.camActivated = 0 - else: - print('Problem to stop video, need to restart spyder') - - if camPar.Memory == 1: - nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) - if nRet == ueye.IS_SUCCESS: - camPar.Memory = 0 - else: - print('Problem to deallocate camera memory, need to restart spyder') - - - if camPar.Exit == 1 or camPar.Exit == 2: - nRet = ueye.is_ExitCamera(camPar.hCam) - if nRet == ueye.IS_SUCCESS: - camPar.Exit = 0 - print('Camera disconnected') - else: - print('Problem to disconnect camera, need to restart spyder') - - -def _init_CAM(): - """ - Initialize and connect to the IDS camera. - - Returns: - CAM: a structure containing the parameters of the IDS camera - """ - camPar = CAM(hCam = ueye.HIDS(0), - sInfo = ueye.SENSORINFO(), - cInfo = ueye.CAMINFO(), - nBitsPerPixel = ueye.INT(8), - m_nColorMode = ueye.INT(), - bytes_per_pixel = int( ueye.INT(8)/ 8), - rectAOI = ueye.IS_RECT(), - pcImageMemory = ueye.c_mem_p(), - MemID = ueye.int(), - pitch = ueye.INT(), - fps = float(), - gain = int(), - gainBoost = str(), - gamma = float(), - exposureTime = float(), - blackLevel = int(), - camActivated = bool(), - pixelClock = ueye.uint(), - bandwidth = float(), - Memory = bool(), - Exit = int(), - vidFormat = str(), - gate_period = int(), - trigger_mode = str(), - avi = ueye.int(), - punFileID = ueye.c_uint(), - timeout = int(), - time_array = [], - int_time_spect = float(), - black_pattern_num = int(), - insert_patterns = bool(), - acq_mode = str(), - ) - - # # Camera Initialization --- - ### Starts the driver and establishes the connection to the camera - nRet = ueye.is_InitCamera(camPar.hCam, None) - if nRet != ueye.IS_SUCCESS: - print("is_InitCamera ERROR") - - ### Reads out the data hard-coded in the non-volatile camera memory and writes it to the data structure that cInfo points to - nRet = ueye.is_GetCameraInfo(camPar.hCam, camPar.cInfo) - if nRet != ueye.IS_SUCCESS: - print("is_GetCameraInfo ERROR") - - ### You can query additional information about the sensor type used in the camera - nRet = ueye.is_GetSensorInfo(camPar.hCam, camPar.sInfo) - if nRet != ueye.IS_SUCCESS: - print("is_GetSensorInfo ERROR") - - ### set camera parameters to default values - nRet = ueye.is_ResetToDefault(camPar.hCam) - if nRet != ueye.IS_SUCCESS: - print("is_ResetToDefault ERROR") - - ### Set display mode to DIB - nRet = ueye.is_SetDisplayMode(camPar.hCam, ueye.IS_SET_DM_DIB) - - ### Set the right color mode - if int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_BAYER: - # setup the color depth to the current windows setting - ueye.is_GetColorDepth(camPar.hCam, camPar.nBitsPerPixel, camPar.m_nColorMode) - camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) - elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_CBYCRY: - # for color camera models use RGB32 mode - camPar.m_nColorMode = ueye.IS_CM_BGRA8_PACKED - camPar.nBitsPerPixel = ueye.INT(32) - camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) - elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_MONOCHROME: - # for color camera models use RGB32 mode - camPar.m_nColorMode = ueye.IS_CM_MONO8 - camPar.nBitsPerPixel = ueye.INT(8) - camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) - else: - # for monochrome camera models use Y8 mode - camPar.m_nColorMode = ueye.IS_CM_MONO8 - camPar.nBitsPerPixel = ueye.INT(8) - camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) - # print("else") - - ### Get the AOI (Area Of Interest) - sizeofrectAOI = ueye.c_uint(4*4) - nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_GET_AOI, camPar.rectAOI, sizeofrectAOI) - if nRet != ueye.IS_SUCCESS: - print("AOI getting ERROR") - - camPar.camActivated = 0 - - # Get current pixel clock - getpixelclock = ueye.UINT(0) - check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, - ueye.sizeof(getpixelclock)) - - camPar.pixelClock = getpixelclock - # print('pixel clock = ' + str(getpixelclock) + ' MHz') - - # get the bandwidth (in MByte/s) - camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) - - camPar.Exit = 1 - - print('IDS camera connected') - - return camPar - - -def captureVid(camPar): - """ - Allocate memory and begin video capture of the IDS camera - - Args: - camPar : a structure containing the parameters of the IDS camera. - - Returns: - camPar : a structure containing the parameters of the IDS camera. - """ - camPar = stopCapt_DeallocMem_ExitCam(camPar) - - if camPar.Exit == 0: - camPar = _init_CAM() - camPar.Exit = 1 - - - ### Set the AOI - sizeofrectAOI = ueye.c_uint(4*4) - nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_SET_AOI, camPar.rectAOI, sizeofrectAOI) - if nRet != ueye.IS_SUCCESS: - print("AOI setting ERROR") - - width = camPar.rectAOI.s32Width - height = camPar.rectAOI.s32Height - - ### Allocates an image memory for an image having its dimensions defined by width and height and its color depth defined by nBitsPerPixel - nRet = ueye.is_AllocImageMem(camPar.hCam, width, height, camPar.nBitsPerPixel, camPar.pcImageMemory, camPar.MemID) - if nRet != ueye.IS_SUCCESS: - print("is_AllocImageMem ERROR") - else: - # Makes the specified image memory the active memory - camPar.Memory = 1 - nRet = ueye.is_SetImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) - if nRet != ueye.IS_SUCCESS: - print("is_SetImageMem ERROR") - else: - # Set the desired color mode - nRet = ueye.is_SetColorMode(camPar.hCam, camPar.m_nColorMode) - - - ### Activates the camera's live video mode (free run mode) - nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) - if nRet != ueye.IS_SUCCESS: - print("is_CaptureVideo ERROR") - - - ### Enables the queue mode for existing image memory sequences - nRet = ueye.is_InquireImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID, width, height, camPar.nBitsPerPixel, camPar.pitch) - if nRet != ueye.IS_SUCCESS: - print("is_InquireImageMem ERROR") - - camPar.camActivated = 1 - - return camPar - -def setup_cam(camPar, pixelClock, fps, Gain, gain_boost, nGamma, ExposureTime, black_level): - """ - Set and read the camera parameters - - Args: - pixelClock = [118, 237 or 474] (MHz) - fps: fps boundary => [1 - No Value] sup limit depend of image size (216 fps for 768x544 pixels for example) - Gain: Gain boundary => [0 100] - gain_boost: 'ON' set "ON" to activate gain boost, "OFF" to deactivate - nGamma: Gamma boundary => [1 - 2.2] - ExposureTime: Exposure time (ms) boundarye => [0.032 - 56.221] - black_level: Black Level boundary => [0 255] - - returns: - CAM: a structure containing the parameters of the IDS camera - """ - # It is necessary to execute twice this code to take account the parameter modification - for i in range(2): - ############################### Set Pixel Clock ############################### - ### Get range of pixel clock, result : range = [118 474] MHz (Inc = 0) - getpixelclock = ueye.UINT(0) - newpixelclock = ueye.UINT(0) - newpixelclock.value = pixelClock - PixelClockRange = (ueye.int * 3)() - - # Get pixel clock range - nRet = ueye.is_PixelClock(camPar.hCam, ueye.IS_PIXELCLOCK_CMD_GET_RANGE, PixelClockRange, ueye.sizeof(PixelClockRange)) - if nRet == ueye.IS_SUCCESS: - nPixelClockMin = PixelClockRange[0] - nPixelClockMax = PixelClockRange[1] - nPixelClockInc = PixelClockRange[2] - - # Set pixel clock - check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_SET, newpixelclock, - ueye.sizeof(newpixelclock)) - # Get current pixel clock - check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, - ueye.sizeof(getpixelclock)) - - camPar.pixelClock = getpixelclock.value - if i == 1: - print(' pixel clock = ' + str(getpixelclock) + ' MHz') - if getpixelclock == 118: - if i == 1: - print('Pixel clcok blocked to 118 MHz, it is necessary to unplug the camera if not desired') - # get the bandwidth (in MByte/s) - camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) - if i == 1: - print(' Bandwidth = ' + str(camPar.bandwidth) + ' MB/s') - ############################### Set FrameRate ################################# - ### Read current FrameRate - dblFPS_init = ueye.c_double() - nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_init) - if nRet != ueye.IS_SUCCESS: - print("FrameRate getting ERROR") - else: - dblFPS_eff = dblFPS_init - if i == 1: - print(' current FPS = '+str(round(dblFPS_init.value*100)/100) + ' fps') - if fps < 1: - fps = 1 - if i == 1: - print('FPS exceed lower limit >= 1') - - dblFPS = ueye.c_double(fps) - if (dblFPS.value < dblFPS_init.value-0.01) | (dblFPS.value > dblFPS_init.value+0.01): - newFPS = ueye.c_double() - nRet = ueye.is_SetFrameRate(camPar.hCam, dblFPS, newFPS) - time.sleep(1) - if nRet != ueye.IS_SUCCESS: - print("FrameRate setting ERROR") - else: - if i == 1: - print(' new FPS = '+str(round(newFPS.value*100)/100) + ' fps') - ### Read again the effective FPS / depend of the image size, 17.7 fps is not possible with the entire image size (ie 2076x3088) - dblFPS_eff = ueye.c_double() - nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_eff) - if nRet != ueye.IS_SUCCESS: - print("FrameRate getting ERROR") - else: - if i == 1: - print(' effective FPS = '+str(round(dblFPS_eff.value*100)/100) + ' fps') - ############################### Set GAIN ###################################### - #### Maximum gain is depending of the sensor. Convertion gain code to gain to limit values from 0 to 100 - # gain_code = gain * slope + b - gain_max_code = 1450 - gain_min_code = 100 - gain_max = 100 - gain_min = 0 - slope = (gain_max_code-gain_min_code)/(gain_max-gain_min) - b = gain_min_code - #### Read gain setting - current_gain_code = ueye.c_int() - current_gain_code = ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_GET_MASTER_GAIN_FACTOR, current_gain_code) - current_gain = round((current_gain_code-b)/slope) - - if i == 1: - print(' current GAIN = '+str(current_gain)) - gain_eff = current_gain - - ### Set new gain value - gain = ueye.c_int(Gain) - if gain.value != current_gain: - if gain.value < 0: - gain = ueye.c_int(0) - if i == 1: - print('Gain exceed lower limit >= 0') - elif gain.value > 100: - gain = ueye.c_int(100) - if i == 1: - print('Gain exceed upper limit <= 100') - gain_code = ueye.c_int(round(slope*gain.value+b)) - - ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_SET_MASTER_GAIN_FACTOR, gain_code) - new_gain = round((gain_code-b)/slope) - - if i == 1: - print(' new GAIN = '+str(new_gain)) - gain_eff = new_gain - ############################### Set GAIN Boost ################################ - ### Read current state of the gain boost - current_gain_boost_bool = ueye.is_SetGainBoost(camPar.hCam, ueye.IS_GET_GAINBOOST) - if nRet != ueye.IS_SUCCESS: - print("Gain boost ERROR") - if current_gain_boost_bool == 0: - current_gain_boost = 'OFF' - elif current_gain_boost_bool == 1: - current_gain_boost = 'ON' - - if i == 1: - print('current Gain boost mode = ' + current_gain_boost) - - ### Set the state of the gain boost - if gain_boost != current_gain_boost: - if gain_boost == 'OFF': - nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_OFF) - print(' new Gain Boost : OFF') - - elif gain_boost == 'ON': - nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_ON) - print(' new Gain Boost : ON') - - if nRet != ueye.IS_SUCCESS: - print("Gain boost setting ERROR") - ############################### Set Gamma ##################################### - ### Check boundary of Gamma - if nGamma > 2.2: - nGamma = 2.2 - if i == 1: - print('Gamma exceed upper limit <= 2.2') - elif nGamma < 1: - nGamma = 1 - if i == 1: - print('Gamma exceed lower limit >= 1') - ### Read current Gamma - c_nGamma_init = ueye.c_void_p() - sizeOfnGamma = ueye.c_uint(4) - nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_GET, c_nGamma_init, sizeOfnGamma) - if nRet != ueye.IS_SUCCESS: - print("Gamma getting ERROR") - else: - if i == 1: - print(' current Gamma = ' + str(c_nGamma_init.value/100)) - ### Set Gamma - c_nGamma = ueye.c_void_p(round(nGamma*100)) # need to multiply by 100 [100 - 220] - if c_nGamma_init.value != c_nGamma.value: - nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_SET, c_nGamma, sizeOfnGamma) - if nRet != ueye.IS_SUCCESS: - print("Gamma setting ERROR") - else: - if i == 1: - print(' new Gamma = '+str(c_nGamma.value/100)) - ############################### Set Exposure time ############################# - ### Read current Exposure Time - getExposure = ueye.c_double() - sizeOfpParam = ueye.c_uint(8) - nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE, getExposure, sizeOfpParam) - if nRet == ueye.IS_SUCCESS: - getExposure.value = round(getExposure.value*1000)/1000 - - if i == 1: - print(' current Exposure Time = ' + str(getExposure.value) + ' ms') - ### Get minimum Exposure Time - minExposure = ueye.c_double() - nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MIN, minExposure, sizeOfpParam) - ### Get maximum Exposure Time - maxExposure = ueye.c_double() - nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MAX, maxExposure, sizeOfpParam) - ### Get increment Exposure Time - incExposure = ueye.c_double() - nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_INC, incExposure, sizeOfpParam) - ### Set new Exposure Time - setExposure = ueye.c_double(ExposureTime) - if setExposure.value > maxExposure.value: - setExposure.value = maxExposure.value - if i == 1: - print('Exposure Time exceed upper limit <= ' + str(maxExposure.value)) - elif setExposure.value < minExposure.value: - setExposure.value = minExposure.value - if i == 1: - print('Exposure Time exceed lower limit >= ' + str(minExposure.value)) - - if (setExposure.value < getExposure.value-incExposure.value/2) | (setExposure.value > getExposure.value+incExposure.value/2): - nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_SET_EXPOSURE, setExposure, sizeOfpParam) - if nRet != ueye.IS_SUCCESS: - print("Exposure Time ERROR") - else: - if i == 1: - print(' new Exposure Time = ' + str(round(setExposure.value*1000)/1000) + ' ms') - ############################### Set Black Level ############################### - current_black_level_c = ueye.c_uint() - sizeOfBlack_level = ueye.c_uint(4) - ### Read current Black Level - nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_GET_OFFSET, current_black_level_c, sizeOfBlack_level) - if nRet != ueye.IS_SUCCESS: - print("Black Level getting ERROR") - else: - if i == 1: - print(' current Black Level = ' + str(current_black_level_c.value)) - - ### Set Black Level - if black_level > 255: - black_level = 255 - if i == 1: - print('Black Level exceed upper limit <= 255') - if black_level < 0: - black_level = 0 - if i == 1: - print('Black Level exceed lower limit >= 0') - - black_level_c = ueye.c_uint(black_level) - if black_level != current_black_level_c.value : - nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_SET_OFFSET, black_level_c, sizeOfBlack_level) - if nRet != ueye.IS_SUCCESS: - print("Black Level setting ERROR") - else: - if i == 1: - print(' new Black Level = ' + str(black_level_c.value)) - - - camPar.fps = round(dblFPS_eff.value*100)/100 - camPar.gain = gain_eff - camPar.gainBoost = gain_boost - camPar.gamma = c_nGamma.value/100 - camPar.exposureTime = round(setExposure.value*1000)/1000 - camPar.blackLevel = black_level_c.value - - return camPar - - -def snapshot(camPar, pathIDSsnapshot, pathIDSsnapshot_overview): - """ - Snapshot of the IDS camera - - Args: - CAM: a structure containing the parameters of the IDS camera - """ - array = ueye.get_data(camPar.pcImageMemory, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel, camPar.pitch, copy=False) - - # ...reshape it in an numpy array... - frame = np.reshape(array,(camPar.rectAOI.s32Height.value, camPar.rectAOI.s32Width.value))#, camPar.bytes_per_pixel)) - - with pathIDSsnapshot.open('wb') as f: #('ab') as f: #(pathname, mode='w', encoding='utf-8') as f: #('ab') as f: - np.save(f,frame) - - maxi = np.amax(frame) - if maxi == 0: - maxi = 1 - im = Image.fromarray(frame*math.floor(255/maxi)) - im.save(pathIDSsnapshot_overview) - - maxi = np.amax(frame) - # print() - # print('frame max = ' + str(maxi)) - # print('frame min = ' + str(np.amin(frame))) - if maxi >= 255: - print('Saturation detected') - - plt.figure - plt.imshow(frame)#, cmap='gray', vmin=mini, vmax=maxi) - plt.colorbar(); - plt.show() - - \ No newline at end of file diff --git a/spas/metadata_SPC1D.py b/spas/metadata_SPC1D.py deleted file mode 100644 index 344b1dd..0000000 --- a/spas/metadata_SPC1D.py +++ /dev/null @@ -1,1118 +0,0 @@ -# -*- coding: utf-8 -*- -__author__ = 'Guilherme Beneti Martins' - -"""Metadata classes and utilities. - -Metadata classes to keep and save all relevant data during an acquisition. -Utility functions to recreate objects from JSON files, save them to JSON and to -improve readability. -""" - -import json -from datetime import datetime -from enum import IntEnum -from dataclasses import dataclass, InitVar, field -from typing import Optional, Union, List, Tuple, Optional -from pathlib import Path -import os -from dataclasses_json import dataclass_json -import numpy as np -import ctypes as ct -import pickle -##### DLL for the DMD -try: - import ALP4 -except: # in the cas the DLL of the DMD is not installed - class ALP4: - pass - setattr(ALP4, 'ALP4', None) - print('DLL of the DMD not installed') -##### DLL for the spectrometer Avantes -try: - from msl.equipment.resources.avantes import MeasConfigType -except: # in the cas the DLL of the spectrometer is not installed - class MeasConfigType: - pass - MeasConfigType = None - print('DLL of the spectrometer not installed !!!') - -##### DLL for the camera -try: - from pyueye import ueye - dll_pyueye_installed = 1 -except: - dll_pyueye_installed = 0 - print('DLL of the cam not installed !!') - -class DMDTypes(IntEnum): - """Enumeration of DMD types and respective codes.""" - ALP_DMDTYPE_XGA = 1 - ALP_DMDTYPE_SXGA_PLUS = 2 - ALP_DMDTYPE_1080P_095A = 3 - ALP_DMDTYPE_XGA_07A = 4 - ALP_DMDTYPE_XGA_055A = 5 - ALP_DMDTYPE_XGA_055X = 6 - ALP_DMDTYPE_WUXGA_096A = 7 - ALP_DMDTYPE_WQXGA_400MHZ_090A = 8 - ALP_DMDTYPE_WQXGA_480MHZ_090A = 9 - ALP_DMDTYPE_WXGA_S450 = 12 - ALP_DMDTYPE_DISCONNECT = 255 - - -@dataclass_json -@dataclass -class MetaData: - """ Class containing overall acquisition parameters and description. - - Metadata concerning the experiment, paths, file inputs and file outputs. - This class is adapted to be reconstructed from a JSON file. - - Attributes: - output_directory (Union[str, Path], optional): - Directory where multiple related acquisitions will be stored. - pattern_order_source (Union[str, Path], optional): - File where the order of patterns to be sent to DMD is specified. It - can be a text file containing a list of pattern indeces or a numpy - file containing a covariance matrix from which the pattern order is - calculated. - pattern_source (Union[str, Path], optional): - Pattern source folder. - pattern_prefix (str): - Prefix used in pattern naming. - experiment_name (str): - Prefix of all files related to a single acquisition. Files will - appear with the following string pattern: - experiment_name + '_' + filename. - light_source (str): - Light source used to illuminate an object during acquisition. - object (str): - Object imaged during acquisition. - filter (str): - Light filter used. - description (str): - Acqusition experiment description. - date (str, optional): - Acquisition date. Automatically set when object is created. Default - is None. - time (str, optional): - Time when metadata object is created. Set automatically by - __post_init__(). Default is None. - class_description (str): - Class description used to improve redability when dumped to JSON - file. Default is 'Metadata'. - """ - - pattern_prefix: str - experiment_name: str - - light_source: str - object: str - filter: str - description: str - - output_directory: Union[str, Path] - pattern_order_source: Union[str, Path] - pattern_source: Union[str, Path] - - date: Optional[str] = None - time: Optional[str] = None - - class_description: str = 'Metadata' - - def __post_init__(self): - """Sets time and date of object cretion and deals with paths""" - - today = datetime.today() - self.date = '--/--/----' #today.strftime('%d/%m/%Y') - self.time = today.strftime('%I:%M:%S %p') - - # If parameter is str, turn it into Path - if isinstance(self.output_directory, str): - self.output_directory = Path(self.output_directory) - - # If parameter is Path or was turned into a Path, resolve it and get the - # str format. - if issubclass(self.output_directory.__class__, Path): - self.output_directory = str(self.output_directory.resolve()) - - - if isinstance(self.pattern_order_source, str): - self.pattern_order_source = Path(self.pattern_order_source) - - if issubclass(self.pattern_order_source.__class__, Path): - self.pattern_order_source = str( - self.pattern_order_source.resolve()) - - - if isinstance(self.pattern_source, str): - self.pattern_source = Path(self.pattern_source) - - if issubclass(self.pattern_source.__class__, Path): - self.pattern_source = str(self.pattern_source.resolve()) - - -@dataclass_json -@dataclass -class CAM: - """Class containing IDS camera configurations. - - Further information: https://en.ids-imaging.com/manuals/ids-software-suite/ueye-manual/4.95/en/c_programmierung.html. - - Attributes: - hCam (ueye.c_uint): Handle of the camera. - sInfo (ueye.SENSORINFO):sensor information : [SensorID [c_ushort] = 566; - strSensorName [c_char_Array_32] = b'UI388xCP-M'; - nColorMode [c_char] = b'\x01'; - nMaxWidth [c_uint] = 3088; - nMaxHeight [c_uint] = 2076; - bMasterGain [c_int] = 1; - bRGain [c_int] = 0; - bGGain [c_int] = 0; - bBGain [c_int] = 0; - bGlobShutter [c_int] = 0; - wPixelSize [c_ushort] = 240; - nUpperLeftBayerPixel [c_char] = b'\x00'; - Reserved]. - cInfo (ueye.BOARDINFO):Camera information: [SerNo [c_char_Array_12] = b'4103219888'; - ID [c_char_Array_20] = b'IDS GmbH'; - Version [c_char_Array_10] = b''; - Date [c_char_Array_12] = b'30.11.2017'; - Select [c_ubyte] = 1; - Type [c_ubyte] = 100; - Reserved [c_char_Array_8] = b'';] - nBitsPerPixel (ueye.c_int): number of bits per pixel (8 for monochrome, 24 for color). - m_nColorMode (ueye.c_int): color mode : Y8/RGB16/RGB24/REG32. - bytes_per_pixel (int): bytes_per_pixel = int(nBitsPerPixel / 8). - rectAOI (ueye.IS_RECT()): rectangle of the Area Of Interest (AOI): s32X [c_int] = 0; - s32Y [c_int] = 0; - s32Width [c_int] = 3088; - s32Height [c_int] = 2076; - pcImageMemory (ueye.c_mem_p()): memory allocation. - MemID (ueye.int()): memory identifier. - pitch (ueye.INT()): ???. - fps (float): set frame per second. - gain (int): Set gain between [0 - 100]. - gainBoost (str): Activate gain boosting ("ON") or deactivate ("OFF"). - gamma (float): Set Gamma between [1 - 2.5] to change the image contrast - exposureTime (float): Set the exposure time between [0.032 - 56.221] - blackLevel (int): Set the black level between [0 - 255] to set an offset in the image. It is adviced to put 5 for noise measurement - camActivated (bool) : need to to know if the camera is ready to acquire (1: yes, 0: No) - pixelClock (int) : the pixel clock, three values possible : [118, 237, 474] (MHz) - bandwidth (float) the bandwidth (in MByte/s) is an approximate value which is calculated based on the pixel clock - Memory (bool) : a boolean to know if the memory inside the camera is busy [1] or free [0] - Exit (int) : if Exit = 2 => excute is_ExitCamera function (disables the hCam camera handle and releases the memory) | if Exit = 0 => allow to init cam, after that, Exit = 1 - vidFormat (str) : save video in the format avi or bin (for binary) - gate_period (int) : a second TTL is sent by the DMD to trigg the camera, and based on the fisrt TTL to trigg the spectrometer. camera trigger period = gate_period*(spectrometer trigger period) - trigger_mode (str) : hard or soft - avi (ueye.int) : A pointer that returns the instance ID which is needed for calling the other uEye AVI functions - punFileID (ueye.c_int) : a pointer in which the instance ID is returned. This ID is needed for calling other functions. - timeout (int) : a time which stop the camera that waiting for a TTL - time_array (List[float]) : the time array saved after each frame received on the camera - int_time_spect (float) : is egal to the integration time of the spectrometer, it is need to know this value because of the rolling shutter of the monochrome IDS camera - black_pattern_num (int) : is number inside the image name of the black pattern (for the hyperspectral arm, or white pattern for the camera arm) to be inserted betweem the Hadamard patterns - insert_patterns (int) : 0 => no insertion / 1=> insert white patterns for the camera - acq_mode (str) : mode of the acquisition => 'video' or 'snapshot' mode - """ - if dll_pyueye_installed: - hCam: Optional[ueye.c_uint] = None - sInfo: Optional[ueye.SENSORINFO] = None - cInfo: Optional[ueye.BOARDINFO] = None - nBitsPerPixel: Optional[ueye.c_int] = None - m_nColorMode: Optional[ueye.c_int] = None - bytes_per_pixel: Optional[int] = None - rectAOI: Optional[ueye.IS_RECT] = None - pcImageMemory: Optional[ueye.c_mem_p] = None - MemID: Optional[ueye.c_int] = None - pitch: Optional[ueye.c_int] = None - fps: Optional[float] = None - gain: Optional[int] = None - gainBoost: Optional[str] = None - gamma: Optional[float] = None - exposureTime: Optional[float] = None - blackLevel: Optional[int] = None - camActivated : Optional[bool] = None - pixelClock : Optional[int] = None - bandwidth : Optional[float] = None - Memory : Optional[bool] = None - Exit : Optional[int] = None - vidFormat : Optional[str] = None - gate_period : Optional[int] = None - trigger_mode : Optional[str] = None - avi : Optional[ueye.int] = None - punFileID : Optional[ueye.c_int] = None - timeout : Optional[int] = None - time_array : Optional[Union[List[float], str]] = field(default=None, repr=False) - int_time_spect : Optional[float] = None - black_pattern_num : Optional[int] = None - insert_patterns : Optional[int] = None - acq_mode : Optional[str] = None - - class_description: str = 'IDS camera configuration' - - def undo_readable_class_CAM(self) -> None: - """Changes the time_array attribute from `str` to `List` of `int`.""" - - def to_float(str_arr): - arr = [] - for s in str_arr: - try: - num = float(s) - arr.append(num) - except ValueError: - pass - return arr - - if self.time_array: - self.time_array = ( - self.time_array.strip('[').strip(']').split(', ')) - self.time_array = to_float(self.time_array) - self.time_array = np.asarray(self.time_array) - - @staticmethod - def readable_class_CAM(cam_params_dict: dict) -> dict: - # pass - """Turns list of time_array into a string. - convert the c_type structure (sInfo, cInfo and rectAOI) into a nested dict - change the bytes type item into str - change the c_types item into their value - """ - - readable_cam_dict = {} - readable_cam_dict_temp = cam_params_dict#camPar.to_dict()# - inc = 0 - for item in readable_cam_dict_temp: - stri = str(type(readable_cam_dict_temp[item])) - # print('----- item : ' + item) - if item == 'sInfo' or item == 'cInfo' or item == 'rectAOI': - readable_cam_dict[item] = dict() - try: - for sub_item in readable_cam_dict_temp[item]._fields_: - new_item = item + '-' + sub_item[0] - try: - att = getattr(readable_cam_dict_temp[item], sub_item[0]).value - except: - att = getattr(readable_cam_dict_temp[item], sub_item[0]) - - if type(att) == bytes: - att = str(att) - - readable_cam_dict[item][sub_item[0]] = att - except: - try: - for sub_item in readable_cam_dict_temp[item]: - # print('----- sub_item : ' + sub_item) - new_item = item + '-' + sub_item - att = readable_cam_dict_temp[item][sub_item] - - if type(att) == bytes: - att = str(att) - - readable_cam_dict[item][sub_item] = att - except: - print('warning, impossible to read the subitem of readable_cam_dict_temp[item]') - - elif stri.find('pyueye') >=0: - try: - readable_cam_dict[item] = readable_cam_dict_temp[item].value - except: - readable_cam_dict[item] = readable_cam_dict_temp[item] - elif item == 'time_array': - readable_cam_dict[item] = str(readable_cam_dict_temp[item]) - else: - readable_cam_dict[item] = readable_cam_dict_temp[item] - - return readable_cam_dict - - -@dataclass_json -@dataclass -class AcquisitionParameters: - """Class containing acquisition specifications and timing results. - - This class is adapted to be reconstructed from a JSON file. - - Attributes: - pattern_compression (float): - Percentage of total available patterns to be present in an - acquisition sequence. - pattern_dimension_x (int): - Length of reconstructed image that defines pattern length. - pattern_dimension_y (int): - Width of reconstructed image that defines pattern width. - zoom (int): - numerical zoom of the patterns - xw_offset (int): - offset of the pattern in the DMD for zoom > 1 in the width (x) direction - yh_offset (int): - offset of the pattern in the DMD for zoom > 1 in the heihgt (y) direction - mask_index (Union[np.ndarray, str], optional): - Array of `int` type corresponding to the index of the mask vector where - the value is egal to 1 - x_mask_coord (Union[np.ndarray, str], optional): - coordinates of the mask in the x direction x[0] and x[1] are the first - and last points respectively - y_mask_coord (Union[np.ndarray, str], optional): - coordinates of the mask in the y direction y[0] and y[1] are the first - and last points respectively - pattern_amount (int, optional): - Quantity of patterns sent to DMD for an acquisition. This value is - calculated by an external function. Default in None. - acquired_spectra (int, optional): - Amount of spectra actually read from the spectrometer. This value is - calculated by an external function. Default in None. - mean_callback_acquisition_time_ms (float, optional): - Mean time between 2 callback executions during an acquisition. This - value is calculated by an external function. Default in None. - total_callback_acquisition_time_s (float, optional): - Total time of callback executions during an acquisition. This value - is calculated by an external function. Default in None. - mean_spectrometer_acquisition_time_ms (float, optional): - Mean time between 2 spectrometer measurements during an acquisition - based on its own internal clock. This value is calculated by an - external function. Default in None. - total_spectrometer_acquisition_time_s (float, optional): - Total time of spectrometer measurements during an acquisition - based on its own internal clock. This value is calculated by an - external function. Default in None. - saturation_detected (bool, optional): - Boolean incating if saturation was detected during acquisition. - Default is None. - patterns (Union[List[int],str], optional) = None - List `int` or `str` containing all patterns sent to the DMD for an - acquisition sequence. This value is set by an external function and - its type can be modified by multiple functions during object - creation, manipulation, when dumping to a JSON file or - when reconstructing an AcquisitionParameters object from a JSON - file. It is intended to be of type List[int] most of the execution - List[int]time. Default is None. - wavelengths (Union[np.ndarray, str], optional): - Array of `float` type corresponding to the wavelengths associated - with spectrometer's start and stop pixels. - timestamps (Union[List[float], str], optional): - List of `float` type elapsed time between each measurement - made by the spectrometer based on its internal clock. Units in - milliseconds. Default is None. - measurement_time (Union[List[float], str], optional): - List of `float` type elapsed times between each callback. Units in - milliseconds. Default is None. - class_description (str): - Class description used to improve redability when dumped to JSON - file. Default is 'Acquisition parameters'. - """ - - pattern_compression: float - pattern_dimension_x: int - pattern_dimension_y: int - zoom: Optional[int] = field(default=None) - xw_offset: Optional[int] = field(default=None) - yh_offset: Optional[int] = field(default=None) - mask_index: Optional[Union[np.ndarray, str]] = field(default=None, - repr=False) - x_mask_coord: Optional[Union[np.ndarray, str]] = field(default=None, - repr=False) - y_mask_coord: Optional[Union[np.ndarray, str]] = field(default=None, - repr=False) - - pattern_amount: Optional[int] = None - acquired_spectra: Optional[int] = None - - mean_callback_acquisition_time_ms: Optional[float] = None - total_callback_acquisition_time_s: Optional[float] = None - mean_spectrometer_acquisition_time_ms: Optional[float] = None - total_spectrometer_acquisition_time_s: Optional[float] = None - - saturation_detected: Optional[bool] = None - - patterns: Optional[Union[List[int], str]] = field(default=None, repr=False) - patterns_wp: Optional[Union[List[int], str]] = field(default=None, repr=False) - wavelengths: Optional[Union[np.ndarray, str]] = field(default=None, - repr=False) - timestamps: Optional[Union[List[float], str]] = field(default=None, - repr=False) - measurement_time: Optional[Union[List[float], str]] = field(default=None, - repr=False) - - class_description: str = 'Acquisition parameters' - - - def undo_readable_pattern_order(self) -> None: - """Changes the patterns attribute from `str` to `List` of `int`. - - When reconstructing an AcquisitionParameters object from a JSON file, - this method turns the patterns, wavelengths, timestamps and - measurement_time attributes from a string to a list of integers - containing the pattern indices used in that acquisition. - """ - - def to_float(str_arr): - arr = [] - for s in str_arr: - try: - num = float(s) - arr.append(num) - except ValueError: - pass - return arr - - self.patterns = self.patterns.strip('[').strip(']').split(', ') - self.patterns = [int(s) for s in self.patterns if s.isdigit()] - try: - self.patterns_wp = self.patterns_wp.text.strip('[').strip(']').split(', ') - self.patterns_wp = [int(s) for s in self.patterns_wp if s.isdigit()] - except: - print('patterns_wp has no attribute ''strip''') - - if self.wavelengths: - self.wavelengths = ( - self.wavelengths.strip('[').strip(']').split(', ')) - self.wavelengths = to_float(self.wavelengths) - self.wavelengths = np.asarray(self.wavelengths) - else: - print('wavelenghts not present in metadata.' - ' Reading data in legacy mode.') - - if self.timestamps: - self.timestamps = self.timestamps.strip('[').strip(']').split(', ') - self.timestamps = to_float(self.timestamps) - else: - print('timestamps not present in metadata.' - ' Reading data in legacy mode.') - - if self.measurement_time: - self.measurement_time = ( - self.measurement_time.strip('[').strip(']').split(', ')) - self.measurement_time = to_float(self.measurement_time) - else: - print('measurement_time not present in metadata.' - ' Reading data in legacy mode.') - - if self.mask_index: - self.mask_index = ( - self.mask_index.strip('[').strip(']').split(', ')) - self.mask_index = to_float(self.mask_index) - self.mask_index = np.asarray(self.mask_index) - else: - print('mask_index not present in metadata.' - ' Reading data in legacy mode.') - - if self.x_mask_coord: - self.x_mask_coord = ( - self.x_mask_coord.strip('[').strip(']').split(', ')) - self.x_mask_coord = to_float(self.x_mask_coord) - self.x_mask_coord = np.asarray(self.x_mask_coord) - else: - print('x_mask_coord not present in metadata.' - ' Reading data in legacy mode.') - - if self.y_mask_coord: - self.y_mask_coord = ( - self.y_mask_coord.strip('[').strip(']').split(', ')) - self.y_mask_coord = to_float(self.y_mask_coord) - self.y_mask_coord = np.asarray(self.y_mask_coord) - else: - print('y_mask_coord not present in metadata.' - ' Reading data in legacy mode.') - - @staticmethod - def readable_pattern_order(acquisition_params_dict: dict) -> dict: - """Turns list of patterns into a string. - - Turns the list of pattern attributes from an AcquisitionParameters - object (turned into a dictionary) into a string that will improve - readability once all metadata is dumped into a JSON file. - This function must be called before dumping. - - Args: - acquisition_params_dict (dict): Dictionary obtained from converting - an AcquisitionParameters object. - - Returns: - [dict]: Modified dictionary with acquisition parameters metadata. - """ - - def _hard_coded_conversion(data): - s = '[' - for value in data: - s += f'{value:.4f}, ' - s = s[:-2] - s += ']' - - return s - - readable_dict = acquisition_params_dict - readable_dict['patterns'] = str(readable_dict['patterns']) - readable_dict['patterns_wp'] = str(readable_dict['patterns_wp']) - - readable_dict['wavelengths'] = _hard_coded_conversion( - readable_dict['wavelengths']) - - readable_dict['timestamps'] = _hard_coded_conversion( - readable_dict['timestamps']) - - readable_dict['measurement_time'] = _hard_coded_conversion( - readable_dict['measurement_time']) - - readable_dict['mask_index'] = _hard_coded_conversion( - readable_dict['mask_index']) - - readable_dict['x_mask_coord'] = _hard_coded_conversion( - readable_dict['x_mask_coord']) - - readable_dict['y_mask_coord'] = _hard_coded_conversion( - readable_dict['y_mask_coord']) - - return readable_dict - - - def update_timings(self, timestamps: np.ndarray, - measurement_time: np.ndarray): - """Updates acquisition timings. - - Args: - timestamps (ndarray): - Array of `float` type elapsed time between each measurement made - by the spectrometer based on its internal clock. Units in - milliseconds. - measurement_time (ndarray): - Array of `float` type elapsed times between each callback. Units - in milliseconds. - """ - self.mean_callback_acquisition_time_ms = np.mean(measurement_time) - self.total_callback_acquisition_time_s = np.sum(measurement_time) / 1000 - self.mean_spectrometer_acquisition_time_ms = np.mean( - timestamps, dtype=float) - self.total_spectrometer_acquisition_time_s = np.sum(timestamps) / 1000 - - self.timestamps = timestamps - self.measurement_time = measurement_time - - - -@dataclass_json -@dataclass -class SpectrometerParameters: - """Class containing spectrometer configurations. - - Further information: AvaSpec Library Manual (Version 9.10.2.0). - - Attributes: - high_resolution (bool): - True if 16-bit AD Converter is used. False if 14-bit ADC is used. - initial_available_pixels (int): - Number of pixels available in spectrometer. - detector (str): - Name of the light detector. - firmware_version (str, optional): - Spectrometer firmware version. - dll_version (str, optional): - Spectrometer dll version. - fpga_version (str, optional): - Internal FPGA version. - integration_delay_ns (int, optional): - Parameter used to start the integration time not immediately after - the measurement request (or on an external hardware trigger), but - after a specified delay. Unit is based on internal FPGA clock cycle. - integration_time_ms (float, optional): - Spectrometer exposure time during one scan in miliseconds. - start_pixel (int, optional): - Initial pixel data received from spectrometer. - stop_pixel (int, optional): - Last pixel data received from spectrometer. - averages (int, optional): - Number of averages in a single measurement. - dark_correction_enable (bool, optional): - Enable dynamic dark current correction. - dark_correction_forget_percentage (int, optional): - Percentage of the new dark value pixels that has to be used. e.g., - a percentage of 100 means only new dark values are used. A - percentage of 10 means that 10 percent of the new dark values is - used and 90 percent of the old values is used for drift correction. - smooth_pixels (int, optional): - Number of neighbor pixels used for smoothing, max. has to be smaller - than half the selected pixel range because both the pixels on the - left and on the right are used. - smooth_model (int, optional): - Smoothing model. Currently a single model is supported in which the - spectral data is averaged over a number of pixels on the detector - array. For example, if the smoothpix parameter is set to 2, the - spectral data for all pixels x(n) on the detector array will be - averaged with their neighbor pixels x(n-2), x(n-1), x(n+1) and - x(n+2). - saturation_detection (bool, optional): - Enable detection of saturation/overexposition in pixels. - trigger_mode (int, optional): - Trigger mode (0 = Software, 1 = Hardware, 2 = Single Scan). - trigger_source (int, optional): - Trigger source (0 = external trigger, 1 = sync input). - trigger_source_type (int, optional): - Trigger source type (0 = edge trigger, 1 = level trigger). - store_to_ram (int, optional): - Define how many scans can be stored in RAM. In DynamicRAM mode, can - be set to 0 to indicate infinite measurements. - configs: InitVar[MeasConfigType]: - Initialization object containing data to create SpectrometerData - object. Unnecessary if reconstructing object from JSON file Defaut - is None. - version_info: InitVar[Tuple[str]]: - Initialization variable used for receiving firmware, dll and FPGA - version data. Unnecessary if reconstructing object from JSON file. - class_description (str): - Class description used to improve redability when dumped to JSON - file. Default is 'Spectrometer parameters'. - """ - - high_resolution: bool - initial_available_pixels: int - detector: str - firmware_version: Optional[str] = None - dll_version: Optional[str] = None - fpga_version: Optional[str] = None - - integration_delay_ns: Optional[int] = None - integration_time_ms: Optional[float] = None - - start_pixel: Optional[int] = None - stop_pixel: Optional[int] = None - averages: Optional[int] = None - - dark_correction_enable: Optional[bool] = None - dark_correction_forget_percentage: Optional[int] = None - - smooth_pixels: Optional[int] = None - smooth_model: Optional[int] = None - - saturation_detection: Optional[bool] = None - - trigger_mode: Optional[int] = None - trigger_source: Optional[int] = None - trigger_source_type: Optional[int] = None - - store_to_ram: Optional[int] = None - - configs: InitVar[MeasConfigType] = None - version_info: InitVar[Tuple[str]] = None - - class_description: str = 'Spectrometer parameters' - - - def __post_init__(self, configs: Optional[MeasConfigType] = None, - version_info: Optional[Tuple[str, str, str]] = None): - """Post initialization of attributes. - - Receives the data sent to spectrometer and some version data and unwraps - everything to set the majority of SpectrometerParameters's attributes. - During reconstruction from JSON, arguments of type InitVar (configs and - version_info) are set to None and the function does nothing, letting - initialization for the standard __init__ function. - - Args: - configs (MeasConfigType, optional): - Object containing configurations sent to spectrometer. - Defaults to None. - version_info (Tuple[str, str, str], optional): - Tuple containing firmware, dll and FPGA version data. Defaults - to None. - """ - if configs is None or version_info is None: - pass - - else: - self.fpga_version, self.firmware_version, self.dll_version = ( - version_info) - - self.integration_delay_ns = configs.m_IntegrationDelay - self.integration_time_ms = configs.m_IntegrationTime - - self.start_pixel = configs.m_StartPixel - self.stop_pixel = configs.m_StopPixel - self.averages = configs.m_NrAverages - - self.dark_correction_enable = configs.m_CorDynDark.m_Enable - self.dark_correction_forget_percentage = ( - configs.m_CorDynDark.m_ForgetPercentage) - - self.smooth_pixels = configs.m_Smoothing.m_SmoothPix - self.smooth_model = configs.m_Smoothing.m_SmoothModel - - self.saturation_detection = configs.m_SaturationDetection - - self.trigger_mode = configs.m_Trigger.m_Mode - self.trigger_source = configs.m_Trigger.m_Source - self.trigger_source_type = configs.m_Trigger.m_SourceType - - self.store_to_ram = configs.m_Control.m_StoreToRam - - -@dataclass_json -@dataclass -class DMDParameters: - """Class containing DMD configurations and status. - - Further information: ALP-4.2 API Description (14/04/2020). - - Attributes: - add_illumination_time_us (int): - Extra time in microseconds to account for the spectrometer's - "dead time". - initial_memory (int): - Initial memory available before sending patterns to DMD. - dark_phase_time_us (int, optional): - Time in microseconds taken by the DMD mirrors to completely tilt. - Minimum time for XGA type DMD is 44 us. - illumination_time_us (int, optional): - Duration of the display of one pattern in a DMD sequence. Units in - microseconds. - picture_time_us (int, optional): - Time between the start of two consecutive pictures (i.e. this - parameter defines the image display rate). Units in microseconds. - synch_pulse_width_us (int, optional): - Duration of DMD's frame synch output pulse. Units in microseconds. - synch_pulse_delay (int, optional): - Time in microseconds between start of the frame synch output pulse - and the start of the pattern display (in master mode). - device_number (int, optional): - Serial number of the ALP device. - ALP_version (int, optional): - Version number of the ALP device. - id (int, optional): - ALP device identifier for a DMD provided by the API. - synch_polarity (str, optional): - Frame synch output signal polarity: 'High' or 'Low.' - trigger_edge (str, optional): - Trigger input signal slope. Can be a 'Falling' or 'Rising' edge. - type (str, optional): - Digital light processing (DLP) chip present in DMD. - usb_connection (bool, optional): - True if USB connection is ok. - ddc_fpga_temperature (float, optional): - Temperature of the DDC FPGA (IC4) at DMD connection. Units in °C. - apps_fpga_temperature (float, optional): - Temperature of the Applications FPGA (IC3) at DMD connection. Units - in °C. - pcb_temperature (float, optional): - Internal temperature of the temperature sensor IC (IC2) at DMD - connection. Units in °C. - display_height (int, optional): - DMD display height in pixels. - display_width (int, optional): - DMD display width in pixels. - patterns (int, optional): - Number of patterns uploaded to DMD. - unused_memory (int, optional): - Memory available after sending patterns to DMD. - bitplanes (int, optional): - Bit depth of the patterns to be displayed. Values supported from 1 - to 8. - DMD (InitVar[ALP4.ALP4], optional): - Initialization DMD object. Can be used to automatically fill most of - the DMDParameters' attributes. Unnecessary if reconstructing object - from JSON file. Defaut is None. - class_description (str): - Class description used to improve redability when dumped to JSON - file. Default is 'DMD parameters'. - """ - - add_illumination_time_us: int - initial_memory: int - - dark_phase_time_us: Optional[int] = None - illumination_time_us: Optional[int] = None - picture_time_us: Optional[int] = None - synch_pulse_width_us: Optional[int] = None - synch_pulse_delay: Optional[int] = None - - device_number: Optional[int] = None - ALP_version: Optional[int] = None - id: Optional[int] = None - - synch_polarity: Optional[str] = None - trigger_edge: Optional[str] = None - - # synch_polarity_OUT1: Optional[str] = None - # synch_period_OUT1: Optional[str] = None - # synch_gate_OUT1: Optional[str] = None - - type: Optional[str] = None - usb_connection: Optional[bool] = None - - ddc_fpga_temperature: Optional[float] = None - apps_fpga_temperature: Optional[float] = None - pcb_temperature: Optional[float] = None - - display_height: Optional[int] = None - display_width: Optional[int] = None - - patterns: Optional[int] = None - patterns_wp: Optional[int] = None - unused_memory: Optional[int] = None - bitplanes: Optional[int] = None - - DMD: InitVar[ALP4.ALP4] = None - - class_description: str = 'DMD parameters' - - - def __post_init__(self, DMD: Optional[ALP4.ALP4] = None): - """ Post initialization of attributes. - - Receives a DMD object and directly asks it for its configurations and - status, then sets the majority of SpectrometerParameters's attributes. - During reconstruction from JSON, DMD is set to None and the function - does nothing, letting initialization for the standard __init__ function. - - Args: - DMD (ALP4.ALP4, optional): - Connected DMD. Defaults to None. - """ - if DMD == None: - pass - - else: - self.device_number = DMD.DevInquire(ALP4.ALP_DEVICE_NUMBER) - self.ALP_version = DMD.DevInquire(ALP4.ALP_VERSION) - self.id = DMD.ALP_ID.value - - polarity = DMD.DevInquire(ALP4.ALP_SYNCH_POLARITY) - if polarity == 2006: - self.synch_polarity = 'High' - elif polarity == 2007: - self.synch_polarity = 'Low' - - edge = DMD.DevInquire(ALP4.ALP_TRIGGER_EDGE) - if edge == 2008: - self.trigger_edge = 'Falling' - elif edge == 2009: - self.trigger_edge = 'Rising' - - # synch_polarity_OUT1 = - - self.type = DMDTypes(DMD.DevInquire(ALP4.ALP_DEV_DMDTYPE)) - - if DMD.DevInquire(ALP4.ALP_USB_CONNECTION) == 0: - self.usb_connection = True - else: - self.usb_connection = False - - # Temperatures converted to °C - self.ddc_fpga_temperature = DMD.DevInquire( - ALP4.ALP_DDC_FPGA_TEMPERATURE)/256 - self.apps_fpga_temperature = DMD.DevInquire( - ALP4.ALP_APPS_FPGA_TEMPERATURE)/256 - self.pcb_temperature = DMD.DevInquire( - ALP4.ALP_PCB_TEMPERATURE)/256 - - self.display_width = DMD.nSizeX - self.display_height = DMD.nSizeY - - - def update_memory(self, unused_memory: int): - - self.unused_memory = unused_memory - self.patterns = self.initial_memory - unused_memory - - - def update_sequence_parameters(self, add_illumination_time, - DMD: Optional[ALP4.ALP4] = None): - - self.bitplanes = DMD.SeqInquire(ALP4.ALP_BITPLANES) - self.illumination_time_us = DMD.SeqInquire(ALP4.ALP_ILLUMINATE_TIME) - self.picture_time_us = DMD.SeqInquire(ALP4.ALP_PICTURE_TIME) - self.dark_phase_time_us = self.picture_time_us - self.illumination_time_us - self.synch_pulse_width_us = DMD.SeqInquire(ALP4.ALP_SYNCH_PULSEWIDTH) - self.synch_pulse_delay = DMD.SeqInquire(ALP4.ALP_SYNCH_DELAY) - self.add_illumination_time_us = add_illumination_time - - - - - -def read_metadata(file_path: str) -> Tuple[MetaData, - AcquisitionParameters, - SpectrometerParameters, - DMDParameters]: - """Reads metadata of a previous acquisition from JSON file. - - Args: - file_path (str): - Name of JSON file containing all metadata. - - Returns: - Tuple[MetaData, AcquisitionParameters, SpectrometerParameters, - DMDParameters]: - saved_metadata (MetaData): - Metadata object read from JSON. - saved_acquisition_params(AcquisitionParameters): - AcquisitionParameters object read from JSON. - saved_spectrometer_params(SpectrometerParameters): - SpectrometerParameters object read from JSON. - saved_dmd_params(DMDParameters): - DMDParameters object read from JSON. - """ - - file = open(file_path,'r') - data = json.load(file) - file.close() - - for object in data: - if object['class_description'] == 'Metadata': - saved_metadata = MetaData.from_dict(object) - if object['class_description'] == 'Acquisition parameters': - saved_acquisition_params = AcquisitionParameters.from_dict(object) - saved_acquisition_params.undo_readable_pattern_order() - if object['class_description'] == 'Spectrometer parameters': - saved_spectrometer_params = SpectrometerParameters.from_dict(object) - if object['class_description'] == 'DMD parameters': - saved_dmd_params = DMDParameters.from_dict(object) - - return (saved_metadata, saved_acquisition_params, - saved_spectrometer_params, saved_dmd_params) - -def read_metadata_2arms(file_path: str) -> Tuple[MetaData, - AcquisitionParameters, - SpectrometerParameters, - DMDParameters, - CAM]: - """Reads metadata of a previous acquisition from JSON file. - - Args: - file_path (str): - Name of JSON file containing all metadata. - - Returns: - Tuple[MetaData, AcquisitionParameters, SpectrometerParameters, - DMDParameters]: - saved_metadata (MetaData): - Metadata object read from JSON. - saved_acquisition_params(AcquisitionParameters): - AcquisitionParameters object read from JSON. - saved_spectrometer_params(SpectrometerParameters): - SpectrometerParameters object read from JSON. - saved_dmd_params(DMDParameters): - DMDParameters object read from JSON. - """ - - file = open(file_path,'r') - data = json.load(file) - file.close() - - for object in data: - if object['class_description'] == 'Metadata': - saved_metadata = MetaData.from_dict(object) - if object['class_description'] == 'Acquisition parameters': - saved_acquisition_params = AcquisitionParameters.from_dict(object) - saved_acquisition_params.undo_readable_pattern_order() - if object['class_description'] == 'Spectrometer parameters': - saved_spectrometer_params = SpectrometerParameters.from_dict(object) - if object['class_description'] == 'DMD parameters': - saved_dmd_params = DMDParameters.from_dict(object) - if object['class_description'] == 'IDS camera configuration': - saved_cam_params = CAM.from_dict(object) - saved_cam_params.undo_readable_class_CAM() - - - return (saved_metadata, saved_acquisition_params, - saved_spectrometer_params, saved_dmd_params, saved_cam_params) - -def save_metadata(metadata: MetaData, - DMD_params: DMDParameters, - spectrometer_params: SpectrometerParameters, - acquisition_parameters: AcquisitionParameters) -> None: - """Saves metadata to JSON file. - - Args: - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. - DMD_params (DMDParameters): - Class containing DMD configurations and status. - spectrometer_params (SpectrometerParameters): - Object containing spectrometer configurations. - acquisition_parameters (AcquisitionParameters): - Object containing acquisition specifications and timing results. - """ - - path = Path(metadata.output_directory) - with open( - path / f'{metadata.experiment_name}_metadata.json', - 'w', encoding='utf8') as output: - - output_params = [ - metadata.to_dict(), - DMD_params.to_dict(), - spectrometer_params.to_dict(), - AcquisitionParameters.readable_pattern_order( - acquisition_parameters.to_dict())] - - json.dump(output_params,output,ensure_ascii=False,indent=4) - -def save_metadata_2arms(metadata: MetaData, - DMD_params: DMDParameters, - spectrometer_params: SpectrometerParameters, - camPar : CAM, - acquisition_parameters: AcquisitionParameters) -> None: - """Saves metadata to JSON file. - - Args: - metadata (MetaData): - Metadata concerning the experiment, paths, file inputs and file - outputs. - DMD_params (DMDParameters): - Class containing DMD configurations and status. - spectrometer_params (SpectrometerParameters): - Object containing spectrometer configurations. - acquisition_parameters (AcquisitionParameters): - Object containing acquisition specifications and timing results. - """ - - path = Path(metadata.output_directory) - with open( - path / f'{metadata.experiment_name}_metadata.json', - 'w', encoding='utf8') as output: - - output_params = [ - metadata.to_dict(), - DMD_params.to_dict(), - spectrometer_params.to_dict(), - CAM.readable_class_CAM(camPar.to_dict()), - AcquisitionParameters.readable_pattern_order(acquisition_parameters.to_dict())] - - json.dump(output_params, output, ensure_ascii=False, indent=4)#, default=convert) - - # with open(path / f'{metadata.experiment_name}_metadata_cam.pkl', 'wb') as f: - # pickle.dump(camPar.__dict__, f) - -@dataclass_json -@dataclass -class func_path: - def __init__(self, data_folder_name, data_name, ask_overwrite=False): - if not os.path.exists('../data/' + data_folder_name): - os.makedirs('../data/' + data_folder_name) - - if not os.path.exists('../data/' + data_folder_name + '/' + data_name): - os.makedirs('../data/' + data_folder_name + '/' + data_name) - aborted = False - elif ask_overwrite == True: - res = input('Acquisition already exists, overwrite it ?[y/n]') - if res == 'n': - aborted = True - else: - aborted = False - else: - aborted = True - - self.aborted = aborted - self.subfolder_path = '../data/' + data_folder_name + '/' + data_name - self.overview_path = self.subfolder_path + '/overview' - if not os.path.exists(self.overview_path): - os.makedirs(self.overview_path) - - self.data_name = data_name - self.data_path = self.subfolder_path + '/' + data_name - self.had_reco_path = self.data_path + '_had_reco.npz' - self.fig_had_reco_path = self.overview_path + '/' + data_name - self.pathIDSsnapshot = Path(self.data_path + '_IDScam_before_acq.npy') - self.pathIDSsnapshot_overview = self.overview_path + '/' + data_name + '_IDScam_before_acq.png' - self.nn_reco_path = self.data_path + '_nn_reco.npz' - self.fig_nn_reco_path = self.overview_path + '/' + data_name - - diff --git a/spas/reconstruction_SPC1D.py b/spas/reconstruction_SPC1D.py deleted file mode 100644 index 291649e..0000000 --- a/spas/reconstruction_SPC1D.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -__author__ = 'Guilherme Beneti Martins' - -import numpy as np -from spas.metadata import AcquisitionParameters - -def reconstruction_hadamard(acquisition_parameters: AcquisitionParameters, - mode: str, - Q: np.ndarray, - M: np.ndarray, - N: int = 64) -> np.ndarray: - """Reconstruct an image acquired with Hadamard patterns. - - Args: - acquisition_parameters (AcquisitionParameters): - Object containing acquisition specifications - mode (str): - Select if reconstruction is based on MATLAB, fht or Walsh generated - patterns. - Q (np.ndarray): - Acquisition matrix used to generate Hadamard patterns. - M (np.ndarray): - Spectral data matrix containing acquired spectra. - N (int, optional): - Reconstructed image dimension. Defaults to 64. - - Returns: - [np.ndarray]: - Reconstructed matrix of size NxN pixels. - """ - - patterns = acquisition_parameters.patterns - - if mode == 'matlab': - ind_opt = patterns[1::2] - if mode == 'fht' or mode == 'walsh': - ind_opt = patterns[0::2] - - ind_opt = np.array(ind_opt)/2 - - if mode == 'matlab': - ind_opt = ind_opt - 1 - - ind_opt = ind_opt.astype('int') - M_breve = M[0::2,:] - M[1::2,:] - M_Had = np.zeros((N*N, M.shape[1])) - M_Had[ind_opt,:] = M_breve - - f = np.matmul(Q,M_Had) # Q.T = Q - frames = np.reshape(f,(N,N,M.shape[1])) - frames /= N*N - - mask_index = acquisition_parameters.mask_index - if len(mask_index) > 0: - x_mask_coord = acquisition_parameters.x_mask_coord - y_mask_coord = acquisition_parameters.y_mask_coord - x_mask_length = x_mask_coord[1] - x_mask_coord[0] - y_mask_length = y_mask_coord[1] - y_mask_coord[0] - - GTnew_vec = np.zeros((x_mask_length*y_mask_length, frames.shape[2])) - GT_vec = frames.reshape(-1, frames.shape[-1]) - - GTnew_vec[mask_index,:] = GT_vec[:len(mask_index),:] - frames = np.reshape(GTnew_vec, (y_mask_length, x_mask_length, frames.shape[2])) - - return frames - - -def reconstruction_raster(M: np.ndarray, N: int = 64) -> np.ndarray: - """Reconstruct an image obtained via Raster scan. - - Args: - M (np.ndarray): - Spectral data matrix containing acquired spectra. - N (int, optional): - Reconstructed image dimension. Defaults to 64. - - Returns: - np.ndarray: - Reconstructed matrix of size NxN pixels. - """ - return np.reshape(M,(N,N,M.shape[1])) \ No newline at end of file From 9b6c06f96e91e041af79e3cf21040db57505c4de Mon Sep 17 00:00:00 2001 From: Varvara Chiliaeva Date: Tue, 18 Feb 2025 17:14:01 +0100 Subject: [PATCH 4/5] import the modules ending with _SPC2D --- spas/acquisition_SPC2D.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spas/acquisition_SPC2D.py b/spas/acquisition_SPC2D.py index 1c39899..dd30952 100644 --- a/spas/acquisition_SPC2D.py +++ b/spas/acquisition_SPC2D.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +Queue# -*- coding: utf-8 -*- __author__ = 'Guilherme Beneti Martins' """Acquisition utility functions. @@ -34,8 +34,8 @@ class ALP4: pass from tqdm import tqdm -from spas.metadata import DMDParameters, MetaData, AcquisitionParameters -from spas.metadata import SpectrometerParameters, save_metadata, CAM, save_metadata_2arms +from spas.metadata_SPC2D import DMDParameters, MetaData, AcquisitionParameters +from spas.metadata_SPC2D import SpectrometerParameters, save_metadata, CAM, save_metadata_2arms from spas.reconstruction_nn import reconstruct_process, plot_recon, ReconstructionParameters # DLL for the IDS CAMERA From 2a6931fac32be965d5963637cce53653745de2b3 Mon Sep 17 00:00:00 2001 From: Varvara Chiliaeva Date: Tue, 18 Feb 2025 17:20:31 +0100 Subject: [PATCH 5/5] SPC1D files created --- spas/acquisition_SPC1D.py | 2878 ++++++++++++++++++++++++++++++++++ spas/metadata_SPC1D.py | 1118 +++++++++++++ spas/reconstruction_SPC1D.py | 82 + spas/reconstruction_SPC2D.py | 2 +- 4 files changed, 4079 insertions(+), 1 deletion(-) create mode 100644 spas/acquisition_SPC1D.py create mode 100644 spas/metadata_SPC1D.py create mode 100644 spas/reconstruction_SPC1D.py diff --git a/spas/acquisition_SPC1D.py b/spas/acquisition_SPC1D.py new file mode 100644 index 0000000..731cc3b --- /dev/null +++ b/spas/acquisition_SPC1D.py @@ -0,0 +1,2878 @@ +Queue# -*- coding: utf-8 -*- +__author__ = 'Guilherme Beneti Martins' + +"""Acquisition utility functions. + + Acquisition module is a generic module that call function in different setup (SPC1D_1arm, SPC1D_2arms, SCP1D and SPIM) + +""" + +import warnings +from time import sleep, perf_counter_ns +from typing import NamedTuple, Tuple, List, Optional +from collections import namedtuple +from pathlib import Path +from multiprocessing import Process, Queue +import shutil +import math + +import numpy as np +from PIL import Image +##### DLL for the DMD +try: + from ALP4 import ALP4, ALP_FIRSTFRAME, ALP_LASTFRAME + from ALP4 import ALP_AVAIL_MEMORY, ALP_DEV_DYN_SYNCH_OUT1_GATE, tAlpDynSynchOutGate + # print('ALP4 is ok in Acquisition file') +except: + class ALP4: + pass +##### DLL for the spectrometer Avantes +try: + from msl.equipment import EquipmentRecord, ConnectionRecord, Backend + from msl.equipment.resources.avantes import MeasureCallback, Avantes +except: + pass + +from tqdm import tqdm +from spas.metadata_SPC1D import DMDParameters, MetaData, AcquisitionParameters +from spas.metadata_SPC1D import SpectrometerParameters, save_metadata, CAM, save_metadata_2arms +from spas.reconstruction_nn import reconstruct_process, plot_recon, ReconstructionParameters + +# DLL for the IDS CAMERA +try: + from pyueye import ueye, ueye_tools +except: + print('ueye DLL not installed') + +from matplotlib import pyplot as plt +from IPython import get_ipython +import ctypes as ct +import logging +import time +import threading + + +def _init_spectrometer() -> Avantes: + """Initialize and connect to an Avantes Spectrometer. + + Returns: + Avantes: Avantes spectrometer. + """ + + dll_path = Path(__file__).parent.parent.joinpath( + 'lib/avaspec3/avaspecx64.dll') + + record = EquipmentRecord( + manufacturer='Avantes', + model='AvaSpec-UCLS2048BCL-EVO-RS', # update for your device + serial='2011126U1', # update for your device + connection=ConnectionRecord( + address=f'SDK::{dll_path}', + backend=Backend.MSL)) + + # Initialize Avantes SDK and establish the connection to the spectrometer + ava = record.connect() + print('Spectrometer connected') + + return ava + + +def _init_DMD(dmd_lib_version: str = '4.2') -> Tuple[ALP4, int]: + """Initialize a DMD and clean its allocated memory from a previous use. + + Args: + dmd_lib_version [str]: the version of the DMD library + + Returns: + Tuple[ALP4, int]: Tuple containing initialized DMD object and DMD + initial available memory. + """ + + # Initializing DMD + stop_init = False + if dmd_lib_version == '4.1': + print('dmd lib version = ' + dmd_lib_version + ' not installed, please, install it at the location : "openspyrit/spas/alpV41"') + stop_init = True + elif dmd_lib_version == '4.2': + dll_path = Path(__file__).parent.parent.joinpath('lib/alpV42').__str__() + DMD = ALP4(version='4.2',libDir=dll_path) + elif dmd_lib_version == '4.3': + dll_path = Path(__file__).parent.parent.joinpath('lib/alpV43').__str__() + DMD = ALP4(version='4.3',libDir=dll_path) + else: + print('unknown version of dmd library') + stop_init = True + + if stop_init == False: + DMD.Initialize(DeviceNum=None) + + #print(f'DMD initial available memory: {DMD.DevInquire(ALP_AVAIL_MEMORY)}') + print('DMD connected') + + return DMD, DMD.DevInquire(ALP_AVAIL_MEMORY) + else: + print('DMD initialisation aborted') + + +def init(dmd_lib_version: str = '4.2') -> Tuple[Avantes, ALP4, int]: + """Call functions to initialize spectrometer and DMD. + + Args: + dmd_lib_version [str]: the version of the DMD library + + Returns: + Tuple[Avantes, ALP4, int]: Tuple containing equipments and DMD initial + available memory: + Avantes: + Connected spectrometer object. + ALP4: + Connected DMD object. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + """ + + DMD, DMD_initial_memory = _init_DMD(dmd_lib_version) + return _init_spectrometer(), DMD, DMD_initial_memory + + +def init_2arms(dmd_lib_version: str = '4.2') -> Tuple[Avantes, ALP4, int]: + """Call functions to initialize spectrometer and DMD. + + Args: + dmd_lib_version [str]: the version of the DMD library + + Returns: + Tuple[Avantes, ALP4, int]: Tuple containing equipments and DMD initial + available memory: + Avantes: + Connected spectrometer object. + ALP4: + Connected DMD object. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + """ + + DMD, DMD_initial_memory = _init_DMD(dmd_lib_version) + camPar = _init_CAM() + return _init_spectrometer(), DMD, DMD_initial_memory, camPar + + +def _calculate_timings(integration_time: float = 1, + integration_delay: int = 0, + add_illumination_time: int = 300, + synch_pulse_delay: int = 0, + dark_phase_time: int = 44, + ) -> Tuple[int, int, int]: + """Calculate spectrometer and DMD dependant timings. + + Args: + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. + Default is 1 ms. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + Default is 0 us. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". Default is 365 us. + synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). Default is + 0 us. + dark_phase_time (int): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. Default is 44 us. + + Returns: + [Tuple]: DMD timings which depend on spectrometer's parameters. + synch_pulse_width: Duration of DMD's frame synch output pulse. Units + in microseconds. + illumination_time: Duration of the display of one pattern in a DMD + sequence. Units in microseconds. + picture_time: Time between the start of two consecutive pictures + (i.e. this parameter defines the image display rate). Units in + microseconds. + """ + + illumination_time = (integration_delay/1000 + integration_time*1000 + + add_illumination_time) + picture_time = illumination_time + dark_phase_time + synch_pulse_width = round(illumination_time/2 + synch_pulse_delay) + illumination_time = round(illumination_time) + picture_time = round(picture_time) + + return synch_pulse_width, illumination_time, picture_time + + +def _setup_spectrometer(ava: Avantes, + integration_time: float, + integration_delay: int, + start_pixel: int, + stop_pixel: int, + ) -> Tuple[SpectrometerParameters, List[float]]: + """Sets configurations in the spectrometer. + + Set all necessary configurations in the spectrometer preparing it for a + measurement. Creates SpectrometerData containing its metadata. Gets the + correct wavelengths depending on the selected pixels to be used. + + Args: + ava (Avantes): + Avantes spectrometer. + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + start_pixel (int): + Initial pixel data received from spectrometer. + stop_pixel (int, optional): + Last pixel data received from spectrometer. If None, then its value + will be determined from the amount of available pixels in the + spectrometer. + Returns: + Tuple[SpectrometerParameters, List[float]]: Metadata and wavelengths. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata. + wavelengths (List): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + """ + + spectrometer_detector = ava.SensType( + ava.get_parameter().m_Detector.m_SensorType).name + + # Get the number of pixels that the spectrometer has + initial_available_pixels = ava.get_num_pixels() + + # print(f'\nThe spectrometer has {initial_available_pixels} pixels') + + # Enable the 16-bit AD converter for High-Resolution + ava.use_high_res_adc(True) + + # Creating configuration block + measconfig = ava.MeasConfigType() + + measconfig.m_StartPixel = start_pixel + + if stop_pixel is None: + measconfig.m_StopPixel = initial_available_pixels - 1 + else: + measconfig.m_StopPixel = stop_pixel + + measconfig.m_IntegrationTime = integration_time + measconfig.m_IntegrationDelay = integration_delay + measconfig.m_NrAverages = 1 + + dark_correction = ava.DarkCorrectionType() + dark_correction.m_Enable = 0 + dark_correction.m_ForgetPercentage = 100 + measconfig.m_CorDynDark = dark_correction + + smoothing = ava.SmoothingType() + smoothing.m_SmoothPix = 0 + smoothing.m_SmoothModel = 0 + measconfig.m_Smoothing = smoothing + + measconfig.m_SaturationDetection = 1 + + trigger = ava.TriggerType() + trigger.m_Mode = 2 + trigger.m_Source = 0 + trigger.m_SourceType = 0 + measconfig.m_Trigger = trigger + + control_settings = ava.ControlSettingsType() + control_settings.m_StrobeControl = 0 + control_settings.m_LaserDelay = 0 + control_settings.m_LaserWidth = 0 + control_settings.LaserWaveLength = 0.00 + control_settings.m_StoreToRam = 0 + measconfig.m_Control = control_settings + + ava.prepare_measure(measconfig) + + spectrometer_params = SpectrometerParameters( + high_resolution=True, + initial_available_pixels=initial_available_pixels, + detector=spectrometer_detector, + configs=measconfig, + version_info=ava.get_version_info()) + + # Get the wavelength corresponding to each pixel + wavelengths = ava.get_lambda()[ + spectrometer_params.start_pixel:spectrometer_params.stop_pixel+1] + + return spectrometer_params, np.asarray(wavelengths) + + +def _setup_DMD(DMD: ALP4, + add_illumination_time: int, + initial_memory: int + ) -> DMDParameters: + """Create DMD metadata. + + Creates basic DMD metadata, but leaves most of its fields empty to be set + later. Sets up the initial free memory present in the DMD. + This function's name is used to create cohesion between spectrometer and DMD + related functions. + + Args: + DMD (ALP4): + Connected DMD object. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + initial_memory (int): + Initial memory available in DMD after initialization. + + Returns: + DMDParameters: + DMD metadata object. + """ + + return DMDParameters( + add_illumination_time_us=add_illumination_time, + initial_memory=initial_memory, + DMD=DMD) + + +def _sequence_limits(DMD: ALP4, + pattern_compression: int, + sequence_lenght: int, + pos_neg: bool = True) -> int: + """Set sequence limits based on a sequence already uploaded to DMD. + + Args: + DMD (ALP4): + Connected DMD object. + pattern_compression (int): + Percentage of total available patterns to be present in an + acquisition sequence. + sequence_lenght (int): + Amount of patterns present in DMD memory. + pos_neg (bool): + Boolean indicating if sequence is formed by positive and negative + patterns. Default is True. + + Returns: + frames (int): + Amount of patterns to be used from a sequence based on the pattern + compression. + """ + + # Choosing beggining of the sequence + # DMD.SeqControl(ALP_BITNUM, 1) + DMD.SeqControl(ALP_FIRSTFRAME, 0) + + # Choosing the end of the sequence + if (round(pattern_compression * sequence_lenght) % 2 == 0) or not (pos_neg): + frames = round(pattern_compression * sequence_lenght) + else: + frames = round(pattern_compression * sequence_lenght) + 1 + + DMD.SeqControl(ALP_LASTFRAME, frames - 1) + + return frames + + +def _update_sequence(DMD: ALP4, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + pattern_source: str, + pattern_prefix: str, + pattern_order: List[int], + bitplanes: int = 1): + """Send new complete pattern sequence to DMD. + + Args: + DMD (ALP4): + Connected DMD object. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + pattern_source (str): + Pattern source folder. + pattern_preffix (str): + Prefix used in pattern naming. + pattern_order (List[int]): + List of the pattern indices in a certain order for upload to DMD. + bitplanes (int, optional): + Pattern bitplanes. Defaults to 1. + """ + + import cv2 + + path_base = Path(pattern_source) + + seqId = DMD.SeqAlloc(nbImg=len(pattern_order), + bitDepth=bitplanes) + + zoom = acquisition_params.zoom + x_offset = acquisition_params.xw_offset + y_offset = acquisition_params.yh_offset + Np = acquisition_params.pattern_dimension_x + + dmd_height = DMD_params.display_height + dmd_width = DMD_params.display_width + len_im = int(dmd_height / zoom) + + t = perf_counter_ns() + + # for adaptative patterns into a ROI + apply_mask = False + mask_index = acquisition_params.mask_index + + if len(mask_index) > 0: + apply_mask = True + Npx = acquisition_params.pattern_dimension_x + Npy = acquisition_params.pattern_dimension_y + mask_element_nbr = len(mask_index) + x_mask_coord = acquisition_params.x_mask_coord + y_mask_coord = acquisition_params.y_mask_coord + x_mask_length = x_mask_coord[1] - x_mask_coord[0] + y_mask_length = y_mask_coord[1] - y_mask_coord[0] + + first_pass = True + for index,pattern_name in enumerate(tqdm(pattern_order, unit=' patterns', total=len(pattern_order))): + # read numpy patterns + path = path_base.joinpath(f'{pattern_prefix}_{pattern_name}.npy') + im = np.load(path) + + patterns = np.zeros((dmd_height, dmd_width), dtype=np.uint8) + + if apply_mask == True: # for adaptative patterns into a ROI + pat_mask_all = np.zeros(y_mask_length*x_mask_length) # initialize a vector of lenght = size of the cropped mask + pat_mask_all[mask_index] = im[:mask_element_nbr] #pat_re_vec[:mask_element_nbr] # put the pattern into the vector + pat_mask_all_mat = np.reshape(pat_mask_all, [y_mask_length, x_mask_length]) # reshape the vector into a matrix of the 2d cropped mask + # resize the matrix to the DMD size + pat_mask_all_mat_DMD = cv2.resize(pat_mask_all_mat, (int(dmd_height*x_mask_length/(Npx*zoom)), int(dmd_height*y_mask_length/(Npy*zoom))), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + first_pass = False + len_im3 = pat_mask_all_mat_DMD.shape + + patterns[y_offset:y_offset+len_im3[0], x_offset:x_offset+len_im3[1]] = pat_mask_all_mat_DMD + else: # send the entire square pattern without the mask + im_mat = np.reshape(im, [Np,Np]) + im_HD = cv2.resize(im_mat, (int(dmd_height/zoom), int(dmd_height/zoom)), interpolation = cv2.INTER_NEAREST) + + if first_pass == True: + len_im = im_HD.shape + first_pass = False + + patterns[y_offset:y_offset+len_im[0], x_offset:x_offset+len_im[1]] = im_HD + + # if pattern_name == 800: + # plt.figure() + # # plt.imshow(pat_c_re) + # # plt.imshow(pat_mask_all_mat) + # # plt.imshow(pat_mask_all_mat_DMD) + # plt.imshow(np.rot90(patterns,2)) + # plt.colorbar() + # plt.title('pattern n°' + str(pattern_name)) + + patterns = patterns.ravel() + + DMD.SeqPut( + imgData=patterns.copy(), + PicOffset=index, + PicLoad=1) + + print(f'\nTime for sending all patterns: ' + f'{(perf_counter_ns() - t)/1e+9} s') + + +def _setup_patterns(DMD: ALP4, + metadata: MetaData, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + cov_path: str = None, + pattern_to_display: str = 'white', + loop: bool = False) -> None: + """Read and send patterns to DMD. + + Reads patterns from a file and sends a percentage of them to the DMD, + considering positve and negative Hadamard patterns, which should be even in + number. + Prints time taken to read all patterns and send the requested ones + to DMD. + Updates available memory in DMD metadata object (DMD_params). + + Args: + DMD (ALP4): + Connected DMD object. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + loop (bool): + is to projet in loop, one or few patterns continously (see AlpProjStartCont + in the doc for more detail). Default is False + """ + + file = np.load(Path(metadata.pattern_order_source)) + pattern_order = file['pattern_order'] + pos_neg = file['pos_neg'] + + if loop == True: + pos_neg = False + if pattern_to_display == 'white': + pattern_order = np.array(pattern_order[0:1], dtype=np.int16) + elif pattern_to_display == 'black': + pattern_order = np.array(pattern_order[1:2], dtype=np.int16) + elif pattern_to_display == 'gray': + index = int(np.where(pattern_order == 1953)[0]) + print(index) + pattern_order = np.array(pattern_order[index:index+1], dtype=np.int16) + + bitplanes = 1 + DMD_params.bitplanes = bitplanes + + if (DMD_params.initial_memory - DMD.DevInquire(ALP_AVAIL_MEMORY) == + len(pattern_order)) and loop == False: + print('Reusing patterns from previous acquisition') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + else: + if (DMD.Seqs): + DMD.FreeSeq() + + _update_sequence(DMD, DMD_params, acquisition_params, metadata.pattern_source, metadata.pattern_prefix, + pattern_order, bitplanes) + print(f'DMD available memory after sequence allocation: ' + f'{DMD.DevInquire(ALP_AVAIL_MEMORY)}') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + acquisition_params.patterns = ( + pattern_order[0:acquisition_params.pattern_amount]) + + # Confirm memory allocated in DMD + DMD_params.update_memory(DMD.DevInquire(ALP_AVAIL_MEMORY)) + + +def _setup_patterns_2arms(DMD: ALP4, + metadata: MetaData, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + camPar: CAM, + cov_path: str = None) -> None: + """Read and send patterns to DMD. + + Reads patterns from a file and sends a percentage of them to the DMD, + considering positve and negative Hadamard patterns, which should be even in + number. + Prints time taken to read all patterns and send the requested ones + to DMD. + Updates available memory in DMD metadata object (DMD_params). + + Args: + DMD (ALP4): + Connected DMD object. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + cov_path (str): + Path to the covariance matrix used for reconstruction. + It must be a .npy (numpy) or .pt (pytorch) file. It is converted to + a torch tensor for reconstruction. + + """ + + file = np.load(Path(metadata.pattern_order_source)) + pattern_order = file['pattern_order'] + pattern_order = pattern_order.astype('int32') + + # copy the black pattern image (png) to the number = -1 + # black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.png' ) + black_pattern_dest_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + '-1.npy' ) + + if black_pattern_dest_path.is_file() == False: + # black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + + # str(camPar.black_pattern_num) + '.png' ) + black_pattern_orig_path = Path( metadata.pattern_source + '/' + metadata.pattern_prefix + '_' + + str(camPar.black_pattern_num) + '.npy' ) + shutil.copyfile(black_pattern_orig_path, black_pattern_dest_path) + + + # add white patterns for the camera + if camPar.insert_patterns == 1: + inc = 0 + while True: + try: + pattern_order[inc] # except error from the end of array to stop the loop + if (inc % camPar.gate_period) == 0:#16) == 0: + pattern_order = np.insert(pattern_order, inc, -1) # double white pattern is required if integration time is shorter than 3.85 ms + if camPar.int_time_spect < 3.85: + pattern_order = np.insert(pattern_order, inc+1, -1) + if camPar.int_time_spect < 1.65: + pattern_order = np.insert(pattern_order, inc+2, -1) + if camPar.int_time_spect < 1: + pattern_order = np.insert(pattern_order, inc+1, -1) + if camPar.int_time_spect <= 0.6: + pattern_order = np.insert(pattern_order, inc+1, -1) + inc = inc + 1 + except: + # print('while loop finished') + break + + # if camPar.int_time_spect < 1.75: # add one pattern at the beginning of the sequence when the integration time of the spectrometer is shorter than 1.75 ms + # print('no interleaving') + # #pattern_order = np.insert(pattern_order, 0, -1) + # # pattern_order = np.insert(pattern_order, 0, -1) + if (len(pattern_order)%2) != 0: # Add one pattern at the end of the sequence if the pattern number is even + pattern_order = np.insert(pattern_order, len(pattern_order), -1) + print('pattern order is odd => a black image is automaticly insert, need to be deleted in the case for tuning the spectrometer') + + pos_neg = file['pos_neg'] + + bitplanes = 1 + DMD_params.bitplanes = bitplanes + + if (DMD_params.initial_memory - DMD.DevInquire(ALP_AVAIL_MEMORY) == + len(pattern_order)): + print('Reusing patterns from previous acquisition') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + else: + if (DMD.Seqs): + DMD.FreeSeq() + + _update_sequence(DMD, DMD_params, acquisition_params, metadata.pattern_source, metadata.pattern_prefix, + pattern_order, bitplanes) + print(f'DMD available memory after sequence allocation: ' + f'{DMD.DevInquire(ALP_AVAIL_MEMORY)}') + acquisition_params.pattern_amount = _sequence_limits( + DMD, + acquisition_params.pattern_compression, + len(pattern_order), + pos_neg=pos_neg) + + acquisition_params.patterns = ( + pattern_order[0:acquisition_params.pattern_amount]) + + acquisition_params.patterns_wp = acquisition_params.patterns + + # Confirm memory allocated in DMD + DMD_params.update_memory(DMD.DevInquire(ALP_AVAIL_MEMORY)) + +def _setup_timings(DMD: ALP4, + DMD_params: DMDParameters, + picture_time: int, + illumination_time: int, + synch_pulse_delay: int, + synch_pulse_width: int, + trigger_in_delay: int, + add_illumination_time: int) -> None: + """Setup pattern sequence timings in DMD. + + Send previously user-defined plus calculated timings to DMD. + Updates DMD metadata with sequence and timing related data. + This function has no default values for timings and lets the burden of + setting them to the setup function. + + Args: + DMD (ALP4): + Connected DMD object. + DMD_params (DMDParameters): + DMD metadata object to be updated with pattern related data and with + memory available after patterns are sent to DMD. + picture_time (int): + Time between the start of two consecutive pictures (i.e. this + parameter defines the image display rate). Units in microseconds. + illumination_time (int): + Duration of the display of one pattern in a DMD sequence. + Units in microseconds. + synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). + synch_pulse_width (int): + Duration of DMD's frame synch output pulse. Units in microseconds. + trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + """ + + DMD.SetTiming(illuminationTime=illumination_time, + pictureTime=picture_time, + synchDelay=synch_pulse_delay, + synchPulseWidth=synch_pulse_width, + triggerInDelay=trigger_in_delay) + + DMD_params.update_sequence_parameters(add_illumination_time, DMD=DMD) + + +def setup(spectrometer: Avantes, + DMD: ALP4, + DMD_initial_memory: int, + metadata: MetaData, + acquisition_params: AcquisitionParameters, + start_pixel: int = 0, + stop_pixel: Optional[int] = None, + integration_time: float = 1, + integration_delay: int = 0, + DMD_output_synch_pulse_delay: int = 0, + add_illumination_time: int = 356, + dark_phase_time: int = 44, + DMD_trigger_in_delay: int = 0, + pattern_to_display: str = 'white', + loop: bool = False + ) -> Tuple[SpectrometerParameters, DMDParameters]: + """Setup everything needed to start an acquisition. + + Sets all parameters for DMD, spectrometer, DMD patterns and DMD timings. + Must be called before every acquisition. + + Args: + spectrometer (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y. + start_pixel (int): + Initial pixel data received from spectrometer. Default is 0. + stop_pixel (int, optional): + Last pixel data received from spectrometer. Default is None if it + should be determined from the amount of available pixels in the + spectrometer. + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. Default + is 1 ms. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + Default is 0 us. + DMD_output_synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). Default is + 0 us. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". Default is 365 us. + dark_phase_time (int): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. Default is 44 us. + DMD_trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). Default is 0 us. + pattern_to_display (string): + display one pattern on the DMD to tune the spectrometer. Default is white + pattern + loop (bool): + is to projet in loop, one or few patterns continuously (see AlpProjStartCont + in the doc for more detail). Default is False + Raises: + ValueError: Sum of dark phase and additional illumination time is lower + than 400 us. + + Returns: + Tuple[SpectrometerParameters, DMDParameters, List]: Tuple containing DMD + and spectrometer relate metadata, as well as wavelengths. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + """ + + if loop == False: + path = Path(metadata.output_directory) + if not path.exists(): + path.mkdir() + + if dark_phase_time + add_illumination_time < 350: + raise ValueError(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' Must be greater than 350 µs.') + + elif dark_phase_time + add_illumination_time < 400: + warnings.warn(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' It is recomended to choose at least 400 µs.') + + synch_pulse_width, illumination_time, picture_time = _calculate_timings( + integration_time, + integration_delay, + add_illumination_time, + DMD_output_synch_pulse_delay, + dark_phase_time) + + spectrometer_params, wavelenghts = _setup_spectrometer( + spectrometer, + integration_time, + integration_delay, + start_pixel, + stop_pixel) + + acquisition_params.wavelengths = np.asarray(wavelenghts, dtype=np.float64) + + DMD_params = _setup_DMD(DMD, add_illumination_time, DMD_initial_memory) + + _setup_patterns(DMD=DMD, metadata=metadata, DMD_params=DMD_params, + acquisition_params=acquisition_params, loop=loop, + pattern_to_display=pattern_to_display) + + _setup_timings(DMD, DMD_params, picture_time, illumination_time, + DMD_output_synch_pulse_delay, synch_pulse_width, + DMD_trigger_in_delay, add_illumination_time) + + return spectrometer_params, DMD_params + + +def change_patterns(DMD: ALP4, + acquisition_params: AcquisitionParameters, + zoom: int = 1, + xw_offset: int = 0, + yh_offset: int = 0, + force_change: bool = False + ): + """ + Delete patterns in the memory of the DMD in the case where the zoom or (x,y) offset change + + DMD (ALP4): + Connected DMD. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + zoom (int): + digital zoom. Deafult is x1. + xw_offset (int): + offset int he width direction of the patterns for zoom > 1. + Default is 0. + yh_offset (int): + offset int he height direction of the patterns for zoom > 1. + Default is 0. + force_change (bool): + to force the changement of the pattern sequence. Default is False. + """ + + if acquisition_params.zoom != zoom or acquisition_params.xw_offset != xw_offset or acquisition_params.yh_offset != yh_offset or force_change == True: + if (DMD.Seqs): + DMD.FreeSeq() + + +def setup_2arms(spectrometer: Avantes, + DMD: ALP4, + camPar: CAM, + DMD_initial_memory: int, + metadata: MetaData, + acquisition_params: AcquisitionParameters, + start_pixel: int = 0, + stop_pixel: Optional[int] = None, + integration_time: float = 1, + integration_delay: int = 0, + DMD_output_synch_pulse_delay: int = 0, + add_illumination_time: int = 356, + dark_phase_time: int = 44, + DMD_trigger_in_delay: int = 0 + ) -> Tuple[SpectrometerParameters, DMDParameters]: + """Setup everything needed to start an acquisition. + + Sets all parameters for DMD, spectrometer, DMD patterns and DMD timings. + Must be called before every acquisition. + + Args: + spectrometer (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y, + zoom, x and y offest of patterns displayed on the DMD. + start_pixel (int): + Initial pixel data received from spectrometer. Default is 0. + stop_pixel (int, optional): + Last pixel data received from spectrometer. Default is None if it + should be determined from the amount of available pixels in the + spectrometer. + integration_time (float): + Spectrometer exposure time during one scan in miliseconds. Default + is 1 ms. + integration_delay (int): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + Default is 0 us. + DMD_output_synch_pulse_delay (int): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). Default is + 0 us. + add_illumination_time (int): + Extra time in microseconds to account for the spectrometer's + "dead time". Default is 365 us. + dark_phase_time (int): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. Default is 44 us. + DMD_trigger_in_delay (int): + Time in microseconds between the incoming trigger edge and the start + of the pattern display on DMD (slave mode). Default is 0 us. + + Raises: + ValueError: Sum of dark phase and additional illumination time is lower + than 400 us. + + Returns: + Tuple[SpectrometerParameters, DMDParameters, List]: Tuple containing DMD + and spectrometer relate metadata, as well as wavelengths. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + """ + + path = Path(metadata.output_directory) + if not path.exists(): + path.mkdir() + + if dark_phase_time + add_illumination_time < 350: + raise ValueError(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' Must be greater than 350 µs.') + + elif dark_phase_time + add_illumination_time < 400: + warnings.warn(f'Sum of dark phase and additional illumination time ' + f'is {dark_phase_time + add_illumination_time}.' + f' It is recomended to choose at least 400 µs.') + + synch_pulse_width, illumination_time, picture_time = _calculate_timings( + integration_time, + integration_delay, + add_illumination_time, + DMD_output_synch_pulse_delay, + dark_phase_time) + + spectrometer_params, wavelenghts = _setup_spectrometer( + spectrometer, + integration_time, + integration_delay, + start_pixel, + stop_pixel) + + if camPar.gate_period > 16: + gate_period = 16 + print('Warning, gate period is ' + str(camPar.gate_period) + ' > than the max: 16.') + print('Try to increase the FPS of the camera, or the integration time of the spectrometer.') + print('Check the Pixel clock which must be = 474 MHz') + print('Otherwise some frames will be lost.') + elif camPar.gate_period <1: + print('Warning, gate period is ' + str(camPar.gate_period) + ' < than the min: 1.') + gate_period = 1 + else: + gate_period = camPar.gate_period + + camPar.gate_period = gate_period + Gate = tAlpDynSynchOutGate() + Gate.byref[0] = ct.c_ubyte(gate_period) # Period [1 to 16] (it is a multiple of the trig period which go to the spectro) + Gate.byref[1] = ct.c_ubyte(1) # Polarity => 0: active pulse is low, 1: high + Gate.byref[2] = ct.c_ubyte(1) # Gate1 ok to send TTL + Gate.byref[3] = ct.c_ubyte(0) # Gate2 do not send TTL + Gate.byref[4] = ct.c_ubyte(0) # Gate3 do not send TTL + DMD.DevControlEx(ALP_DEV_DYN_SYNCH_OUT1_GATE, Gate) + camPar.gate_period = gate_period + camPar.int_time_spect = integration_time + + acquisition_params.wavelengths = np.asarray(wavelenghts, dtype=np.float64) + + DMD_params = _setup_DMD(DMD, add_illumination_time, DMD_initial_memory) + + _setup_patterns_2arms(DMD=DMD, metadata=metadata, DMD_params=DMD_params, + acquisition_params=acquisition_params, camPar=camPar) + + _setup_timings(DMD, DMD_params, picture_time, illumination_time, + DMD_output_synch_pulse_delay, synch_pulse_width, + DMD_trigger_in_delay, add_illumination_time) + + return spectrometer_params, DMD_params, camPar + + +def _calculate_elapsed_time(start_measurement_time: int, + measurement_time: np.ndarray, + timestamps: List[int], + ) -> Tuple[np.ndarray, np.ndarray]: + """Calculate acquisition timings. + + Calculates elapsed time between each callback measurement taking into + account the moment when the DMD started running a sequence. + Calculates elapsed time between each spectrum acquired by the spectrometer + based on the spectrometer's internal clock. + + Args: + start_measurement_time (int): + Time in nanoseconds when DMD is set to start running a sequence. + measurement_time (np.ndarray): + 1D array with `int` type timings in nanoseconds when each callbacks + starts. + timestamps (List[int]): + 1D array with measurement timestamps from spectrometer. + Timestamps count ticks for the last pixel of the spectrum was + received by the spectrometer microcontroller. Ticks are in 10 + microsecond units since the spectrometer started. + + Returns: + Tuple[np.ndarray, np.ndarray]: Tuple with measurement timings. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + """ + + measurement_time = np.concatenate( + (start_measurement_time,measurement_time),axis=None) + + measurement_time = np.diff(measurement_time)/1e+6 # In ms + timestamps = np.diff(timestamps)/100 # In ms + + return measurement_time, timestamps + + +def _save_acquisition(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + acquisition_parameters: AcquisitionParameters, + spectral_data: np.ndarray) -> None: + """Save all acquisition data and metadata. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + acquisition_parameters (AcquisitionParameters): + Acquisition related metadata object. + spectral_data (ndarray): + 1D array with `float` type spectrometer measurements. Array size + depends on start and stop pixels previously set to the spectrometer. + """ + + # Saving collected data and timings + path = Path(metadata.output_directory) + path = path / f'{metadata.experiment_name}_spectraldata.npz' + np.savez_compressed(path, spectral_data=spectral_data) + + # 'save_metadata' function is commented because the 'save_metadata_2arms' function is executed after the 'acquire' function in the "main_seq_2arms.py" prog + # # Saving metadata + # save_metadata(metadata, + # DMD_params, + # spectrometer_params, + # acquisition_parameters) + +def _save_acquisition_2arms(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + camPar: CAM, + acquisition_parameters: AcquisitionParameters, + spectral_data: np.ndarray) -> None: + """Save all acquisition data and metadata. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + camPar (CAM): + Metadata object of the IDS monochrome camera + acquisition_parameters (AcquisitionParameters): + Acquisition related metadata object. + spectral_data (ndarray): + 1D array with `float` type spectrometer measurements. Array size + depends on start and stop pixels previously set to the spectrometer. + """ + + # Saving collected data and timings + path = Path(metadata.output_directory) + path = path / f'{metadata.experiment_name}_spectraldata.npz' + np.savez_compressed(path, spectral_data=spectral_data) + + # Saving metadata + save_metadata_2arms(metadata, + DMD_params, + spectrometer_params, + camPar, + acquisition_parameters) + + +def _acquire_raw(ava: Avantes, + DMD: ALP4, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + loop: bool = False + ) -> NamedTuple: + """Raw data acquisition. + + Setups a callback function to receive messages from spectrometer whenever a + measurement is ready to be read. Reads a measurement via a callback. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + loop (bool): + if True, projet continuously the pattern, see the AlpProjStartCont function + if False, projet one time the seq of the patterns, see the AlpProjStart function (Default) + + Returns: + NamedTuple: NamedTuple containig spectral data and measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + spectrum_index (int): + Index of the last acquired spectrum. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + start_measurement_time (float): + Time when acquisition started. + saturation_detected (bool): + Boolean incating if saturation was detected during acquisition. + """ + def register_callback(measurement_time, timestamps, + spectral_data, ava): + + def measurement_callback(handle, info): # If we want to reconstruct during callback; can use it in here. Add function as parameter. + nonlocal spectrum_index + nonlocal saturation_detected + + measurement_time[spectrum_index] = perf_counter_ns() + + if info.contents.value >= 0: + timestamp,spectrum = ava.get_data() + spectral_data[spectrum_index,:] = ( + np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + if np.any(ava.get_saturated_pixels() > 0): + saturation_detected = True + + timestamps[spectrum_index] = np.ctypeslib.as_array(timestamp) + + else: # Set values to zero if an error occured + spectral_data[spectrum_index,:] = 0 + timestamps[spectrum_index] = 0 + + spectrum_index += 1 + + return measurement_callback + + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + + measurement_time = np.zeros((acquisition_params.pattern_amount)) + timestamps = np.zeros((acquisition_params.pattern_amount),dtype=np.uint32) + spectral_data = np.zeros( + (acquisition_params.pattern_amount,pixel_amount),dtype=np.float64) + + # Boolean to indicate if saturation was detected during acquisition + saturation_detected = False + + spectrum_index = 0 # Accessed as nonlocal variable inside the callback + + if loop == False: + #spectro.register_callback(-2,acquisition_params.pattern_amount,pixel_amount) + callback = register_callback(measurement_time, timestamps, + spectral_data, ava) + measurement_callback = MeasureCallback(callback) + ava.measure_callback(-2, measurement_callback) + else: + ava.measure(-1) + + + DMD.Run(loop=loop) # if loop=False : Run the whole sequence only once, if loop=True : Run continuously one pattern + start_measurement_time = perf_counter_ns() + + if loop == False: + while(True): + if(spectrum_index >= acquisition_params.pattern_amount) and loop == False: + break + elif((perf_counter_ns() - start_measurement_time) / 1e+6 > + (2 * acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+3)) and loop == False: + print('Stopping measurement. One of the equipments may be blocked ' + 'or disconnected.') + break + else: + sleep(acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+6 / 10) + DMD.Halt() + else: + sleep(0.1) + + timestamp, spectrum = ava.get_data() + spectral_data_1 = (np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + get_ipython().run_line_magic('matplotlib', 'qt') + plt.ion() # create GUI + figure, ax = plt.subplots(figsize=(10, 8)) + line1, = ax.plot(acquisition_params.wavelengths, spectral_data_1) + + plt.title("Tune the Spectrometer", fontsize=20) + plt.xlabel("Lambda (nm)") + plt.ylabel("counts") + plt.xticks(fontsize=14) + plt.yticks(fontsize=14) + plt.grid() + printed = False + while(True): + try: + timestamp, spectrum = ava.get_data() + spectral_data_1 = (np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + line1.set_xdata(acquisition_params.wavelengths) + line1.set_ydata(spectral_data_1) # updating data values + + figure.canvas.draw() # drawing updated values + figure.canvas.flush_events() # flush prior plot + + if not printed: + print('Press "Ctrl + c" to exit') + if np.amax(spectral_data_1) >= 65535: + print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') + printed = True + + except KeyboardInterrupt: + if (DMD.Seqs): + DMD.Halt() + DMD.FreeSeq() + plt.close() + get_ipython().run_line_magic('matplotlib', 'inline') + break + + ava.stop_measure() + + AcquisitionResult = namedtuple('AcquisitionResult', [ + 'spectral_data', + 'spectrum_index', + 'timestamps', + 'measurement_time', + 'start_measurement_time', + 'saturation_detected']) + + return AcquisitionResult(spectral_data, + spectrum_index, + timestamps, + measurement_time, + start_measurement_time, + saturation_detected) + + +def acquire(ava: Avantes, + DMD: ALP4, + metadata: MetaData, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + repetitions: int = 1, + verbose: bool = False, + reconstruct: bool = False, + reconstruction_params: ReconstructionParameters = None + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Perform a complete acquisition. + + Performs single or multiple acquisitions using the same setup configurations + previously chosen. + Finnaly saves all acqusition related data and metadata. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + wavelengths (List[float]): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + repetitions (int): + Number of times the acquisition will be repeated with the same + configurations. Default is 1, a single acquisition. + verbose (bool): + Chooses if data concerning each acquisition should be printed to + user. If False, only overall data regarding all repetitions is + printed. Default is False. + reconstruct (bool): + If True, will perform reconstruction alongside acquisition using + multiprocessing. + reconstruction_params (ReconstructionParameters): + Object containing parameters of the neural network to be loaded for + reconstruction. + + Returns: + Tuple[ndarray, ndarray, ndarray]: Tuple containig spectral data and + measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + """ + + loop = False # if true, is to projet continuously a unique pattern to tune the spectrometer + + if reconstruct == True: + print('Creating reconstruction processes') + + # Creating a Queue for sending spectral data to reconstruction process + queue_to_recon = Queue() + + # Creating a Queue for sending reconstructed images to plot + queue_reconstructed = Queue() + + sleep_time = (acquisition_params.pattern_amount * + DMD_params.picture_time_us/1e+6) + + # Creating reconstruction process + recon_process = Process(target=reconstruct_process, + args=(reconstruction_params.model, + reconstruction_params.device, + queue_to_recon, + queue_reconstructed, + reconstruction_params.batches, + reconstruction_params.noise, + sleep_time)) + + # Creating plot process + plot_process = Process(target=plot_recon, + args=(queue_reconstructed, sleep_time)) + + # Starting processes + recon_process.start() + plot_process.start() + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + measurement_time = np.zeros( + (acquisition_params.pattern_amount * repetitions)) + timestamps = np.zeros( + ((acquisition_params.pattern_amount - 1) * repetitions), + dtype=np.float64) + spectral_data = np.zeros( + (acquisition_params.pattern_amount * repetitions,pixel_amount), + dtype=np.float64) + + acquisition_params.acquired_spectra = 0 + print() + + for repetition in range(repetitions): + if verbose: + print(f"Acquisition {repetition}") + + AcquisitionResults = _acquire_raw(ava, DMD, spectrometer_params, + DMD_params, acquisition_params, loop) + + (data, spectrum_index, timestamp, time, + start_measurement_time, saturation_detected) = AcquisitionResults + + print('Acquisition number : ' + str(repetition) + ' finished') + + if reconstruct == True: + queue_to_recon.put(data.T) + print('Data sent') + + time, timestamp = _calculate_elapsed_time( + start_measurement_time, time, timestamp) + + begin = repetition * acquisition_params.pattern_amount + end = (repetition + 1) * acquisition_params.pattern_amount + spectral_data[begin:end] = data + measurement_time[begin:end] = time + + begin = repetition * (acquisition_params.pattern_amount - 1) + end = (repetition + 1) * (acquisition_params.pattern_amount - 1) + timestamps[begin:end] = timestamp + + acquisition_params.acquired_spectra += spectrum_index + + acquisition_params.saturation_detected = saturation_detected + + if saturation_detected is True: + print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') + # Print data for each repetition + if (verbose): + print('Spectra acquired: {}'.format(spectrum_index)) + print('Mean callback acquisition time: {} ms'.format( + np.mean(time))) + print('Total callback acquisition time: {} s'.format( + np.sum(time)/1000)) + print('Mean spectrometer acquisition time: {} ms'.format( + np.mean(timestamp))) + print('Total spectrometer acquisition time: {} s'.format( + np.sum(timestamp)/1000)) + + # Print shape of acquisition matrix for one repetition + print(f'Partial acquisition matrix dimensions:' + f'{data.shape}') + print() + + acquisition_params.update_timings(timestamps, measurement_time) + # Real time between each spectrum acquisition by the spectrometer + print('Complete acquisition done') + print('Spectra acquired: {}'.format(acquisition_params.acquired_spectra)) + print('Total acquisition time: {0:.2f} s'.format(acquisition_params.total_spectrometer_acquisition_time_s)) + + _save_acquisition(metadata, DMD_params, spectrometer_params, + acquisition_params, spectral_data) + + # Joining processes and closing queues + if reconstruct == True: + queue_to_recon.put('kill') # Sends a message to stop reconstruction + recon_process.join() + queue_to_recon.close() + plot_process.join() + queue_reconstructed.close() + + maxi = np.amax(spectral_data[0,:]) + print('------------------------------------------------') + print('maximum in the spectrum = ' + str(maxi)) + print('------------------------------------------------') + if maxi >= 65535: + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + print('!!!!! warning, spectrum saturation !!!!!!!!') + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + + return spectral_data + +def _acquire_raw_2arms(ava: Avantes, + DMD: ALP4, + camPar: CAM, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + metadata, + repetition, + repetitions + ) -> NamedTuple: + """Raw data acquisition. + + Setups a callback function to receive messages from spectrometer whenever a + measurement is ready to be read. Reads a measurement via a callback. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + + Returns: + NamedTuple: NamedTuple containig spectral data and measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + spectrum_index (int): + Index of the last acquired spectrum. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + start_measurement_time (float): + Time when acquisition started. + saturation_detected (bool): + Boolean incating if saturation was detected during acquisition. + """ + # def for spectrometer acquisition + def register_callback(measurement_time, timestamps, + spectral_data, ava): + + def measurement_callback(handle, info): # If we want to reconstruct during callback; can use it in here. Add function as parameter. + nonlocal spectrum_index + nonlocal saturation_detected + + measurement_time[spectrum_index] = perf_counter_ns() + + if info.contents.value >= 0: + timestamp,spectrum = ava.get_data() + spectral_data[spectrum_index,:] = ( + np.ctypeslib.as_array(spectrum[0:pixel_amount])) + + if np.any(ava.get_saturated_pixels() > 0): + saturation_detected = True + + timestamps[spectrum_index] = np.ctypeslib.as_array(timestamp) + + else: # Set values to zero if an error occured + spectral_data[spectrum_index,:] = 0 + timestamps[spectrum_index] = 0 + + spectrum_index += 1 + + return measurement_callback + + # def for camera acquisition + if repetition == 0: + camPar = stopCapt_DeallocMem(camPar) + camPar.trigger_mode = 'hard'#'soft'# + imageQueue(camPar) + camPar = prepareCam(camPar, metadata) + camPar.timeout = 1000 # time out in ms for the "is_WaitForNextImage" function + start_chrono = time.time() + x = threading.Thread(target = runCam_thread, args=(camPar, start_chrono)) + x.start() + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + + measurement_time = np.zeros((acquisition_params.pattern_amount)) + timestamps = np.zeros((acquisition_params.pattern_amount),dtype=np.uint32) + spectral_data = np.zeros( + (acquisition_params.pattern_amount,pixel_amount),dtype=np.float64) + + # Boolean to indicate if saturation was detected during acquisition + saturation_detected = False + + spectrum_index = 0 # Accessed as nonlocal variable inside the callback + + #spectro.register_callback(-2,acquisition_params.pattern_amount,pixel_amount) + callback = register_callback(measurement_time, timestamps, + spectral_data, ava) + measurement_callback = MeasureCallback(callback) + ava.measure_callback(-2, measurement_callback) + + # time.sleep(0.5) + # Run the whole sequence only once + DMD.Run(loop=False) + start_measurement_time = perf_counter_ns() + #sleep(13) + + while(True): + if(spectrum_index >= acquisition_params.pattern_amount): + break + elif((perf_counter_ns() - start_measurement_time) / 1e+6 > + (2 * acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+3)): + print('Stopping measurement. One of the equipments may be blocked ' + 'or disconnected.') + break + else: + time.sleep(acquisition_params.pattern_amount * + DMD_params.picture_time_us / 1e+6 / 10) + + ava.stop_measure() + DMD.Halt() + camPar.Exit = 2 + if repetition == repetitions-1: + camPar = stopCam(camPar) + #Yprint('MAIN :// camPar.camActivated = ' + str(camPar.camActivated)) + AcquisitionResult = namedtuple('AcquisitionResult', [ + 'spectral_data', + 'spectrum_index', + 'timestamps', + 'measurement_time', + 'start_measurement_time', + 'saturation_detected']) + + return AcquisitionResult(spectral_data, + spectrum_index, + timestamps, + measurement_time, + start_measurement_time, + saturation_detected) + + +def acquire_2arms(ava: Avantes, + DMD: ALP4, + camPar: CAM, + metadata: MetaData, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + repetitions: int = 1, + verbose: bool = False, + reconstruct: bool = False, + reconstruction_params: ReconstructionParameters = None + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Perform a complete acquisition. + + Performs single or multiple acquisitions using the same setup configurations + previously chosen. + Finnaly saves all acqusition related data and metadata. + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + camPar (CAM): + Metadata object of the IDS monochrome camera + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + wavelengths (List[float]): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + repetitions (int): + Number of times the acquisition will be repeated with the same + configurations. Default is 1, a single acquisition. + verbose (bool): + Chooses if data concerning each acquisition should be printed to + user. If False, only overall data regarding all repetitions is + printed. Default is False. + reconstruct (bool): + If True, will perform reconstruction alongside acquisition using + multiprocessing. + reconstruction_params (ReconstructionParameters): + Object containing parameters of the neural network to be loaded for + reconstruction. + + Returns: + Tuple[ndarray, ndarray, ndarray]: Tuple containig spectral data and + measurement timings. + spectral_data (ndarray): + 2D array of `float` of size (pattern_amount x pixel_amount) + containing measurements received from the spectrometer for each + pattern of a sequence. + timestamps (np.ndarray): + 1D array with `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. + Units in milliseconds. + measurement_time (np.ndarray): + 1D array with `float` type elapsed times between each callback. + Units in milliseconds. + """ + + if reconstruct == True: + print('Creating reconstruction processes') + + # Creating a Queue for sending spectral data to reconstruction process + queue_to_recon = Queue() + + # Creating a Queue for sending reconstructed images to plot + queue_reconstructed = Queue() + + sleep_time = (acquisition_params.pattern_amount * + DMD_params.picture_time_us/1e+6) + + # Creating reconstruction process + recon_process = Process(target=reconstruct_process, + args=(reconstruction_params.model, + reconstruction_params.device, + queue_to_recon, + queue_reconstructed, + reconstruction_params.batches, + reconstruction_params.noise, + sleep_time)) + + # Creating plot process + plot_process = Process(target=plot_recon, + args=(queue_reconstructed, sleep_time)) + + # Starting processes + recon_process.start() + plot_process.start() + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + measurement_time = np.zeros( + (acquisition_params.pattern_amount * repetitions)) + timestamps = np.zeros( + ((acquisition_params.pattern_amount - 1) * repetitions), + dtype=np.float64) + spectral_data = np.zeros( + (acquisition_params.pattern_amount * repetitions,pixel_amount), + dtype=np.float64) + + acquisition_params.acquired_spectra = 0 + print() + + for repetition in range(repetitions): + if verbose: + print(f"Acquisition {repetition}") + + AcquisitionResults = _acquire_raw_2arms(ava, DMD, camPar, spectrometer_params, + DMD_params, acquisition_params, metadata, repetition, repetitions) + + (data, spectrum_index, timestamp, time, + start_measurement_time, saturation_detected) = AcquisitionResults + + print('Acquisition number : ' + str(repetition) + ' finished') + + if reconstruct == True: + queue_to_recon.put(data.T) + print('Data sent') + + time, timestamp = _calculate_elapsed_time( + start_measurement_time, time, timestamp) + + begin = repetition * acquisition_params.pattern_amount + end = (repetition + 1) * acquisition_params.pattern_amount + spectral_data[begin:end] = data + measurement_time[begin:end] = time + + begin = repetition * (acquisition_params.pattern_amount - 1) + end = (repetition + 1) * (acquisition_params.pattern_amount - 1) + timestamps[begin:end] = timestamp + + acquisition_params.acquired_spectra += spectrum_index + + acquisition_params.saturation_detected = saturation_detected + + if saturation_detected is True: + print('!!!!!!!!!! Saturation detected in the spectro !!!!!!!!!!') + # Print data for each repetition + if (verbose): + print('Spectra acquired: {}'.format(spectrum_index)) + print('Mean callback acquisition time: {} ms'.format( + np.mean(time))) + print('Total callback acquisition time: {} s'.format( + np.sum(time)/1000)) + print('Mean spectrometer acquisition time: {} ms'.format( + np.mean(timestamp))) + print('Total spectrometer acquisition time: {} s'.format( + np.sum(timestamp)/1000)) + + # Print shape of acquisition matrix for one repetition + print(f'Partial acquisition matrix dimensions:' + f'{data.shape}') + print() + + acquisition_params.update_timings(timestamps, measurement_time) + # Real time between each spectrum acquisition by the spectrometer + print('Complete acquisition done') + print('Spectra acquired: {}'.format(acquisition_params.acquired_spectra)) + print('Total acquisition time: {0:.2f} s'.format(acquisition_params.total_spectrometer_acquisition_time_s)) + + # delete acquisition with black pattern (white for the camera) + if camPar.insert_patterns == 1: + black_pattern_index = np.where(acquisition_params.patterns_wp == -1) + # print('index of white patterns :') + # print(black_pattern_index[0:38]) + if acquisition_params.patterns_wp.shape == acquisition_params.patterns.shape: + acquisition_params.patterns = np.delete(acquisition_params.patterns, black_pattern_index) + spectral_data = np.delete(spectral_data, black_pattern_index, axis = 0) + acquisition_params.timestamps = np.delete(acquisition_params.timestamps, black_pattern_index[1:]) + acquisition_params.measurement_time = np.delete(acquisition_params.measurement_time, black_pattern_index) + acquisition_params.acquired_spectra = len(acquisition_params.patterns) + + _save_acquisition_2arms(metadata, DMD_params, spectrometer_params, camPar, + acquisition_params, spectral_data) + + # Joining processes and closing queues + if reconstruct == True: + queue_to_recon.put('kill') # Sends a message to stop reconstruction + recon_process.join() + queue_to_recon.close() + plot_process.join() + queue_reconstructed.close() + + maxi = np.amax(spectral_data[0,:]) + print('------------------------------------------------') + print('maximum in the spectrum = ' + str(maxi)) + print('------------------------------------------------') + if maxi >= 65535: + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + print('!!!!! warning, spectrum saturation !!!!!!!!') + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + + return spectral_data + + +def setup_tuneSpectro(spectrometer, + DMD, + DMD_initial_memory, + pattern_to_display, + ti : float = 1, + zoom : int = 1, + xw_offset: int = 128, + yh_offset: int = 0, + mask_index : np.array = []): + """ Setup the hadrware to tune the spectrometer in live. The goal is to find + the integration time of the spectrometer, noise is around 700 counts, + saturation is equal to 2**16=65535 + + Args: + spectrometer (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + DMD_initial_memory (int): + Initial memory available in DMD after initialization. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. User must partially fill up + with pattern_compression, pattern_dimension_x, pattern_dimension_y. + pattern_to_display (string): + display one pattern on the DMD to tune the spectrometer. Default is + white pattern + ti (float): + The integration time of the spectrometer during one scan in miliseconds. + Default is 1 ms. + zoom (int): + digital zoom on the DMD. Default is 1 + xw_offset (int): + offset of the pattern in the DMD for zoom > 1 in the width (x) direction + yh_offset (int): + offset of the pattern in the DMD for zoom > 1 in the heihgt (y) direction + mask_index (Union[np.ndarray, str], optional): + Array of `int` type corresponding to the index of the mask vector where + the value is egal to 1 + + return: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + """ + + data_folder_name = 'Tune' + data_name = 'test' + # all_path = func_path(data_folder_name, data_name) + + scan_mode = 'Walsh' + Np = 16 + source = '' + object_name = '' + + metadata = MetaData( + output_directory = '',#all_path.subfolder_path, + pattern_order_source = 'C:/openspyrit/spas/stats/pattern_order_' + scan_mode + '_' + str(Np) + 'x' + str(Np) + '.npz', + pattern_source = 'C:/openspyrit/spas/Patterns/' + scan_mode + '_' + str(Np) + 'x' + str(Np), + pattern_prefix = scan_mode + '_' + str(Np) + 'x' + str(Np), + experiment_name = data_name, + light_source = source, + object = object_name, + filter = '', + description = '' + ) + + acquisition_parameters = AcquisitionParameters( + pattern_compression = 1, + pattern_dimension_x = 16, + pattern_dimension_y = 16, + zoom = zoom, + xw_offset = xw_offset, + yh_offset = yh_offset, + mask_index = [] ) + + acquisition_parameters.pattern_amount = 1 + + spectrometer_params, DMD_params = setup( + spectrometer = spectrometer, + DMD = DMD, + DMD_initial_memory = DMD_initial_memory, + metadata = metadata, + acquisition_params = acquisition_parameters, + pattern_to_display = pattern_to_display, + integration_time = ti, + loop = True ) + + return metadata, spectrometer_params, DMD_params, acquisition_parameters + + +def displaySpectro(ava: Avantes, + DMD: ALP4, + metadata: MetaData, + spectrometer_params: SpectrometerParameters, + DMD_params: DMDParameters, + acquisition_params: AcquisitionParameters, + reconstruction_params: ReconstructionParameters = None + ): + """Perform a continousely acquisition on the spectrometer for optical tuning. + + Send a pattern on the DMD to project light on the spectrometer. The goal is + to have a look on the amplitude of the spectrum to tune the illumination to + avoid saturation (sat >= 65535) and noisy signal (amp <= 650). + + Args: + ava (Avantes): + Connected spectrometer (Avantes object). + DMD (ALP4): + Connected DMD. + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + spectrometer_params (SpectrometerParameters): + Spectrometer metadata object with spectrometer configurations. + DMD_params (DMDParameters): + DMD metadata object with DMD configurations. + acquisition_params (AcquisitionParameters): + Acquisition related metadata object. + wavelengths (List[float]): + List of float corresponding to the wavelengths associated with + spectrometer's start and stop pixels. + reconstruction_params (ReconstructionParameters): + Object containing parameters of the neural network to be loaded for + reconstruction. + """ + + loop = True # is to project continuously a unique pattern to tune the spectrometer + + pixel_amount = (spectrometer_params.stop_pixel - + spectrometer_params.start_pixel + 1) + + spectral_data = np.zeros( + (acquisition_params.pattern_amount,pixel_amount), + dtype=np.float64) + + acquisition_params.acquired_spectra = 0 + + AcquisitionResults = _acquire_raw(ava, DMD, spectrometer_params, + DMD_params, acquisition_params, loop) + + (data, spectrum_index, timestamp, time, + start_measurement_time, saturation_detected) = AcquisitionResults + + time, timestamp = _calculate_elapsed_time( + start_measurement_time, time, timestamp) + + begin = acquisition_params.pattern_amount + end = 2 * acquisition_params.pattern_amount + spectral_data[begin:end] = data + + acquisition_params.acquired_spectra += spectrum_index + + acquisition_params.saturation_detected = saturation_detected + + +def check_ueye(func, *args, exp=0, raise_exc=True, txt=None): + """Check for bad input value + + Args: + ---------- + func : TYPE + the ueye function. + *args : TYPE + the input value. + exp : TYPE, optional + DESCRIPTION. The default is 0. + raise_exc : TYPE, optional + DESCRIPTION. The default is True. + txt : TYPE, optional + DESCRIPTION. The default is None. + + Raises + ------ + RuntimeError + DESCRIPTION. + + Returns + ------- + None. + """ + + ret = func(*args) + if not txt: + txt = "{}: Expected {} but ret={}!".format(str(func), exp, ret) + if ret != exp: + if raise_exc: + raise RuntimeError(txt) + else: + logging.critical(txt) + + +def stopCapt_DeallocMem(camPar): + """Stop capture and deallocate camera memory if need to change AOI + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns: + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + print('video stop successful') + else: + print('problem to stop the video') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + print('deallocate memory successful') + else: + print('Problem to deallocate memory of the camera') + + return camPar + + +def stopCapt_DeallocMem_ExitCam(camPar): + """Stop capture, deallocate camera memory if need to change AOI and disconnect the camera + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns: + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + print('video stop successful') + else: + print('problem to stop the video') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + print('deallocate memory successful') + else: + print('Problem to deallocate memory of the camera') + + if camPar.Exit == 2: + nRet = ueye.is_ExitCamera(camPar.hCam) + if nRet == ueye.IS_SUCCESS: + camPar.Exit = 0 + print('Camera disconnected') + else: + print('Problem to disconnect camera, need to restart spyder') + + return camPar + + +class ImageBuffer: + """A class to allocate buffer in the camera memory + """ + + pcImageMemory = None + MemID = None + width = None + height = None + nbitsPerPixel = None + + +def imageQueue(camPar): + """Create Imagequeue / Allocate 3 ore more buffers depending on the framerate / Initialize Image queue + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns: + ------- + None. + + """ + + sleep(1) # is required (delay of 1s was not optimized!!) + buffers = [] + for y in range(10): + buffers.append(ImageBuffer()) + + for x in range(len(buffers)): + buffers[x].nbitsPerPixel = camPar.nBitsPerPixel # RAW8 + buffers[x].height = camPar.rectAOI.s32Height # sensorinfo.nMaxHeight + buffers[x].width = camPar.rectAOI.s32Width # sensorinfo.nMaxWidth + buffers[x].MemID = ueye.int(0) + buffers[x].pcImageMemory = ueye.c_mem_p() + check_ueye(ueye.is_AllocImageMem, camPar.hCam, buffers[x].width, buffers[x].height, buffers[x].nbitsPerPixel, + buffers[x].pcImageMemory, buffers[x].MemID) + check_ueye(ueye.is_AddToSequence, camPar.hCam, buffers[x].pcImageMemory, buffers[x].MemID) + + check_ueye(ueye.is_InitImageQueue, camPar.hCam, ueye.c_int(0)) + if camPar.trigger_mode == 'soft': + check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_SOFTWARE) + elif camPar.trigger_mode == 'hard': + check_ueye(ueye.is_SetExternalTrigger, camPar.hCam, ueye.IS_SET_TRIGGER_LO_HI) + + +def prepareCam(camPar, metadata): + """Prepare the IDS monochrome camera before acquisition + + Args: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. Must be created and filled up by the user. + + Returns: + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + """ + cam_path = metadata.output_directory + '\\' + metadata.experiment_name + '_video.' + camPar.vidFormat + strFileName = ueye.c_char_p(cam_path.encode('utf-8')) + + if camPar.vidFormat == 'avi': + # print('Video format : AVI') + camPar.avi = ueye.int() + nRet = ueye_tools.isavi_InitAVI(camPar.avi, camPar.hCam) + # print("isavi_InitAVI") + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_InitAVI ERROR") + + nRet = ueye_tools.isavi_SetImageSize(camPar.avi, camPar.m_nColorMode, camPar.rectAOI.s32Width , camPar.rectAOI.s32Height, 0, 0, 0) + nRet = ueye_tools.isavi_SetImageQuality(camPar.avi, 100) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_SetImageQuality ERROR") + + nRet = ueye_tools.isavi_OpenAVI(camPar.avi, strFileName) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_OpenAVI ERROR") + print('Error code = ' + str(nRet)) + print('Certainly, it is a problem with the file name, Avoid special character like "µ" or try to redcue its size') + + nRet = ueye_tools.isavi_SetFrameRate(camPar.avi, camPar.fps) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_SetFrameRate ERROR") + nRet = ueye_tools.isavi_StartAVI(camPar.avi) + # print("isavi_StartAVI") + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("isavi_StartAVI ERROR") + + + elif camPar.vidFormat == 'bin': + camPar.punFileID = ueye.c_uint() + nRet = ueye_tools.israw_InitFile(camPar.punFileID, ueye_tools.IS_FILE_ACCESS_MODE_WRITE) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("INIT RAW FILE ERROR") + + nRet = ueye_tools.israw_SetImageInfo(camPar.punFileID, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel) + if nRet != ueye_tools.IS_AVI_NO_ERR: + print("SET IMAGE INFO ERROR") + + if nRet == ueye.IS_SUCCESS: + # print('initFile ok') + # print('SetImageInfo ok') + nRet = ueye_tools.israw_OpenFile(camPar.punFileID, strFileName) + # if nRet == ueye.IS_SUCCESS: + # # print('OpenFile success') + + # --------------------------------------------------------- + # Activates the camera's live video mode (free run mode) + # --------------------------------------------------------- + nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) + + if nRet != ueye.IS_SUCCESS: + print("is_CaptureVideo ERROR") + else: + camPar.camActivated = 1 + + return camPar + + +def runCam_thread(camPar, start_chrono): + """Acquire video with the IDS monochrome camera in a thread + + Parameters: + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + start_chrono : int + to save a delay for each acquisition frame of the video. + + Returns: + ------- + None. + """ + + imageinfo = ueye.UEYEIMAGEINFO() + current_buffer = ueye.c_mem_p() + current_id = ueye.int() + # inc = 0 + entier_old = 0 + # time.sleep(0.01) + while True: + nret = ueye.is_WaitForNextImage(camPar.hCam, camPar.timeout, current_buffer, current_id) + if nret == ueye.IS_SUCCESS: + check_ueye(ueye.is_GetImageInfo, camPar.hCam, current_id, imageinfo, ueye.sizeof(imageinfo)) + start_time = time.time() + counter = start_time - start_chrono + camPar.time_array.append(counter) + if camPar.vidFormat == 'avi': + nRet = ueye_tools.isavi_AddFrame(camPar.avi, current_buffer) + elif camPar.vidFormat == 'bin': + nRet = ueye_tools.israw_AddFrame(camPar.punFileID, current_buffer, imageinfo.u64TimestampDevice) + + check_ueye(ueye.is_UnlockSeqBuf, camPar.hCam, current_id, current_buffer) + else: + print('Thread finished') + break + + +def stopCam(camPar): + """To stop the acquisition of the video + + Parameters + ---------- + camPar (CAM): + Metadata object of the IDS monochrome camera + + Returns + ------- + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + + if camPar.vidFormat == 'avi': + ueye_tools.isavi_StopAVI(camPar.hCam) + ueye_tools.isavi_CloseAVI(camPar.hCam) + ueye_tools.isavi_ExitAVI(camPar.hCam) + elif camPar.vidFormat == 'bin': + ueye_tools.israw_CloseFile(camPar.punFileID) + ueye_tools.israw_ExitFile(camPar.punFileID) + camPar.punFileID = ueye.c_uint() + + return camPar + + +def disconnect(ava: Optional[Avantes]=None, + DMD: Optional[ALP4]=None): + """Disconnect spectrometer and DMD. + + Disconnects equipments trying to stop a running pattern sequence (possibly + blocking correct functioning) and trying to free DMD memory to avoid errors + in later acqusitions. + + Args: + ava (Avantes, optional): + Connected spectrometer (Avantes object). Defaults to None. + DMD (ALP4, optional): + Connected DMD. Defaults to None. + """ + + if ava is not None: + ava.disconnect() + print('Spectro disconnected') + + if DMD is not None: + + # Stop the sequence display + DMD.Halt() + + # Free the sequence from the onboard memory (if any is present) + if (DMD.Seqs): + DMD.FreeSeq() + + DMD.Free() + print('DMD disconnected') + + +def disconnect_2arms(ava: Optional[Avantes]=None, + DMD: Optional[ALP4]=None, + camPar=None): + """Disconnect spectrometer, DMD and the IDS monochrome camera. + + Disconnects equipments trying to stop a running pattern sequence (possibly + blocking correct functioning) and trying to free DMD memory to avoid errors + in later acqusitions. + + Args: + ava (Avantes, optional): + Connected spectrometer (Avantes object). Defaults to None. + DMD (ALP4, optional): + Connected DMD. Defaults to None. + camPar (CAM): + Metadata object of the IDS monochrome camera + """ + + if ava is not None: + ava.disconnect() + print('Spectro disconnected') + + if DMD is not None: + # Stop the sequence display + try: + DMD.Halt() + # Free the sequence from the onboard memory (if any is present) + if (DMD.Seqs): + DMD.FreeSeq() + + DMD.Free() + print('DMD disconnected') + + except: + print('probelm to Halt the DMD') + + + if camPar.camActivated == 1: + nRet = ueye.is_StopLiveVideo(camPar.hCam, ueye.IS_FORCE_VIDEO_STOP) + if nRet == ueye.IS_SUCCESS: + camPar.camActivated = 0 + else: + print('Problem to stop video, need to restart spyder') + + if camPar.Memory == 1: + nRet = ueye.is_FreeImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet == ueye.IS_SUCCESS: + camPar.Memory = 0 + else: + print('Problem to deallocate camera memory, need to restart spyder') + + + if camPar.Exit == 1 or camPar.Exit == 2: + nRet = ueye.is_ExitCamera(camPar.hCam) + if nRet == ueye.IS_SUCCESS: + camPar.Exit = 0 + print('Camera disconnected') + else: + print('Problem to disconnect camera, need to restart spyder') + + +def _init_CAM(): + """ + Initialize and connect to the IDS camera. + + Returns: + CAM: a structure containing the parameters of the IDS camera + """ + camPar = CAM(hCam = ueye.HIDS(0), + sInfo = ueye.SENSORINFO(), + cInfo = ueye.CAMINFO(), + nBitsPerPixel = ueye.INT(8), + m_nColorMode = ueye.INT(), + bytes_per_pixel = int( ueye.INT(8)/ 8), + rectAOI = ueye.IS_RECT(), + pcImageMemory = ueye.c_mem_p(), + MemID = ueye.int(), + pitch = ueye.INT(), + fps = float(), + gain = int(), + gainBoost = str(), + gamma = float(), + exposureTime = float(), + blackLevel = int(), + camActivated = bool(), + pixelClock = ueye.uint(), + bandwidth = float(), + Memory = bool(), + Exit = int(), + vidFormat = str(), + gate_period = int(), + trigger_mode = str(), + avi = ueye.int(), + punFileID = ueye.c_uint(), + timeout = int(), + time_array = [], + int_time_spect = float(), + black_pattern_num = int(), + insert_patterns = bool(), + acq_mode = str(), + ) + + # # Camera Initialization --- + ### Starts the driver and establishes the connection to the camera + nRet = ueye.is_InitCamera(camPar.hCam, None) + if nRet != ueye.IS_SUCCESS: + print("is_InitCamera ERROR") + + ### Reads out the data hard-coded in the non-volatile camera memory and writes it to the data structure that cInfo points to + nRet = ueye.is_GetCameraInfo(camPar.hCam, camPar.cInfo) + if nRet != ueye.IS_SUCCESS: + print("is_GetCameraInfo ERROR") + + ### You can query additional information about the sensor type used in the camera + nRet = ueye.is_GetSensorInfo(camPar.hCam, camPar.sInfo) + if nRet != ueye.IS_SUCCESS: + print("is_GetSensorInfo ERROR") + + ### set camera parameters to default values + nRet = ueye.is_ResetToDefault(camPar.hCam) + if nRet != ueye.IS_SUCCESS: + print("is_ResetToDefault ERROR") + + ### Set display mode to DIB + nRet = ueye.is_SetDisplayMode(camPar.hCam, ueye.IS_SET_DM_DIB) + + ### Set the right color mode + if int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_BAYER: + # setup the color depth to the current windows setting + ueye.is_GetColorDepth(camPar.hCam, camPar.nBitsPerPixel, camPar.m_nColorMode) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_CBYCRY: + # for color camera models use RGB32 mode + camPar.m_nColorMode = ueye.IS_CM_BGRA8_PACKED + camPar.nBitsPerPixel = ueye.INT(32) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + elif int.from_bytes(camPar.sInfo.nColorMode.value, byteorder='big') == ueye.IS_COLORMODE_MONOCHROME: + # for color camera models use RGB32 mode + camPar.m_nColorMode = ueye.IS_CM_MONO8 + camPar.nBitsPerPixel = ueye.INT(8) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + else: + # for monochrome camera models use Y8 mode + camPar.m_nColorMode = ueye.IS_CM_MONO8 + camPar.nBitsPerPixel = ueye.INT(8) + camPar.bytes_per_pixel = int(camPar.nBitsPerPixel / 8) + # print("else") + + ### Get the AOI (Area Of Interest) + sizeofrectAOI = ueye.c_uint(4*4) + nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_GET_AOI, camPar.rectAOI, sizeofrectAOI) + if nRet != ueye.IS_SUCCESS: + print("AOI getting ERROR") + + camPar.camActivated = 0 + + # Get current pixel clock + getpixelclock = ueye.UINT(0) + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, + ueye.sizeof(getpixelclock)) + + camPar.pixelClock = getpixelclock + # print('pixel clock = ' + str(getpixelclock) + ' MHz') + + # get the bandwidth (in MByte/s) + camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) + + camPar.Exit = 1 + + print('IDS camera connected') + + return camPar + + +def captureVid(camPar): + """ + Allocate memory and begin video capture of the IDS camera + + Args: + camPar : a structure containing the parameters of the IDS camera. + + Returns: + camPar : a structure containing the parameters of the IDS camera. + """ + camPar = stopCapt_DeallocMem_ExitCam(camPar) + + if camPar.Exit == 0: + camPar = _init_CAM() + camPar.Exit = 1 + + + ### Set the AOI + sizeofrectAOI = ueye.c_uint(4*4) + nRet = ueye.is_AOI(camPar.hCam, ueye.IS_AOI_IMAGE_SET_AOI, camPar.rectAOI, sizeofrectAOI) + if nRet != ueye.IS_SUCCESS: + print("AOI setting ERROR") + + width = camPar.rectAOI.s32Width + height = camPar.rectAOI.s32Height + + ### Allocates an image memory for an image having its dimensions defined by width and height and its color depth defined by nBitsPerPixel + nRet = ueye.is_AllocImageMem(camPar.hCam, width, height, camPar.nBitsPerPixel, camPar.pcImageMemory, camPar.MemID) + if nRet != ueye.IS_SUCCESS: + print("is_AllocImageMem ERROR") + else: + # Makes the specified image memory the active memory + camPar.Memory = 1 + nRet = ueye.is_SetImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID) + if nRet != ueye.IS_SUCCESS: + print("is_SetImageMem ERROR") + else: + # Set the desired color mode + nRet = ueye.is_SetColorMode(camPar.hCam, camPar.m_nColorMode) + + + ### Activates the camera's live video mode (free run mode) + nRet = ueye.is_CaptureVideo(camPar.hCam, ueye.IS_DONT_WAIT) + if nRet != ueye.IS_SUCCESS: + print("is_CaptureVideo ERROR") + + + ### Enables the queue mode for existing image memory sequences + nRet = ueye.is_InquireImageMem(camPar.hCam, camPar.pcImageMemory, camPar.MemID, width, height, camPar.nBitsPerPixel, camPar.pitch) + if nRet != ueye.IS_SUCCESS: + print("is_InquireImageMem ERROR") + + camPar.camActivated = 1 + + return camPar + +def setup_cam(camPar, pixelClock, fps, Gain, gain_boost, nGamma, ExposureTime, black_level): + """ + Set and read the camera parameters + + Args: + pixelClock = [118, 237 or 474] (MHz) + fps: fps boundary => [1 - No Value] sup limit depend of image size (216 fps for 768x544 pixels for example) + Gain: Gain boundary => [0 100] + gain_boost: 'ON' set "ON" to activate gain boost, "OFF" to deactivate + nGamma: Gamma boundary => [1 - 2.2] + ExposureTime: Exposure time (ms) boundarye => [0.032 - 56.221] + black_level: Black Level boundary => [0 255] + + returns: + CAM: a structure containing the parameters of the IDS camera + """ + # It is necessary to execute twice this code to take account the parameter modification + for i in range(2): + ############################### Set Pixel Clock ############################### + ### Get range of pixel clock, result : range = [118 474] MHz (Inc = 0) + getpixelclock = ueye.UINT(0) + newpixelclock = ueye.UINT(0) + newpixelclock.value = pixelClock + PixelClockRange = (ueye.int * 3)() + + # Get pixel clock range + nRet = ueye.is_PixelClock(camPar.hCam, ueye.IS_PIXELCLOCK_CMD_GET_RANGE, PixelClockRange, ueye.sizeof(PixelClockRange)) + if nRet == ueye.IS_SUCCESS: + nPixelClockMin = PixelClockRange[0] + nPixelClockMax = PixelClockRange[1] + nPixelClockInc = PixelClockRange[2] + + # Set pixel clock + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_SET, newpixelclock, + ueye.sizeof(newpixelclock)) + # Get current pixel clock + check_ueye(ueye.is_PixelClock, camPar.hCam, ueye.PIXELCLOCK_CMD.IS_PIXELCLOCK_CMD_GET, getpixelclock, + ueye.sizeof(getpixelclock)) + + camPar.pixelClock = getpixelclock.value + if i == 1: + print(' pixel clock = ' + str(getpixelclock) + ' MHz') + if getpixelclock == 118: + if i == 1: + print('Pixel clcok blocked to 118 MHz, it is necessary to unplug the camera if not desired') + # get the bandwidth (in MByte/s) + camPar.bandwidth = ueye.is_GetUsedBandwidth(camPar.hCam) + if i == 1: + print(' Bandwidth = ' + str(camPar.bandwidth) + ' MB/s') + ############################### Set FrameRate ################################# + ### Read current FrameRate + dblFPS_init = ueye.c_double() + nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_init) + if nRet != ueye.IS_SUCCESS: + print("FrameRate getting ERROR") + else: + dblFPS_eff = dblFPS_init + if i == 1: + print(' current FPS = '+str(round(dblFPS_init.value*100)/100) + ' fps') + if fps < 1: + fps = 1 + if i == 1: + print('FPS exceed lower limit >= 1') + + dblFPS = ueye.c_double(fps) + if (dblFPS.value < dblFPS_init.value-0.01) | (dblFPS.value > dblFPS_init.value+0.01): + newFPS = ueye.c_double() + nRet = ueye.is_SetFrameRate(camPar.hCam, dblFPS, newFPS) + time.sleep(1) + if nRet != ueye.IS_SUCCESS: + print("FrameRate setting ERROR") + else: + if i == 1: + print(' new FPS = '+str(round(newFPS.value*100)/100) + ' fps') + ### Read again the effective FPS / depend of the image size, 17.7 fps is not possible with the entire image size (ie 2076x3088) + dblFPS_eff = ueye.c_double() + nRet = ueye.is_GetFramesPerSecond(camPar.hCam, dblFPS_eff) + if nRet != ueye.IS_SUCCESS: + print("FrameRate getting ERROR") + else: + if i == 1: + print(' effective FPS = '+str(round(dblFPS_eff.value*100)/100) + ' fps') + ############################### Set GAIN ###################################### + #### Maximum gain is depending of the sensor. Convertion gain code to gain to limit values from 0 to 100 + # gain_code = gain * slope + b + gain_max_code = 1450 + gain_min_code = 100 + gain_max = 100 + gain_min = 0 + slope = (gain_max_code-gain_min_code)/(gain_max-gain_min) + b = gain_min_code + #### Read gain setting + current_gain_code = ueye.c_int() + current_gain_code = ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_GET_MASTER_GAIN_FACTOR, current_gain_code) + current_gain = round((current_gain_code-b)/slope) + + if i == 1: + print(' current GAIN = '+str(current_gain)) + gain_eff = current_gain + + ### Set new gain value + gain = ueye.c_int(Gain) + if gain.value != current_gain: + if gain.value < 0: + gain = ueye.c_int(0) + if i == 1: + print('Gain exceed lower limit >= 0') + elif gain.value > 100: + gain = ueye.c_int(100) + if i == 1: + print('Gain exceed upper limit <= 100') + gain_code = ueye.c_int(round(slope*gain.value+b)) + + ueye.is_SetHWGainFactor(camPar.hCam, ueye.IS_SET_MASTER_GAIN_FACTOR, gain_code) + new_gain = round((gain_code-b)/slope) + + if i == 1: + print(' new GAIN = '+str(new_gain)) + gain_eff = new_gain + ############################### Set GAIN Boost ################################ + ### Read current state of the gain boost + current_gain_boost_bool = ueye.is_SetGainBoost(camPar.hCam, ueye.IS_GET_GAINBOOST) + if nRet != ueye.IS_SUCCESS: + print("Gain boost ERROR") + if current_gain_boost_bool == 0: + current_gain_boost = 'OFF' + elif current_gain_boost_bool == 1: + current_gain_boost = 'ON' + + if i == 1: + print('current Gain boost mode = ' + current_gain_boost) + + ### Set the state of the gain boost + if gain_boost != current_gain_boost: + if gain_boost == 'OFF': + nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_OFF) + print(' new Gain Boost : OFF') + + elif gain_boost == 'ON': + nRet = ueye.is_SetGainBoost (camPar.hCam, ueye.IS_SET_GAINBOOST_ON) + print(' new Gain Boost : ON') + + if nRet != ueye.IS_SUCCESS: + print("Gain boost setting ERROR") + ############################### Set Gamma ##################################### + ### Check boundary of Gamma + if nGamma > 2.2: + nGamma = 2.2 + if i == 1: + print('Gamma exceed upper limit <= 2.2') + elif nGamma < 1: + nGamma = 1 + if i == 1: + print('Gamma exceed lower limit >= 1') + ### Read current Gamma + c_nGamma_init = ueye.c_void_p() + sizeOfnGamma = ueye.c_uint(4) + nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_GET, c_nGamma_init, sizeOfnGamma) + if nRet != ueye.IS_SUCCESS: + print("Gamma getting ERROR") + else: + if i == 1: + print(' current Gamma = ' + str(c_nGamma_init.value/100)) + ### Set Gamma + c_nGamma = ueye.c_void_p(round(nGamma*100)) # need to multiply by 100 [100 - 220] + if c_nGamma_init.value != c_nGamma.value: + nRet = ueye.is_Gamma(camPar.hCam, ueye.IS_GAMMA_CMD_SET, c_nGamma, sizeOfnGamma) + if nRet != ueye.IS_SUCCESS: + print("Gamma setting ERROR") + else: + if i == 1: + print(' new Gamma = '+str(c_nGamma.value/100)) + ############################### Set Exposure time ############################# + ### Read current Exposure Time + getExposure = ueye.c_double() + sizeOfpParam = ueye.c_uint(8) + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE, getExposure, sizeOfpParam) + if nRet == ueye.IS_SUCCESS: + getExposure.value = round(getExposure.value*1000)/1000 + + if i == 1: + print(' current Exposure Time = ' + str(getExposure.value) + ' ms') + ### Get minimum Exposure Time + minExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MIN, minExposure, sizeOfpParam) + ### Get maximum Exposure Time + maxExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_MAX, maxExposure, sizeOfpParam) + ### Get increment Exposure Time + incExposure = ueye.c_double() + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE_INC, incExposure, sizeOfpParam) + ### Set new Exposure Time + setExposure = ueye.c_double(ExposureTime) + if setExposure.value > maxExposure.value: + setExposure.value = maxExposure.value + if i == 1: + print('Exposure Time exceed upper limit <= ' + str(maxExposure.value)) + elif setExposure.value < minExposure.value: + setExposure.value = minExposure.value + if i == 1: + print('Exposure Time exceed lower limit >= ' + str(minExposure.value)) + + if (setExposure.value < getExposure.value-incExposure.value/2) | (setExposure.value > getExposure.value+incExposure.value/2): + nRet = ueye.is_Exposure(camPar.hCam, ueye.IS_EXPOSURE_CMD_SET_EXPOSURE, setExposure, sizeOfpParam) + if nRet != ueye.IS_SUCCESS: + print("Exposure Time ERROR") + else: + if i == 1: + print(' new Exposure Time = ' + str(round(setExposure.value*1000)/1000) + ' ms') + ############################### Set Black Level ############################### + current_black_level_c = ueye.c_uint() + sizeOfBlack_level = ueye.c_uint(4) + ### Read current Black Level + nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_GET_OFFSET, current_black_level_c, sizeOfBlack_level) + if nRet != ueye.IS_SUCCESS: + print("Black Level getting ERROR") + else: + if i == 1: + print(' current Black Level = ' + str(current_black_level_c.value)) + + ### Set Black Level + if black_level > 255: + black_level = 255 + if i == 1: + print('Black Level exceed upper limit <= 255') + if black_level < 0: + black_level = 0 + if i == 1: + print('Black Level exceed lower limit >= 0') + + black_level_c = ueye.c_uint(black_level) + if black_level != current_black_level_c.value : + nRet = ueye.is_Blacklevel(camPar.hCam, ueye.IS_BLACKLEVEL_CMD_SET_OFFSET, black_level_c, sizeOfBlack_level) + if nRet != ueye.IS_SUCCESS: + print("Black Level setting ERROR") + else: + if i == 1: + print(' new Black Level = ' + str(black_level_c.value)) + + + camPar.fps = round(dblFPS_eff.value*100)/100 + camPar.gain = gain_eff + camPar.gainBoost = gain_boost + camPar.gamma = c_nGamma.value/100 + camPar.exposureTime = round(setExposure.value*1000)/1000 + camPar.blackLevel = black_level_c.value + + return camPar + + +def snapshot(camPar, pathIDSsnapshot, pathIDSsnapshot_overview): + """ + Snapshot of the IDS camera + + Args: + CAM: a structure containing the parameters of the IDS camera + """ + array = ueye.get_data(camPar.pcImageMemory, camPar.rectAOI.s32Width, camPar.rectAOI.s32Height, camPar.nBitsPerPixel, camPar.pitch, copy=False) + + # ...reshape it in an numpy array... + frame = np.reshape(array,(camPar.rectAOI.s32Height.value, camPar.rectAOI.s32Width.value))#, camPar.bytes_per_pixel)) + + with pathIDSsnapshot.open('wb') as f: #('ab') as f: #(pathname, mode='w', encoding='utf-8') as f: #('ab') as f: + np.save(f,frame) + + maxi = np.amax(frame) + if maxi == 0: + maxi = 1 + im = Image.fromarray(frame*math.floor(255/maxi)) + im.save(pathIDSsnapshot_overview) + + maxi = np.amax(frame) + # print() + # print('frame max = ' + str(maxi)) + # print('frame min = ' + str(np.amin(frame))) + if maxi >= 255: + print('Saturation detected') + + plt.figure + plt.imshow(frame)#, cmap='gray', vmin=mini, vmax=maxi) + plt.colorbar(); + plt.show() + + \ No newline at end of file diff --git a/spas/metadata_SPC1D.py b/spas/metadata_SPC1D.py new file mode 100644 index 0000000..344b1dd --- /dev/null +++ b/spas/metadata_SPC1D.py @@ -0,0 +1,1118 @@ +# -*- coding: utf-8 -*- +__author__ = 'Guilherme Beneti Martins' + +"""Metadata classes and utilities. + +Metadata classes to keep and save all relevant data during an acquisition. +Utility functions to recreate objects from JSON files, save them to JSON and to +improve readability. +""" + +import json +from datetime import datetime +from enum import IntEnum +from dataclasses import dataclass, InitVar, field +from typing import Optional, Union, List, Tuple, Optional +from pathlib import Path +import os +from dataclasses_json import dataclass_json +import numpy as np +import ctypes as ct +import pickle +##### DLL for the DMD +try: + import ALP4 +except: # in the cas the DLL of the DMD is not installed + class ALP4: + pass + setattr(ALP4, 'ALP4', None) + print('DLL of the DMD not installed') +##### DLL for the spectrometer Avantes +try: + from msl.equipment.resources.avantes import MeasConfigType +except: # in the cas the DLL of the spectrometer is not installed + class MeasConfigType: + pass + MeasConfigType = None + print('DLL of the spectrometer not installed !!!') + +##### DLL for the camera +try: + from pyueye import ueye + dll_pyueye_installed = 1 +except: + dll_pyueye_installed = 0 + print('DLL of the cam not installed !!') + +class DMDTypes(IntEnum): + """Enumeration of DMD types and respective codes.""" + ALP_DMDTYPE_XGA = 1 + ALP_DMDTYPE_SXGA_PLUS = 2 + ALP_DMDTYPE_1080P_095A = 3 + ALP_DMDTYPE_XGA_07A = 4 + ALP_DMDTYPE_XGA_055A = 5 + ALP_DMDTYPE_XGA_055X = 6 + ALP_DMDTYPE_WUXGA_096A = 7 + ALP_DMDTYPE_WQXGA_400MHZ_090A = 8 + ALP_DMDTYPE_WQXGA_480MHZ_090A = 9 + ALP_DMDTYPE_WXGA_S450 = 12 + ALP_DMDTYPE_DISCONNECT = 255 + + +@dataclass_json +@dataclass +class MetaData: + """ Class containing overall acquisition parameters and description. + + Metadata concerning the experiment, paths, file inputs and file outputs. + This class is adapted to be reconstructed from a JSON file. + + Attributes: + output_directory (Union[str, Path], optional): + Directory where multiple related acquisitions will be stored. + pattern_order_source (Union[str, Path], optional): + File where the order of patterns to be sent to DMD is specified. It + can be a text file containing a list of pattern indeces or a numpy + file containing a covariance matrix from which the pattern order is + calculated. + pattern_source (Union[str, Path], optional): + Pattern source folder. + pattern_prefix (str): + Prefix used in pattern naming. + experiment_name (str): + Prefix of all files related to a single acquisition. Files will + appear with the following string pattern: + experiment_name + '_' + filename. + light_source (str): + Light source used to illuminate an object during acquisition. + object (str): + Object imaged during acquisition. + filter (str): + Light filter used. + description (str): + Acqusition experiment description. + date (str, optional): + Acquisition date. Automatically set when object is created. Default + is None. + time (str, optional): + Time when metadata object is created. Set automatically by + __post_init__(). Default is None. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'Metadata'. + """ + + pattern_prefix: str + experiment_name: str + + light_source: str + object: str + filter: str + description: str + + output_directory: Union[str, Path] + pattern_order_source: Union[str, Path] + pattern_source: Union[str, Path] + + date: Optional[str] = None + time: Optional[str] = None + + class_description: str = 'Metadata' + + def __post_init__(self): + """Sets time and date of object cretion and deals with paths""" + + today = datetime.today() + self.date = '--/--/----' #today.strftime('%d/%m/%Y') + self.time = today.strftime('%I:%M:%S %p') + + # If parameter is str, turn it into Path + if isinstance(self.output_directory, str): + self.output_directory = Path(self.output_directory) + + # If parameter is Path or was turned into a Path, resolve it and get the + # str format. + if issubclass(self.output_directory.__class__, Path): + self.output_directory = str(self.output_directory.resolve()) + + + if isinstance(self.pattern_order_source, str): + self.pattern_order_source = Path(self.pattern_order_source) + + if issubclass(self.pattern_order_source.__class__, Path): + self.pattern_order_source = str( + self.pattern_order_source.resolve()) + + + if isinstance(self.pattern_source, str): + self.pattern_source = Path(self.pattern_source) + + if issubclass(self.pattern_source.__class__, Path): + self.pattern_source = str(self.pattern_source.resolve()) + + +@dataclass_json +@dataclass +class CAM: + """Class containing IDS camera configurations. + + Further information: https://en.ids-imaging.com/manuals/ids-software-suite/ueye-manual/4.95/en/c_programmierung.html. + + Attributes: + hCam (ueye.c_uint): Handle of the camera. + sInfo (ueye.SENSORINFO):sensor information : [SensorID [c_ushort] = 566; + strSensorName [c_char_Array_32] = b'UI388xCP-M'; + nColorMode [c_char] = b'\x01'; + nMaxWidth [c_uint] = 3088; + nMaxHeight [c_uint] = 2076; + bMasterGain [c_int] = 1; + bRGain [c_int] = 0; + bGGain [c_int] = 0; + bBGain [c_int] = 0; + bGlobShutter [c_int] = 0; + wPixelSize [c_ushort] = 240; + nUpperLeftBayerPixel [c_char] = b'\x00'; + Reserved]. + cInfo (ueye.BOARDINFO):Camera information: [SerNo [c_char_Array_12] = b'4103219888'; + ID [c_char_Array_20] = b'IDS GmbH'; + Version [c_char_Array_10] = b''; + Date [c_char_Array_12] = b'30.11.2017'; + Select [c_ubyte] = 1; + Type [c_ubyte] = 100; + Reserved [c_char_Array_8] = b'';] + nBitsPerPixel (ueye.c_int): number of bits per pixel (8 for monochrome, 24 for color). + m_nColorMode (ueye.c_int): color mode : Y8/RGB16/RGB24/REG32. + bytes_per_pixel (int): bytes_per_pixel = int(nBitsPerPixel / 8). + rectAOI (ueye.IS_RECT()): rectangle of the Area Of Interest (AOI): s32X [c_int] = 0; + s32Y [c_int] = 0; + s32Width [c_int] = 3088; + s32Height [c_int] = 2076; + pcImageMemory (ueye.c_mem_p()): memory allocation. + MemID (ueye.int()): memory identifier. + pitch (ueye.INT()): ???. + fps (float): set frame per second. + gain (int): Set gain between [0 - 100]. + gainBoost (str): Activate gain boosting ("ON") or deactivate ("OFF"). + gamma (float): Set Gamma between [1 - 2.5] to change the image contrast + exposureTime (float): Set the exposure time between [0.032 - 56.221] + blackLevel (int): Set the black level between [0 - 255] to set an offset in the image. It is adviced to put 5 for noise measurement + camActivated (bool) : need to to know if the camera is ready to acquire (1: yes, 0: No) + pixelClock (int) : the pixel clock, three values possible : [118, 237, 474] (MHz) + bandwidth (float) the bandwidth (in MByte/s) is an approximate value which is calculated based on the pixel clock + Memory (bool) : a boolean to know if the memory inside the camera is busy [1] or free [0] + Exit (int) : if Exit = 2 => excute is_ExitCamera function (disables the hCam camera handle and releases the memory) | if Exit = 0 => allow to init cam, after that, Exit = 1 + vidFormat (str) : save video in the format avi or bin (for binary) + gate_period (int) : a second TTL is sent by the DMD to trigg the camera, and based on the fisrt TTL to trigg the spectrometer. camera trigger period = gate_period*(spectrometer trigger period) + trigger_mode (str) : hard or soft + avi (ueye.int) : A pointer that returns the instance ID which is needed for calling the other uEye AVI functions + punFileID (ueye.c_int) : a pointer in which the instance ID is returned. This ID is needed for calling other functions. + timeout (int) : a time which stop the camera that waiting for a TTL + time_array (List[float]) : the time array saved after each frame received on the camera + int_time_spect (float) : is egal to the integration time of the spectrometer, it is need to know this value because of the rolling shutter of the monochrome IDS camera + black_pattern_num (int) : is number inside the image name of the black pattern (for the hyperspectral arm, or white pattern for the camera arm) to be inserted betweem the Hadamard patterns + insert_patterns (int) : 0 => no insertion / 1=> insert white patterns for the camera + acq_mode (str) : mode of the acquisition => 'video' or 'snapshot' mode + """ + if dll_pyueye_installed: + hCam: Optional[ueye.c_uint] = None + sInfo: Optional[ueye.SENSORINFO] = None + cInfo: Optional[ueye.BOARDINFO] = None + nBitsPerPixel: Optional[ueye.c_int] = None + m_nColorMode: Optional[ueye.c_int] = None + bytes_per_pixel: Optional[int] = None + rectAOI: Optional[ueye.IS_RECT] = None + pcImageMemory: Optional[ueye.c_mem_p] = None + MemID: Optional[ueye.c_int] = None + pitch: Optional[ueye.c_int] = None + fps: Optional[float] = None + gain: Optional[int] = None + gainBoost: Optional[str] = None + gamma: Optional[float] = None + exposureTime: Optional[float] = None + blackLevel: Optional[int] = None + camActivated : Optional[bool] = None + pixelClock : Optional[int] = None + bandwidth : Optional[float] = None + Memory : Optional[bool] = None + Exit : Optional[int] = None + vidFormat : Optional[str] = None + gate_period : Optional[int] = None + trigger_mode : Optional[str] = None + avi : Optional[ueye.int] = None + punFileID : Optional[ueye.c_int] = None + timeout : Optional[int] = None + time_array : Optional[Union[List[float], str]] = field(default=None, repr=False) + int_time_spect : Optional[float] = None + black_pattern_num : Optional[int] = None + insert_patterns : Optional[int] = None + acq_mode : Optional[str] = None + + class_description: str = 'IDS camera configuration' + + def undo_readable_class_CAM(self) -> None: + """Changes the time_array attribute from `str` to `List` of `int`.""" + + def to_float(str_arr): + arr = [] + for s in str_arr: + try: + num = float(s) + arr.append(num) + except ValueError: + pass + return arr + + if self.time_array: + self.time_array = ( + self.time_array.strip('[').strip(']').split(', ')) + self.time_array = to_float(self.time_array) + self.time_array = np.asarray(self.time_array) + + @staticmethod + def readable_class_CAM(cam_params_dict: dict) -> dict: + # pass + """Turns list of time_array into a string. + convert the c_type structure (sInfo, cInfo and rectAOI) into a nested dict + change the bytes type item into str + change the c_types item into their value + """ + + readable_cam_dict = {} + readable_cam_dict_temp = cam_params_dict#camPar.to_dict()# + inc = 0 + for item in readable_cam_dict_temp: + stri = str(type(readable_cam_dict_temp[item])) + # print('----- item : ' + item) + if item == 'sInfo' or item == 'cInfo' or item == 'rectAOI': + readable_cam_dict[item] = dict() + try: + for sub_item in readable_cam_dict_temp[item]._fields_: + new_item = item + '-' + sub_item[0] + try: + att = getattr(readable_cam_dict_temp[item], sub_item[0]).value + except: + att = getattr(readable_cam_dict_temp[item], sub_item[0]) + + if type(att) == bytes: + att = str(att) + + readable_cam_dict[item][sub_item[0]] = att + except: + try: + for sub_item in readable_cam_dict_temp[item]: + # print('----- sub_item : ' + sub_item) + new_item = item + '-' + sub_item + att = readable_cam_dict_temp[item][sub_item] + + if type(att) == bytes: + att = str(att) + + readable_cam_dict[item][sub_item] = att + except: + print('warning, impossible to read the subitem of readable_cam_dict_temp[item]') + + elif stri.find('pyueye') >=0: + try: + readable_cam_dict[item] = readable_cam_dict_temp[item].value + except: + readable_cam_dict[item] = readable_cam_dict_temp[item] + elif item == 'time_array': + readable_cam_dict[item] = str(readable_cam_dict_temp[item]) + else: + readable_cam_dict[item] = readable_cam_dict_temp[item] + + return readable_cam_dict + + +@dataclass_json +@dataclass +class AcquisitionParameters: + """Class containing acquisition specifications and timing results. + + This class is adapted to be reconstructed from a JSON file. + + Attributes: + pattern_compression (float): + Percentage of total available patterns to be present in an + acquisition sequence. + pattern_dimension_x (int): + Length of reconstructed image that defines pattern length. + pattern_dimension_y (int): + Width of reconstructed image that defines pattern width. + zoom (int): + numerical zoom of the patterns + xw_offset (int): + offset of the pattern in the DMD for zoom > 1 in the width (x) direction + yh_offset (int): + offset of the pattern in the DMD for zoom > 1 in the heihgt (y) direction + mask_index (Union[np.ndarray, str], optional): + Array of `int` type corresponding to the index of the mask vector where + the value is egal to 1 + x_mask_coord (Union[np.ndarray, str], optional): + coordinates of the mask in the x direction x[0] and x[1] are the first + and last points respectively + y_mask_coord (Union[np.ndarray, str], optional): + coordinates of the mask in the y direction y[0] and y[1] are the first + and last points respectively + pattern_amount (int, optional): + Quantity of patterns sent to DMD for an acquisition. This value is + calculated by an external function. Default in None. + acquired_spectra (int, optional): + Amount of spectra actually read from the spectrometer. This value is + calculated by an external function. Default in None. + mean_callback_acquisition_time_ms (float, optional): + Mean time between 2 callback executions during an acquisition. This + value is calculated by an external function. Default in None. + total_callback_acquisition_time_s (float, optional): + Total time of callback executions during an acquisition. This value + is calculated by an external function. Default in None. + mean_spectrometer_acquisition_time_ms (float, optional): + Mean time between 2 spectrometer measurements during an acquisition + based on its own internal clock. This value is calculated by an + external function. Default in None. + total_spectrometer_acquisition_time_s (float, optional): + Total time of spectrometer measurements during an acquisition + based on its own internal clock. This value is calculated by an + external function. Default in None. + saturation_detected (bool, optional): + Boolean incating if saturation was detected during acquisition. + Default is None. + patterns (Union[List[int],str], optional) = None + List `int` or `str` containing all patterns sent to the DMD for an + acquisition sequence. This value is set by an external function and + its type can be modified by multiple functions during object + creation, manipulation, when dumping to a JSON file or + when reconstructing an AcquisitionParameters object from a JSON + file. It is intended to be of type List[int] most of the execution + List[int]time. Default is None. + wavelengths (Union[np.ndarray, str], optional): + Array of `float` type corresponding to the wavelengths associated + with spectrometer's start and stop pixels. + timestamps (Union[List[float], str], optional): + List of `float` type elapsed time between each measurement + made by the spectrometer based on its internal clock. Units in + milliseconds. Default is None. + measurement_time (Union[List[float], str], optional): + List of `float` type elapsed times between each callback. Units in + milliseconds. Default is None. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'Acquisition parameters'. + """ + + pattern_compression: float + pattern_dimension_x: int + pattern_dimension_y: int + zoom: Optional[int] = field(default=None) + xw_offset: Optional[int] = field(default=None) + yh_offset: Optional[int] = field(default=None) + mask_index: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + x_mask_coord: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + y_mask_coord: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + + pattern_amount: Optional[int] = None + acquired_spectra: Optional[int] = None + + mean_callback_acquisition_time_ms: Optional[float] = None + total_callback_acquisition_time_s: Optional[float] = None + mean_spectrometer_acquisition_time_ms: Optional[float] = None + total_spectrometer_acquisition_time_s: Optional[float] = None + + saturation_detected: Optional[bool] = None + + patterns: Optional[Union[List[int], str]] = field(default=None, repr=False) + patterns_wp: Optional[Union[List[int], str]] = field(default=None, repr=False) + wavelengths: Optional[Union[np.ndarray, str]] = field(default=None, + repr=False) + timestamps: Optional[Union[List[float], str]] = field(default=None, + repr=False) + measurement_time: Optional[Union[List[float], str]] = field(default=None, + repr=False) + + class_description: str = 'Acquisition parameters' + + + def undo_readable_pattern_order(self) -> None: + """Changes the patterns attribute from `str` to `List` of `int`. + + When reconstructing an AcquisitionParameters object from a JSON file, + this method turns the patterns, wavelengths, timestamps and + measurement_time attributes from a string to a list of integers + containing the pattern indices used in that acquisition. + """ + + def to_float(str_arr): + arr = [] + for s in str_arr: + try: + num = float(s) + arr.append(num) + except ValueError: + pass + return arr + + self.patterns = self.patterns.strip('[').strip(']').split(', ') + self.patterns = [int(s) for s in self.patterns if s.isdigit()] + try: + self.patterns_wp = self.patterns_wp.text.strip('[').strip(']').split(', ') + self.patterns_wp = [int(s) for s in self.patterns_wp if s.isdigit()] + except: + print('patterns_wp has no attribute ''strip''') + + if self.wavelengths: + self.wavelengths = ( + self.wavelengths.strip('[').strip(']').split(', ')) + self.wavelengths = to_float(self.wavelengths) + self.wavelengths = np.asarray(self.wavelengths) + else: + print('wavelenghts not present in metadata.' + ' Reading data in legacy mode.') + + if self.timestamps: + self.timestamps = self.timestamps.strip('[').strip(']').split(', ') + self.timestamps = to_float(self.timestamps) + else: + print('timestamps not present in metadata.' + ' Reading data in legacy mode.') + + if self.measurement_time: + self.measurement_time = ( + self.measurement_time.strip('[').strip(']').split(', ')) + self.measurement_time = to_float(self.measurement_time) + else: + print('measurement_time not present in metadata.' + ' Reading data in legacy mode.') + + if self.mask_index: + self.mask_index = ( + self.mask_index.strip('[').strip(']').split(', ')) + self.mask_index = to_float(self.mask_index) + self.mask_index = np.asarray(self.mask_index) + else: + print('mask_index not present in metadata.' + ' Reading data in legacy mode.') + + if self.x_mask_coord: + self.x_mask_coord = ( + self.x_mask_coord.strip('[').strip(']').split(', ')) + self.x_mask_coord = to_float(self.x_mask_coord) + self.x_mask_coord = np.asarray(self.x_mask_coord) + else: + print('x_mask_coord not present in metadata.' + ' Reading data in legacy mode.') + + if self.y_mask_coord: + self.y_mask_coord = ( + self.y_mask_coord.strip('[').strip(']').split(', ')) + self.y_mask_coord = to_float(self.y_mask_coord) + self.y_mask_coord = np.asarray(self.y_mask_coord) + else: + print('y_mask_coord not present in metadata.' + ' Reading data in legacy mode.') + + @staticmethod + def readable_pattern_order(acquisition_params_dict: dict) -> dict: + """Turns list of patterns into a string. + + Turns the list of pattern attributes from an AcquisitionParameters + object (turned into a dictionary) into a string that will improve + readability once all metadata is dumped into a JSON file. + This function must be called before dumping. + + Args: + acquisition_params_dict (dict): Dictionary obtained from converting + an AcquisitionParameters object. + + Returns: + [dict]: Modified dictionary with acquisition parameters metadata. + """ + + def _hard_coded_conversion(data): + s = '[' + for value in data: + s += f'{value:.4f}, ' + s = s[:-2] + s += ']' + + return s + + readable_dict = acquisition_params_dict + readable_dict['patterns'] = str(readable_dict['patterns']) + readable_dict['patterns_wp'] = str(readable_dict['patterns_wp']) + + readable_dict['wavelengths'] = _hard_coded_conversion( + readable_dict['wavelengths']) + + readable_dict['timestamps'] = _hard_coded_conversion( + readable_dict['timestamps']) + + readable_dict['measurement_time'] = _hard_coded_conversion( + readable_dict['measurement_time']) + + readable_dict['mask_index'] = _hard_coded_conversion( + readable_dict['mask_index']) + + readable_dict['x_mask_coord'] = _hard_coded_conversion( + readable_dict['x_mask_coord']) + + readable_dict['y_mask_coord'] = _hard_coded_conversion( + readable_dict['y_mask_coord']) + + return readable_dict + + + def update_timings(self, timestamps: np.ndarray, + measurement_time: np.ndarray): + """Updates acquisition timings. + + Args: + timestamps (ndarray): + Array of `float` type elapsed time between each measurement made + by the spectrometer based on its internal clock. Units in + milliseconds. + measurement_time (ndarray): + Array of `float` type elapsed times between each callback. Units + in milliseconds. + """ + self.mean_callback_acquisition_time_ms = np.mean(measurement_time) + self.total_callback_acquisition_time_s = np.sum(measurement_time) / 1000 + self.mean_spectrometer_acquisition_time_ms = np.mean( + timestamps, dtype=float) + self.total_spectrometer_acquisition_time_s = np.sum(timestamps) / 1000 + + self.timestamps = timestamps + self.measurement_time = measurement_time + + + +@dataclass_json +@dataclass +class SpectrometerParameters: + """Class containing spectrometer configurations. + + Further information: AvaSpec Library Manual (Version 9.10.2.0). + + Attributes: + high_resolution (bool): + True if 16-bit AD Converter is used. False if 14-bit ADC is used. + initial_available_pixels (int): + Number of pixels available in spectrometer. + detector (str): + Name of the light detector. + firmware_version (str, optional): + Spectrometer firmware version. + dll_version (str, optional): + Spectrometer dll version. + fpga_version (str, optional): + Internal FPGA version. + integration_delay_ns (int, optional): + Parameter used to start the integration time not immediately after + the measurement request (or on an external hardware trigger), but + after a specified delay. Unit is based on internal FPGA clock cycle. + integration_time_ms (float, optional): + Spectrometer exposure time during one scan in miliseconds. + start_pixel (int, optional): + Initial pixel data received from spectrometer. + stop_pixel (int, optional): + Last pixel data received from spectrometer. + averages (int, optional): + Number of averages in a single measurement. + dark_correction_enable (bool, optional): + Enable dynamic dark current correction. + dark_correction_forget_percentage (int, optional): + Percentage of the new dark value pixels that has to be used. e.g., + a percentage of 100 means only new dark values are used. A + percentage of 10 means that 10 percent of the new dark values is + used and 90 percent of the old values is used for drift correction. + smooth_pixels (int, optional): + Number of neighbor pixels used for smoothing, max. has to be smaller + than half the selected pixel range because both the pixels on the + left and on the right are used. + smooth_model (int, optional): + Smoothing model. Currently a single model is supported in which the + spectral data is averaged over a number of pixels on the detector + array. For example, if the smoothpix parameter is set to 2, the + spectral data for all pixels x(n) on the detector array will be + averaged with their neighbor pixels x(n-2), x(n-1), x(n+1) and + x(n+2). + saturation_detection (bool, optional): + Enable detection of saturation/overexposition in pixels. + trigger_mode (int, optional): + Trigger mode (0 = Software, 1 = Hardware, 2 = Single Scan). + trigger_source (int, optional): + Trigger source (0 = external trigger, 1 = sync input). + trigger_source_type (int, optional): + Trigger source type (0 = edge trigger, 1 = level trigger). + store_to_ram (int, optional): + Define how many scans can be stored in RAM. In DynamicRAM mode, can + be set to 0 to indicate infinite measurements. + configs: InitVar[MeasConfigType]: + Initialization object containing data to create SpectrometerData + object. Unnecessary if reconstructing object from JSON file Defaut + is None. + version_info: InitVar[Tuple[str]]: + Initialization variable used for receiving firmware, dll and FPGA + version data. Unnecessary if reconstructing object from JSON file. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'Spectrometer parameters'. + """ + + high_resolution: bool + initial_available_pixels: int + detector: str + firmware_version: Optional[str] = None + dll_version: Optional[str] = None + fpga_version: Optional[str] = None + + integration_delay_ns: Optional[int] = None + integration_time_ms: Optional[float] = None + + start_pixel: Optional[int] = None + stop_pixel: Optional[int] = None + averages: Optional[int] = None + + dark_correction_enable: Optional[bool] = None + dark_correction_forget_percentage: Optional[int] = None + + smooth_pixels: Optional[int] = None + smooth_model: Optional[int] = None + + saturation_detection: Optional[bool] = None + + trigger_mode: Optional[int] = None + trigger_source: Optional[int] = None + trigger_source_type: Optional[int] = None + + store_to_ram: Optional[int] = None + + configs: InitVar[MeasConfigType] = None + version_info: InitVar[Tuple[str]] = None + + class_description: str = 'Spectrometer parameters' + + + def __post_init__(self, configs: Optional[MeasConfigType] = None, + version_info: Optional[Tuple[str, str, str]] = None): + """Post initialization of attributes. + + Receives the data sent to spectrometer and some version data and unwraps + everything to set the majority of SpectrometerParameters's attributes. + During reconstruction from JSON, arguments of type InitVar (configs and + version_info) are set to None and the function does nothing, letting + initialization for the standard __init__ function. + + Args: + configs (MeasConfigType, optional): + Object containing configurations sent to spectrometer. + Defaults to None. + version_info (Tuple[str, str, str], optional): + Tuple containing firmware, dll and FPGA version data. Defaults + to None. + """ + if configs is None or version_info is None: + pass + + else: + self.fpga_version, self.firmware_version, self.dll_version = ( + version_info) + + self.integration_delay_ns = configs.m_IntegrationDelay + self.integration_time_ms = configs.m_IntegrationTime + + self.start_pixel = configs.m_StartPixel + self.stop_pixel = configs.m_StopPixel + self.averages = configs.m_NrAverages + + self.dark_correction_enable = configs.m_CorDynDark.m_Enable + self.dark_correction_forget_percentage = ( + configs.m_CorDynDark.m_ForgetPercentage) + + self.smooth_pixels = configs.m_Smoothing.m_SmoothPix + self.smooth_model = configs.m_Smoothing.m_SmoothModel + + self.saturation_detection = configs.m_SaturationDetection + + self.trigger_mode = configs.m_Trigger.m_Mode + self.trigger_source = configs.m_Trigger.m_Source + self.trigger_source_type = configs.m_Trigger.m_SourceType + + self.store_to_ram = configs.m_Control.m_StoreToRam + + +@dataclass_json +@dataclass +class DMDParameters: + """Class containing DMD configurations and status. + + Further information: ALP-4.2 API Description (14/04/2020). + + Attributes: + add_illumination_time_us (int): + Extra time in microseconds to account for the spectrometer's + "dead time". + initial_memory (int): + Initial memory available before sending patterns to DMD. + dark_phase_time_us (int, optional): + Time in microseconds taken by the DMD mirrors to completely tilt. + Minimum time for XGA type DMD is 44 us. + illumination_time_us (int, optional): + Duration of the display of one pattern in a DMD sequence. Units in + microseconds. + picture_time_us (int, optional): + Time between the start of two consecutive pictures (i.e. this + parameter defines the image display rate). Units in microseconds. + synch_pulse_width_us (int, optional): + Duration of DMD's frame synch output pulse. Units in microseconds. + synch_pulse_delay (int, optional): + Time in microseconds between start of the frame synch output pulse + and the start of the pattern display (in master mode). + device_number (int, optional): + Serial number of the ALP device. + ALP_version (int, optional): + Version number of the ALP device. + id (int, optional): + ALP device identifier for a DMD provided by the API. + synch_polarity (str, optional): + Frame synch output signal polarity: 'High' or 'Low.' + trigger_edge (str, optional): + Trigger input signal slope. Can be a 'Falling' or 'Rising' edge. + type (str, optional): + Digital light processing (DLP) chip present in DMD. + usb_connection (bool, optional): + True if USB connection is ok. + ddc_fpga_temperature (float, optional): + Temperature of the DDC FPGA (IC4) at DMD connection. Units in °C. + apps_fpga_temperature (float, optional): + Temperature of the Applications FPGA (IC3) at DMD connection. Units + in °C. + pcb_temperature (float, optional): + Internal temperature of the temperature sensor IC (IC2) at DMD + connection. Units in °C. + display_height (int, optional): + DMD display height in pixels. + display_width (int, optional): + DMD display width in pixels. + patterns (int, optional): + Number of patterns uploaded to DMD. + unused_memory (int, optional): + Memory available after sending patterns to DMD. + bitplanes (int, optional): + Bit depth of the patterns to be displayed. Values supported from 1 + to 8. + DMD (InitVar[ALP4.ALP4], optional): + Initialization DMD object. Can be used to automatically fill most of + the DMDParameters' attributes. Unnecessary if reconstructing object + from JSON file. Defaut is None. + class_description (str): + Class description used to improve redability when dumped to JSON + file. Default is 'DMD parameters'. + """ + + add_illumination_time_us: int + initial_memory: int + + dark_phase_time_us: Optional[int] = None + illumination_time_us: Optional[int] = None + picture_time_us: Optional[int] = None + synch_pulse_width_us: Optional[int] = None + synch_pulse_delay: Optional[int] = None + + device_number: Optional[int] = None + ALP_version: Optional[int] = None + id: Optional[int] = None + + synch_polarity: Optional[str] = None + trigger_edge: Optional[str] = None + + # synch_polarity_OUT1: Optional[str] = None + # synch_period_OUT1: Optional[str] = None + # synch_gate_OUT1: Optional[str] = None + + type: Optional[str] = None + usb_connection: Optional[bool] = None + + ddc_fpga_temperature: Optional[float] = None + apps_fpga_temperature: Optional[float] = None + pcb_temperature: Optional[float] = None + + display_height: Optional[int] = None + display_width: Optional[int] = None + + patterns: Optional[int] = None + patterns_wp: Optional[int] = None + unused_memory: Optional[int] = None + bitplanes: Optional[int] = None + + DMD: InitVar[ALP4.ALP4] = None + + class_description: str = 'DMD parameters' + + + def __post_init__(self, DMD: Optional[ALP4.ALP4] = None): + """ Post initialization of attributes. + + Receives a DMD object and directly asks it for its configurations and + status, then sets the majority of SpectrometerParameters's attributes. + During reconstruction from JSON, DMD is set to None and the function + does nothing, letting initialization for the standard __init__ function. + + Args: + DMD (ALP4.ALP4, optional): + Connected DMD. Defaults to None. + """ + if DMD == None: + pass + + else: + self.device_number = DMD.DevInquire(ALP4.ALP_DEVICE_NUMBER) + self.ALP_version = DMD.DevInquire(ALP4.ALP_VERSION) + self.id = DMD.ALP_ID.value + + polarity = DMD.DevInquire(ALP4.ALP_SYNCH_POLARITY) + if polarity == 2006: + self.synch_polarity = 'High' + elif polarity == 2007: + self.synch_polarity = 'Low' + + edge = DMD.DevInquire(ALP4.ALP_TRIGGER_EDGE) + if edge == 2008: + self.trigger_edge = 'Falling' + elif edge == 2009: + self.trigger_edge = 'Rising' + + # synch_polarity_OUT1 = + + self.type = DMDTypes(DMD.DevInquire(ALP4.ALP_DEV_DMDTYPE)) + + if DMD.DevInquire(ALP4.ALP_USB_CONNECTION) == 0: + self.usb_connection = True + else: + self.usb_connection = False + + # Temperatures converted to °C + self.ddc_fpga_temperature = DMD.DevInquire( + ALP4.ALP_DDC_FPGA_TEMPERATURE)/256 + self.apps_fpga_temperature = DMD.DevInquire( + ALP4.ALP_APPS_FPGA_TEMPERATURE)/256 + self.pcb_temperature = DMD.DevInquire( + ALP4.ALP_PCB_TEMPERATURE)/256 + + self.display_width = DMD.nSizeX + self.display_height = DMD.nSizeY + + + def update_memory(self, unused_memory: int): + + self.unused_memory = unused_memory + self.patterns = self.initial_memory - unused_memory + + + def update_sequence_parameters(self, add_illumination_time, + DMD: Optional[ALP4.ALP4] = None): + + self.bitplanes = DMD.SeqInquire(ALP4.ALP_BITPLANES) + self.illumination_time_us = DMD.SeqInquire(ALP4.ALP_ILLUMINATE_TIME) + self.picture_time_us = DMD.SeqInquire(ALP4.ALP_PICTURE_TIME) + self.dark_phase_time_us = self.picture_time_us - self.illumination_time_us + self.synch_pulse_width_us = DMD.SeqInquire(ALP4.ALP_SYNCH_PULSEWIDTH) + self.synch_pulse_delay = DMD.SeqInquire(ALP4.ALP_SYNCH_DELAY) + self.add_illumination_time_us = add_illumination_time + + + + + +def read_metadata(file_path: str) -> Tuple[MetaData, + AcquisitionParameters, + SpectrometerParameters, + DMDParameters]: + """Reads metadata of a previous acquisition from JSON file. + + Args: + file_path (str): + Name of JSON file containing all metadata. + + Returns: + Tuple[MetaData, AcquisitionParameters, SpectrometerParameters, + DMDParameters]: + saved_metadata (MetaData): + Metadata object read from JSON. + saved_acquisition_params(AcquisitionParameters): + AcquisitionParameters object read from JSON. + saved_spectrometer_params(SpectrometerParameters): + SpectrometerParameters object read from JSON. + saved_dmd_params(DMDParameters): + DMDParameters object read from JSON. + """ + + file = open(file_path,'r') + data = json.load(file) + file.close() + + for object in data: + if object['class_description'] == 'Metadata': + saved_metadata = MetaData.from_dict(object) + if object['class_description'] == 'Acquisition parameters': + saved_acquisition_params = AcquisitionParameters.from_dict(object) + saved_acquisition_params.undo_readable_pattern_order() + if object['class_description'] == 'Spectrometer parameters': + saved_spectrometer_params = SpectrometerParameters.from_dict(object) + if object['class_description'] == 'DMD parameters': + saved_dmd_params = DMDParameters.from_dict(object) + + return (saved_metadata, saved_acquisition_params, + saved_spectrometer_params, saved_dmd_params) + +def read_metadata_2arms(file_path: str) -> Tuple[MetaData, + AcquisitionParameters, + SpectrometerParameters, + DMDParameters, + CAM]: + """Reads metadata of a previous acquisition from JSON file. + + Args: + file_path (str): + Name of JSON file containing all metadata. + + Returns: + Tuple[MetaData, AcquisitionParameters, SpectrometerParameters, + DMDParameters]: + saved_metadata (MetaData): + Metadata object read from JSON. + saved_acquisition_params(AcquisitionParameters): + AcquisitionParameters object read from JSON. + saved_spectrometer_params(SpectrometerParameters): + SpectrometerParameters object read from JSON. + saved_dmd_params(DMDParameters): + DMDParameters object read from JSON. + """ + + file = open(file_path,'r') + data = json.load(file) + file.close() + + for object in data: + if object['class_description'] == 'Metadata': + saved_metadata = MetaData.from_dict(object) + if object['class_description'] == 'Acquisition parameters': + saved_acquisition_params = AcquisitionParameters.from_dict(object) + saved_acquisition_params.undo_readable_pattern_order() + if object['class_description'] == 'Spectrometer parameters': + saved_spectrometer_params = SpectrometerParameters.from_dict(object) + if object['class_description'] == 'DMD parameters': + saved_dmd_params = DMDParameters.from_dict(object) + if object['class_description'] == 'IDS camera configuration': + saved_cam_params = CAM.from_dict(object) + saved_cam_params.undo_readable_class_CAM() + + + return (saved_metadata, saved_acquisition_params, + saved_spectrometer_params, saved_dmd_params, saved_cam_params) + +def save_metadata(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + acquisition_parameters: AcquisitionParameters) -> None: + """Saves metadata to JSON file. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + Class containing DMD configurations and status. + spectrometer_params (SpectrometerParameters): + Object containing spectrometer configurations. + acquisition_parameters (AcquisitionParameters): + Object containing acquisition specifications and timing results. + """ + + path = Path(metadata.output_directory) + with open( + path / f'{metadata.experiment_name}_metadata.json', + 'w', encoding='utf8') as output: + + output_params = [ + metadata.to_dict(), + DMD_params.to_dict(), + spectrometer_params.to_dict(), + AcquisitionParameters.readable_pattern_order( + acquisition_parameters.to_dict())] + + json.dump(output_params,output,ensure_ascii=False,indent=4) + +def save_metadata_2arms(metadata: MetaData, + DMD_params: DMDParameters, + spectrometer_params: SpectrometerParameters, + camPar : CAM, + acquisition_parameters: AcquisitionParameters) -> None: + """Saves metadata to JSON file. + + Args: + metadata (MetaData): + Metadata concerning the experiment, paths, file inputs and file + outputs. + DMD_params (DMDParameters): + Class containing DMD configurations and status. + spectrometer_params (SpectrometerParameters): + Object containing spectrometer configurations. + acquisition_parameters (AcquisitionParameters): + Object containing acquisition specifications and timing results. + """ + + path = Path(metadata.output_directory) + with open( + path / f'{metadata.experiment_name}_metadata.json', + 'w', encoding='utf8') as output: + + output_params = [ + metadata.to_dict(), + DMD_params.to_dict(), + spectrometer_params.to_dict(), + CAM.readable_class_CAM(camPar.to_dict()), + AcquisitionParameters.readable_pattern_order(acquisition_parameters.to_dict())] + + json.dump(output_params, output, ensure_ascii=False, indent=4)#, default=convert) + + # with open(path / f'{metadata.experiment_name}_metadata_cam.pkl', 'wb') as f: + # pickle.dump(camPar.__dict__, f) + +@dataclass_json +@dataclass +class func_path: + def __init__(self, data_folder_name, data_name, ask_overwrite=False): + if not os.path.exists('../data/' + data_folder_name): + os.makedirs('../data/' + data_folder_name) + + if not os.path.exists('../data/' + data_folder_name + '/' + data_name): + os.makedirs('../data/' + data_folder_name + '/' + data_name) + aborted = False + elif ask_overwrite == True: + res = input('Acquisition already exists, overwrite it ?[y/n]') + if res == 'n': + aborted = True + else: + aborted = False + else: + aborted = True + + self.aborted = aborted + self.subfolder_path = '../data/' + data_folder_name + '/' + data_name + self.overview_path = self.subfolder_path + '/overview' + if not os.path.exists(self.overview_path): + os.makedirs(self.overview_path) + + self.data_name = data_name + self.data_path = self.subfolder_path + '/' + data_name + self.had_reco_path = self.data_path + '_had_reco.npz' + self.fig_had_reco_path = self.overview_path + '/' + data_name + self.pathIDSsnapshot = Path(self.data_path + '_IDScam_before_acq.npy') + self.pathIDSsnapshot_overview = self.overview_path + '/' + data_name + '_IDScam_before_acq.png' + self.nn_reco_path = self.data_path + '_nn_reco.npz' + self.fig_nn_reco_path = self.overview_path + '/' + data_name + + diff --git a/spas/reconstruction_SPC1D.py b/spas/reconstruction_SPC1D.py new file mode 100644 index 0000000..503dfa8 --- /dev/null +++ b/spas/reconstruction_SPC1D.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +__author__ = 'Guilherme Beneti Martins' + +import numpy as np +from spas.metadata_SPC1D import AcquisitionParameters + +def reconstruction_hadamard(acquisition_parameters: AcquisitionParameters, + mode: str, + Q: np.ndarray, + M: np.ndarray, + N: int = 64) -> np.ndarray: + """Reconstruct an image acquired with Hadamard patterns. + + Args: + acquisition_parameters (AcquisitionParameters): + Object containing acquisition specifications + mode (str): + Select if reconstruction is based on MATLAB, fht or Walsh generated + patterns. + Q (np.ndarray): + Acquisition matrix used to generate Hadamard patterns. + M (np.ndarray): + Spectral data matrix containing acquired spectra. + N (int, optional): + Reconstructed image dimension. Defaults to 64. + + Returns: + [np.ndarray]: + Reconstructed matrix of size NxN pixels. + """ + + patterns = acquisition_parameters.patterns + + if mode == 'matlab': + ind_opt = patterns[1::2] + if mode == 'fht' or mode == 'walsh': + ind_opt = patterns[0::2] + + ind_opt = np.array(ind_opt)/2 + + if mode == 'matlab': + ind_opt = ind_opt - 1 + + ind_opt = ind_opt.astype('int') + M_breve = M[0::2,:] - M[1::2,:] + M_Had = np.zeros((N*N, M.shape[1])) + M_Had[ind_opt,:] = M_breve + + f = np.matmul(Q,M_Had) # Q.T = Q + frames = np.reshape(f,(N,N,M.shape[1])) + frames /= N*N + + mask_index = acquisition_parameters.mask_index + if len(mask_index) > 0: + x_mask_coord = acquisition_parameters.x_mask_coord + y_mask_coord = acquisition_parameters.y_mask_coord + x_mask_length = x_mask_coord[1] - x_mask_coord[0] + y_mask_length = y_mask_coord[1] - y_mask_coord[0] + + GTnew_vec = np.zeros((x_mask_length*y_mask_length, frames.shape[2])) + GT_vec = frames.reshape(-1, frames.shape[-1]) + + GTnew_vec[mask_index,:] = GT_vec[:len(mask_index),:] + frames = np.reshape(GTnew_vec, (y_mask_length, x_mask_length, frames.shape[2])) + + return frames + + +def reconstruction_raster(M: np.ndarray, N: int = 64) -> np.ndarray: + """Reconstruct an image obtained via Raster scan. + + Args: + M (np.ndarray): + Spectral data matrix containing acquired spectra. + N (int, optional): + Reconstructed image dimension. Defaults to 64. + + Returns: + np.ndarray: + Reconstructed matrix of size NxN pixels. + """ + return np.reshape(M,(N,N,M.shape[1])) \ No newline at end of file diff --git a/spas/reconstruction_SPC2D.py b/spas/reconstruction_SPC2D.py index 291649e..d659898 100644 --- a/spas/reconstruction_SPC2D.py +++ b/spas/reconstruction_SPC2D.py @@ -2,7 +2,7 @@ __author__ = 'Guilherme Beneti Martins' import numpy as np -from spas.metadata import AcquisitionParameters +from spas.metadata_SPC2D import AcquisitionParameters def reconstruction_hadamard(acquisition_parameters: AcquisitionParameters, mode: str,