diff --git a/jupyter/Test_hardwares/UC480_camera_test.ipynb b/jupyter/Test_hardwares/UC480_camera_test.ipynb new file mode 100644 index 0000000..1c2a88b --- /dev/null +++ b/jupyter/Test_hardwares/UC480_camera_test.ipynb @@ -0,0 +1,261 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import ctypes\n", + "import ctypes.util\n", + "import ctypes.wintypes\n", + "import numpy\n", + "import os\n", + "import sys\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Volume in drive C is OS\n", + " Volume Serial Number is 66FF-ED43\n", + "\n", + " Directory of c:\\Softwares\\Github\\MerScope01\\storm_control\\sc_hardware\\thorlabs\n", + "\n", + "07/17/2023 03:09 AM .\n", + "07/17/2023 03:09 AM ..\n", + "03/28/2023 08:51 PM 19 __init__.py\n", + "07/17/2023 03:36 AM __pycache__\n", + "03/28/2023 08:51 PM 3,044 FW102C.py\n", + "03/28/2023 08:51 PM 2,093 FW102CModule.py\n", + "03/28/2023 08:51 PM 1,923 installing_pyAPT.txt\n", + "03/28/2023 08:51 PM 5,432 LDC210.py\n", + "03/28/2023 08:51 PM 2,385 LDCModule.py\n", + "03/28/2023 08:51 PM 2,487 mgmotorAX.py\n", + "03/28/2023 08:51 PM 7,282 PDQ80S1.py\n", + "03/28/2023 08:51 PM 3,339 PM100.py\n", + "03/28/2023 08:51 PM 3,275 settings.tcp\n", + "03/28/2023 08:51 PM 2,630 TPZ001.py\n", + "06/11/2021 05:51 PM 10,068,008 uc480_64.dll\n", + "07/17/2023 02:49 AM 2,926 uc480_settings.ini\n", + "07/17/2023 03:18 AM 27,221 uc480Camera.py\n", + "03/28/2023 08:51 PM 9,411 uc480CameraModule.py\n", + " 15 File(s) 10,141,475 bytes\n", + " 3 Dir(s) 695,261,057,024 bytes free\n" + ] + } + ], + "source": [ + "ls ..\\..\\storm_control\\sc_hardware\\thorlabs" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "sys.path.append(r\"..\\..\\storm_control\\sc_hardware\\thorlabs\")\n", + "sys.path.append(r\"..\\..\")\n", + "from uc480Camera import Camera, loadDLL" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "global uc480\n", + "loadDLL(r\"C:\\Program Files\\Thorlabs\\Scientific Imaging\\ThorCam\\uc480_64.dll\")" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "ename": "OSError", + "evalue": "cannot load library 'C:\\Windows\\system32\\uc480_64.dll': error 0x45a", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mOSError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[24], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39minstrumental\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mdrivers\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mcameras\u001b[39;00m \u001b[39mimport\u001b[39;00m uc480\n", + "File \u001b[1;32mc:\\Users\\Weissman_Lab\\.conda\\envs\\halenv\\lib\\site-packages\\instrumental\\drivers\\cameras\\uc480.py:32\u001b[0m\n\u001b[0;32m 29\u001b[0m _INST_PARAMS \u001b[39m=\u001b[39m [\u001b[39m'\u001b[39m\u001b[39mserial\u001b[39m\u001b[39m'\u001b[39m, \u001b[39m'\u001b[39m\u001b[39mid\u001b[39m\u001b[39m'\u001b[39m, \u001b[39m'\u001b[39m\u001b[39mmodel\u001b[39m\u001b[39m'\u001b[39m]\n\u001b[0;32m 30\u001b[0m _INST_CLASSES \u001b[39m=\u001b[39m [\u001b[39m'\u001b[39m\u001b[39mUC480_Camera\u001b[39m\u001b[39m'\u001b[39m]\n\u001b[1;32m---> 32\u001b[0m info \u001b[39m=\u001b[39m load_lib(\u001b[39m'\u001b[39;49m\u001b[39muc480\u001b[39;49m\u001b[39m'\u001b[39;49m, __package__)\n\u001b[0;32m 33\u001b[0m ffi \u001b[39m=\u001b[39m info\u001b[39m.\u001b[39m_ffi\n\u001b[0;32m 35\u001b[0m __all__ \u001b[39m=\u001b[39m [\u001b[39m'\u001b[39m\u001b[39mUC480_Camera\u001b[39m\u001b[39m'\u001b[39m]\n", + "File \u001b[1;32mc:\\Users\\Weissman_Lab\\.conda\\envs\\halenv\\lib\\site-packages\\nicelib\\__init__.py:69\u001b[0m, in \u001b[0;36mload_lib\u001b[1;34m(name, pkg, dir, builder, kwargs)\u001b[0m\n\u001b[0;32m 67\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m 68\u001b[0m log\u001b[39m.\u001b[39minfo(\u001b[39m'\u001b[39m\u001b[39mLoading \u001b[39m\u001b[39m%s\u001b[39;00m\u001b[39m from \u001b[39m\u001b[39m%s\u001b[39;00m\u001b[39m...\u001b[39m\u001b[39m'\u001b[39m, lib_name, pkg)\n\u001b[1;32m---> 69\u001b[0m lib_module \u001b[39m=\u001b[39m import_module(lib_name, pkg)\n\u001b[0;32m 70\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mImportError\u001b[39;00m:\n\u001b[0;32m 71\u001b[0m \u001b[39mif\u001b[39;00m builder \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n", + "File \u001b[1;32mc:\\Users\\Weissman_Lab\\.conda\\envs\\halenv\\lib\\importlib\\__init__.py:126\u001b[0m, in \u001b[0;36mimport_module\u001b[1;34m(name, package)\u001b[0m\n\u001b[0;32m 124\u001b[0m \u001b[39mbreak\u001b[39;00m\n\u001b[0;32m 125\u001b[0m level \u001b[39m+\u001b[39m\u001b[39m=\u001b[39m \u001b[39m1\u001b[39m\n\u001b[1;32m--> 126\u001b[0m \u001b[39mreturn\u001b[39;00m _bootstrap\u001b[39m.\u001b[39;49m_gcd_import(name[level:], package, level)\n", + "File \u001b[1;32mc:\\Users\\Weissman_Lab\\.conda\\envs\\halenv\\lib\\site-packages\\instrumental\\drivers\\cameras\\_uc480lib.py:21\u001b[0m\n\u001b[0;32m 19\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39m'\u001b[39m\u001b[39mC:\u001b[39m\u001b[39m\\\\\u001b[39;00m\u001b[39mWindows\u001b[39m\u001b[39m\\\\\u001b[39;00m\u001b[39msystem32\u001b[39m\u001b[39m'\u001b[39m:\n\u001b[0;32m 20\u001b[0m os\u001b[39m.\u001b[39mchdir(\u001b[39m'\u001b[39m\u001b[39mC:\u001b[39m\u001b[39m\\\\\u001b[39;00m\u001b[39mWindows\u001b[39m\u001b[39m\\\\\u001b[39;00m\u001b[39msystem32\u001b[39m\u001b[39m'\u001b[39m)\n\u001b[1;32m---> 21\u001b[0m lib \u001b[39m=\u001b[39m ffi\u001b[39m.\u001b[39;49mdlopen(\u001b[39m'\u001b[39;49m\u001b[39mC:\u001b[39;49m\u001b[39m\\\\\u001b[39;49;00m\u001b[39mWindows\u001b[39;49m\u001b[39m\\\\\u001b[39;49;00m\u001b[39msystem32\u001b[39;49m\u001b[39m\\\\\u001b[39;49;00m\u001b[39muc480_64.dll\u001b[39;49m\u001b[39m'\u001b[39;49m)\n\u001b[0;32m 22\u001b[0m os\u001b[39m.\u001b[39mchdir(_old_curdir)\n\u001b[0;32m 24\u001b[0m \u001b[39m# Generated macro definitions\u001b[39;00m\n", + "\u001b[1;31mOSError\u001b[0m: cannot load library 'C:\\Windows\\system32\\uc480_64.dll': error 0x45a" + ] + } + ], + "source": [ + "from instrumental.drivers.cameras import uc480\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "ename": "OSError", + "evalue": "cannot load library 'C:\\Windows\\system32\\uc480_64.dll': error 0x45a", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mOSError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[23], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39minstrumental\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mdrivers\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mcameras\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39muc480\u001b[39;00m \u001b[39mimport\u001b[39;00m UC480_Camera\n", + "File \u001b[1;32mc:\\Users\\Weissman_Lab\\.conda\\envs\\halenv\\lib\\site-packages\\instrumental\\drivers\\cameras\\uc480.py:32\u001b[0m\n\u001b[0;32m 29\u001b[0m _INST_PARAMS \u001b[39m=\u001b[39m [\u001b[39m'\u001b[39m\u001b[39mserial\u001b[39m\u001b[39m'\u001b[39m, \u001b[39m'\u001b[39m\u001b[39mid\u001b[39m\u001b[39m'\u001b[39m, \u001b[39m'\u001b[39m\u001b[39mmodel\u001b[39m\u001b[39m'\u001b[39m]\n\u001b[0;32m 30\u001b[0m _INST_CLASSES \u001b[39m=\u001b[39m [\u001b[39m'\u001b[39m\u001b[39mUC480_Camera\u001b[39m\u001b[39m'\u001b[39m]\n\u001b[1;32m---> 32\u001b[0m info \u001b[39m=\u001b[39m load_lib(\u001b[39m'\u001b[39;49m\u001b[39muc480\u001b[39;49m\u001b[39m'\u001b[39;49m, __package__)\n\u001b[0;32m 33\u001b[0m ffi \u001b[39m=\u001b[39m info\u001b[39m.\u001b[39m_ffi\n\u001b[0;32m 35\u001b[0m __all__ \u001b[39m=\u001b[39m [\u001b[39m'\u001b[39m\u001b[39mUC480_Camera\u001b[39m\u001b[39m'\u001b[39m]\n", + "File \u001b[1;32mc:\\Users\\Weissman_Lab\\.conda\\envs\\halenv\\lib\\site-packages\\nicelib\\__init__.py:69\u001b[0m, in \u001b[0;36mload_lib\u001b[1;34m(name, pkg, dir, builder, kwargs)\u001b[0m\n\u001b[0;32m 67\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m 68\u001b[0m log\u001b[39m.\u001b[39minfo(\u001b[39m'\u001b[39m\u001b[39mLoading \u001b[39m\u001b[39m%s\u001b[39;00m\u001b[39m from \u001b[39m\u001b[39m%s\u001b[39;00m\u001b[39m...\u001b[39m\u001b[39m'\u001b[39m, lib_name, pkg)\n\u001b[1;32m---> 69\u001b[0m lib_module \u001b[39m=\u001b[39m import_module(lib_name, pkg)\n\u001b[0;32m 70\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mImportError\u001b[39;00m:\n\u001b[0;32m 71\u001b[0m \u001b[39mif\u001b[39;00m builder \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n", + "File \u001b[1;32mc:\\Users\\Weissman_Lab\\.conda\\envs\\halenv\\lib\\importlib\\__init__.py:126\u001b[0m, in \u001b[0;36mimport_module\u001b[1;34m(name, package)\u001b[0m\n\u001b[0;32m 124\u001b[0m \u001b[39mbreak\u001b[39;00m\n\u001b[0;32m 125\u001b[0m level \u001b[39m+\u001b[39m\u001b[39m=\u001b[39m \u001b[39m1\u001b[39m\n\u001b[1;32m--> 126\u001b[0m \u001b[39mreturn\u001b[39;00m _bootstrap\u001b[39m.\u001b[39;49m_gcd_import(name[level:], package, level)\n", + "File \u001b[1;32mc:\\Users\\Weissman_Lab\\.conda\\envs\\halenv\\lib\\site-packages\\instrumental\\drivers\\cameras\\_uc480lib.py:21\u001b[0m\n\u001b[0;32m 19\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39m'\u001b[39m\u001b[39mC:\u001b[39m\u001b[39m\\\\\u001b[39;00m\u001b[39mWindows\u001b[39m\u001b[39m\\\\\u001b[39;00m\u001b[39msystem32\u001b[39m\u001b[39m'\u001b[39m:\n\u001b[0;32m 20\u001b[0m os\u001b[39m.\u001b[39mchdir(\u001b[39m'\u001b[39m\u001b[39mC:\u001b[39m\u001b[39m\\\\\u001b[39;00m\u001b[39mWindows\u001b[39m\u001b[39m\\\\\u001b[39;00m\u001b[39msystem32\u001b[39m\u001b[39m'\u001b[39m)\n\u001b[1;32m---> 21\u001b[0m lib \u001b[39m=\u001b[39m ffi\u001b[39m.\u001b[39;49mdlopen(\u001b[39m'\u001b[39;49m\u001b[39mC:\u001b[39;49m\u001b[39m\\\\\u001b[39;49;00m\u001b[39mWindows\u001b[39;49m\u001b[39m\\\\\u001b[39;49;00m\u001b[39msystem32\u001b[39;49m\u001b[39m\\\\\u001b[39;49;00m\u001b[39muc480_64.dll\u001b[39;49m\u001b[39m'\u001b[39;49m)\n\u001b[0;32m 22\u001b[0m os\u001b[39m.\u001b[39mchdir(_old_curdir)\n\u001b[0;32m 24\u001b[0m \u001b[39m# Generated macro definitions\u001b[39;00m\n", + "\u001b[1;31mOSError\u001b[0m: cannot load library 'C:\\Windows\\system32\\uc480_64.dll': error 0x45a" + ] + } + ], + "source": [ + "from instrumental.drivers.cameras.uc480 import UC480_Camera\n", + "a" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "uc480: Call failed with error 3 is_InitCamera\n", + "uc480: Call failed with error 1 is_GetSensorInfo\n", + "uc480: Call failed with error 1 is_SetColorMode\n", + "uc480: Call failed with error 1 is_SetGainBoost\n", + "uc480: Call failed with error 1 is_SetGamma\n", + "uc480: Call failed with error 1 is_SetHardwareGain\n", + "uc480 used default settings.\n", + "uc480: Call failed with error 1 is_AllocImageMem\n", + "uc480: Call failed with error 1 is_SetImageMem\n" + ] + } + ], + "source": [ + "cam = Camera(1)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "uc480: Call failed with error 1 is_SetFrameRate\n", + "1000\n", + "c_double(0.0)\n", + "uc480: Set frame rate to 0.00 FPS\n" + ] + } + ], + "source": [ + "cam.setFrameRate(verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'cam' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[26], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m cam\u001b[39m.\u001b[39mis_InitCamera()\n", + "\u001b[1;31mNameError\u001b[0m: name 'cam' is not defined" + ] + } + ], + "source": [ + "cam.is_InitCamera()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ctypes.CDLL" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(uc480)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "halenv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/storm_control/hal4000/xml/merscope01_WI.xml b/storm_control/hal4000/xml/merscope01_WI.xml index 0bd7feb..ced2901 100644 --- a/storm_control/hal4000/xml/merscope01_WI.xml +++ b/storm_control/hal4000/xml/merscope01_WI.xml @@ -169,31 +169,31 @@ Other modules will request them with a 'get functionality' message and "name" = "daq.xxx.yyy". --> - + /Dev1/port0/line5 - + - + /Dev1/port0/line4 - + - + /Dev1/port0/line3 - + - + /Dev1/port0/line2 - + @@ -201,6 +201,17 @@ + + + /Dev1/port0/line6 + + + + + /Dev1/port0/line0 + + + /Dev1/ao0 @@ -217,9 +228,6 @@ - - - @@ -230,56 +238,76 @@ - 750 - 200,0,0 + 748 + 148,0,0 - Lumencor750 + Lumencor748 - daq.ilm750.do_task + daq.ilm748.do_task - 647 - 255,0,0 + 637 + 255,31,0 - Lumencor647 + Lumencor637 - daq.ilm647.do_task + daq.ilm637.do_task - 561 - 255,255,0 + 545 + 180,255,0 - Lumencor561 + Lumencor545 - daq.ilm561.do_task + daq.ilm545.do_task - - 488 - 0,255,255 + 518 + 29,255,0 - Lumencor488 + Lumencor518 - daq.ilm488.do_task + daq.ilm518.do_task + 477 + 0,189,255 + + Lumencor477 + + + daq.ilm477.do_task + + + + 446 + 0,31,255 + + Lumencor446 + + + daq.ilm446.do_task + + + 405 - 255,0,255 + 110,0,188 Lumencor405 daq.ilm405.do_task - + + @@ -293,9 +321,9 @@ True - - - + + + storm_control.sc_hardware.lumencor.LumencorModule Celesta @@ -303,10 +331,9 @@ 1 True - - - - + + + storm_control.sc_hardware.lumencor.LumencorModule Celesta @@ -314,10 +341,9 @@ 2 True - - - - + + + storm_control.sc_hardware.lumencor.LumencorModule Celesta @@ -325,10 +351,9 @@ 3 True - - - - + + + storm_control.sc_hardware.lumencor.LumencorModule Celesta @@ -336,9 +361,31 @@ 4 True - + + + + + storm_control.sc_hardware.lumencor.LumencorModule + Celesta + + 192.168.201.200 + 5 + True + + + + + + storm_control.sc_hardware.lumencor.LumencorModule + Celesta + + 192.168.201.200 + 6 + True + + - + storm_control.sc_hardware.ludl.ludlModule LudlStageRS232 @@ -376,34 +423,35 @@ - - UC480Camera - storm_control.sc_hardware.thorlabs.uc480CameraModule - - - True - 1.5 - 0.2 - 1.45 - 0.25 - 240000.0 - 10000.0 - 0.0 - - 32000 - 1 - uc480_settings.ini - C:\Softwares\Github\MerScope01\storm_control\hal4000\data\cam_offsets_merscope01.txt - 25 - 4.0 - C:\Program Files\Thorlabs\Scientific Imaging\DCx Camera Support\OtherDrivers\LabVIEW\For_64-bit_LabVIEW\uc480_64.dll - 320 - 80 - -0.03 - + + UC480Camera + storm_control.sc_hardware.thorlabs.ucScientificCameraModule + + + True + 1.5 + 0.2 + 1.45 + 0.25 + 240000.0 + 10000.0 + 0.0 + + 32000 + 1 + uc480_settings_new.ini + + C:\Softwares\Github\MerScope01\storm_control\hal4000\data\cam_offsets_merscope01.txt + 25 + 4.0 + C:\Program Files\Thorlabs\Scientific Imaging\DCx Camera Support\USB Driver Package\uc480_64.dll + 1000 + 800 + -0.03 + True - + diff --git a/storm_control/sc_hardware/lumencor/celesta.py b/storm_control/sc_hardware/lumencor/celesta.py index c1e6cc5..9418b6b 100644 --- a/storm_control/sc_hardware/lumencor/celesta.py +++ b/storm_control/sc_hardware/lumencor/celesta.py @@ -122,7 +122,7 @@ def setLaserOnOff(self, on): else: self.message = lumencor_httpcommand(command = 'SET CH '+self.laser_id+' 0', ip=self.ip) self.on = False - print("Turning On/Off", self.on, self.message) + #print("Turning On/Off", self.on, self.message) def setPower(self, power_in_mw): """ power_in_mw - The desired laser power in mW. diff --git a/storm_control/sc_hardware/thorlabs/Readme.md b/storm_control/sc_hardware/thorlabs/Readme.md new file mode 100644 index 0000000..ac4bee7 --- /dev/null +++ b/storm_control/sc_hardware/thorlabs/Readme.md @@ -0,0 +1,3 @@ +# Download ThorCam software +# Decompress C:\Program Files\Thorlabs\Scientific Imaging\Scientific Camera Support\Scientific Camera Interfaces\SDK\Python Toolkit\thorlabs_tsi_sdk-0.0.8 +# copy the folder to this directory \ No newline at end of file diff --git a/storm_control/sc_hardware/thorlabs/thorlabs_tsi_sdk-0.0.8.zip b/storm_control/sc_hardware/thorlabs/thorlabs_tsi_sdk-0.0.8.zip new file mode 100644 index 0000000..ded411a Binary files /dev/null and b/storm_control/sc_hardware/thorlabs/thorlabs_tsi_sdk-0.0.8.zip differ diff --git a/storm_control/sc_hardware/thorlabs/uc480Camera.py b/storm_control/sc_hardware/thorlabs/uc480Camera.py index 55915fa..b103207 100644 --- a/storm_control/sc_hardware/thorlabs/uc480Camera.py +++ b/storm_control/sc_hardware/thorlabs/uc480Camera.py @@ -16,9 +16,9 @@ import ctypes.wintypes import numpy import os - +import sys import time - +sys.path.append(r"..\..\..") import storm_control.sc_library.hdebug as hdebug # Import fitting libraries. @@ -268,8 +268,10 @@ def setFrameRate(self, frame_rate = 1000, verbose = False): ctypes.c_double(frame_rate), ctypes.byref(new_fps)), "is_SetFrameRate") + print(frame_rate) + print(new_fps) if verbose: - print("uc480: Set frame rate to {0:.1f} FPS".format(new_fps.value)) + print("uc480: Set frame rate to {0:.2f} FPS".format(new_fps.value)) def setPixelClock(self, pixel_clock_MHz): """ @@ -705,13 +707,15 @@ def fitGaussian(self, data): from PIL import Image - loadDLL("uc480_64.dll") + loadDLL(r"C:\Program Files\Thorlabs\Scientific Imaging\ThorCam\uc480_64.dll") cam = Camera(1) - reps = 1000 + reps = 100 if True: + print("- set area of interest: ") cam.setAOI(772, 566, 200, 200) + print("- set frame rate: ") cam.setFrameRate(verbose = True) for i in range(5): print("start", i) diff --git a/storm_control/sc_hardware/thorlabs/ucScientificCamera.py b/storm_control/sc_hardware/thorlabs/ucScientificCamera.py new file mode 100644 index 0000000..8b65821 --- /dev/null +++ b/storm_control/sc_hardware/thorlabs/ucScientificCamera.py @@ -0,0 +1,715 @@ +#!/usr/bin/env python +""" +Captures pictures from a Thorlabs Scientific series cameras (such as Zelux). +This follows the structure of uc480 written by Hazen + +Run python setup.py install inside: Temp1_Scientific_Camera_Interfaces-Rev_H.zip\Scientific Camera Interfaces\SDK\Python Compact Scientific Camera Toolkit\thorlabs_tsi_camera_python_sdk_package.zip\thorlabs_tsi_sdk-0.0.5 +Also added this to enviromental paths: C:\Program Files\Thorlabs\Scientific Imaging\ThorCam + +Bogdan 1/21 +""" +from thorlabs_tsi_sdk.tl_camera import TLCameraSDK +import ctypes +import ctypes.util +import ctypes.wintypes +import numpy +import os, sys +sys.path.append(r"..\..\..") + +import time + +import storm_control.sc_library.hdebug as hdebug + +# Import fitting libraries. + +# Numpy fitter, this should always be available. +import storm_control.sc_hardware.utility.np_lock_peak_finder as npLPF + +# Finding/fitting using the storm-analysis project. +saLPF = None +try: + import storm_control.sc_hardware.utility.sa_lock_peak_finder as saLPF +except ModuleNotFoundError as mnfe: + print(">> Warning! Storm analysis lock fitting module not found. <<") + print(mnfe) + pass + +# Finding using the storm-analysis project, fitting using image correlation. +cl2DG = None +try: + import storm_control.sc_hardware.utility.corr_lock_c2dg as cl2DG +except ModuleNotFoundError as mnfe: + # Only need one warning about the lack of storm-analysis. + pass +except OSError as ose: + print(">> Warning! Correlation lock fitting C library not found. <<") + print(ose) + pass + +Handle = ctypes.wintypes.HANDLE +class CameraInfo(ctypes.Structure): + """ + The uc480 camera info structure. + """ + _fields_ = [("CameraID", ctypes.wintypes.DWORD), + ("DeviceID", ctypes.wintypes.DWORD), + ("SensorID", ctypes.wintypes.DWORD), + ("InUse", ctypes.wintypes.DWORD), + ("SerNo", ctypes.c_char * 16), + ("Model", ctypes.c_char * 16), + ("Reserved", ctypes.wintypes.DWORD * 16)] + +class CameraProperties(ctypes.Structure): + """ + The uc480 camera properties structure. + """ + _fields_ = [("SensorID", ctypes.wintypes.WORD), + ("strSensorName", ctypes.c_char * 32), + ("nColorMode", ctypes.c_char), + ("nMaxWidth", ctypes.wintypes.DWORD), + ("nMaxHeight", ctypes.wintypes.DWORD), + ("bMasterGain", ctypes.wintypes.BOOL), + ("bRGain", ctypes.wintypes.BOOL), + ("bGGain", ctypes.wintypes.BOOL), + ("bBGain", ctypes.wintypes.BOOL), + ("bGlobShutter", ctypes.wintypes.BOOL), + ("Reserved", ctypes.c_char * 16)] + +class AOIRect(ctypes.Structure): + """ + The uc480 camera AOI structure. + """ + _fields_ = [("s32X", ctypes.wintypes.INT), + ("s32Y", ctypes.wintypes.INT), + ("s32Width", ctypes.wintypes.INT), + ("s32Height", ctypes.wintypes.INT)] + + + +def loadDLL(dll_name): + pass + + + +class Camera(Handle): + """ + UC480 Camera Interface Class + """ + def __init__(self, camera_id, ini_file = "uc480_settings.ini"): + super().__init__(camera_id) + # Initialize sdk and camera. + self.camera_id = int(camera_id)-1 #camera id in the list + self.sdk = TLCameraSDK() #remember to dispose + available_cameras = self.sdk.discover_available_cameras() + print(available_cameras) + self.camera = self.sdk.open_camera(available_cameras[self.camera_id]) + + # Get some information about the camera. + self.info = CameraProperties() + self.info.nMaxWidth = self.camera.image_width_pixels + self.info.nMaxHeight = self.camera.image_height_pixels + + self.im_width = self.info.nMaxWidth + self.im_height = self.info.nMaxHeight + + # Initialize some general camera settings. + self.camera.exposure_time_us = 7000 # set exposure time to 7 ms #30000 # set exposure to 30 ms + self.camera.frames_per_trigger_zero_for_unlimited = 0 # start camera in continuous mode + self.timeout = 1000 + self.camera.image_poll_timeout_ms = self.timeout # 1 second polling timeout + + # Setup capture parameters. + self.bitpixel = 8 # This is correct for a BW camera anyway.. + self.cur_frame = 0 + self.data = False + self.id = 0 + self.image = False + self.running = False + self.disposed=False + self.setBuffers() + def captureImage(self): + """ + Wait for the next frame from the camera, then call self.getImage(). + """ + return self.getImage() + + def captureImageTest(self): + """ + For testing.. + """ + pass + + def getCameraStatus(self, status_code): + return self.camera.is_armed + + def getImage(self): + """ + Copy an image from the camera into self.data and return self.data + """ + self.startCapture() + frame=None + if self.camera.is_armed and not self.disposed: + frame = self.camera.get_pending_frame_or_null() + if frame is not None: + self.data = numpy.copy(frame.image_buffer) + self.data = numpy.clip(self.data,0,255).astype(numpy.uint8) + return self.data + + def getNextImage(self): + """ + Waits until an image is available from the camera, then + call self.getImage() to return the new image. + """ + self.cur_frame += 1 + return self.getImage() + + def getSensorInfo(self): + return self.info + + def getTimeout(self): + return self.timeout + + def loadParameters(self, filename): + pass + + def saveParameters(self, filename): + """ + Save the current camera settings to a file. + """ + pass + + def setAOI(self, x_start, y_start, width, height): + x_start = int(x_start) + y_start = int(y_start) + self.stopCapture() + self.camera.roi = (x_start, y_start, int(x_start+width), int(y_start+height)) + self.im_width = self.camera.image_width_pixels + self.im_height = self.camera.image_height_pixels + self.setBuffers() + self.startCapture() + + def setBuffers(self): + """ + Based on the AOI, create the internal buffer that the camera will use and + the intermediate buffer that we will copy the data from the camera into. + """ + self.data = numpy.zeros((self.im_height, self.im_width), dtype = numpy.uint8) + + def setFrameRate(self, frame_rate = 150., verbose = False): + self.stopCapture() + self.camera.exposure_time_us =int(1000000./frame_rate)+1 + if verbose: + print("uc480: Set frame rate to {0:.1f} FPS".format(frame_rate)) + self.startCapture() + def setPixelClock(self, pixel_clock_MHz): + """ + 43MHz seems to be the max for this camera? + """ + pass + + def setTimeout(self, timeout): + self.stopCapture() + self.timeout = timeout + self.camera.image_poll_timeout_ms = timeout # 1 second polling timeout + self.startCapture() + def shutDown(self): + """ + Shut down the camera. + """ + if self.camera.is_armed: + self.camera.disarm() + self.camera.dispose() + self.sdk.dispose() + self.disposed=True + def startCapture(self): + """ + Start video capture (as opposed to single frame capture, which is done with self.captureImage(). + """ + if not self.camera.is_armed and not self.disposed: + self.camera.arm(2) + self.camera.issue_software_trigger() + + def stopCapture(self): + """ + Stop video capture. + """ + if self.camera.is_armed: + self.camera.disarm() + + +class CameraQPD(object): + """ + QPD emulation class. The default camera ROI of 200x200 pixels. + The focus lock is configured so that there are two laser spots on the camera. + The distance between these spots is fit and the difference between this distance and the + zero distance is returned as the focus lock offset. The maximum value of the camera + pixels is returned as the focus lock sum. + """ + def __init__(self, + allow_single_fits = False, + background = None, + camera_id = 1, + ini_file = None, + offset_file = None, + pixel_clock = None, + sigma = None, + x_width = None, + y_width = None, + **kwds): + super().__init__(**kwds) + + self.allow_single_fits = allow_single_fits + self.background = background + self.fit_mode = 1 + self.fit_size = int(1.5 * sigma) + self.image = None + self.last_power = 0 + self.offset_file = offset_file + self.sigma = sigma + self.x_off1 = 0.0 + self.y_off1 = 0.0 + self.x_off2 = 0.0 + self.y_off2 = 0.0 + self.zero_dist = 0.5 * x_width + + # Add path information to files that should be in the same directory. + ini_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ini_file) + + # Open camera + self.cam = Camera(camera_id, ini_file = ini_file) + + # Set timeout + #self.cam.setTimeout(1000) + + # Set camera AOI x_start, y_start. + with open(self.offset_file) as fp: + [self.x_start, self.y_start] = map(int, fp.readline().split(",")[:2]) + + # Set camera AOI. + self.x_width = x_width + self.y_width = y_width + self.setAOI() + + # Run at maximum speed. + self.cam.setPixelClock(pixel_clock) + self.cam.setFrameRate(verbose = True) + + # Some derived parameters + self.half_x = int(self.x_width/2) + self.half_y = int(self.y_width/2) + self.X = numpy.arange(self.y_width) - 0.5*float(self.y_width) + + def adjustAOI(self, dx, dy): + self.x_start += dx + self.y_start += dy + if(self.x_start < 0): + self.x_start = 0 + if(self.y_start < 0): + self.y_start = 0 + if((self.x_start + self.x_width + 2) > self.cam.info.nMaxWidth): + self.x_start = self.cam.info.nMaxWidth - (self.x_width + 2) + if((self.y_start + self.y_width + 2) > self.cam.info.nMaxHeight): + self.y_start = self.cam.info.nMaxHeight - (self.y_width + 2) + self.setAOI() + + def adjustZeroDist(self, inc): + self.zero_dist += inc + + def capture(self): + """ + Get the next image from the camera. + """ + self.image = self.cam.captureImage() + return self.image + + def changeFitMode(self, mode): + """ + mode 1 = gaussian fit, any other value = first moment calculation. + """ + self.fit_mode = mode + + def doMoments(self, data): + """ + Perform a moment based calculation of the distances. + """ + self.x_off1 = 1.0e-6 + self.y_off1 = 0.0 + self.x_off2 = 1.0e-6 + self.y_off2 = 0.0 + + total_good = 0 + data_band = data[self.half_y-15:self.half_y+15,:] + + # Moment for the object in the left half of the picture. + x = numpy.arange(self.half_x) + data_ave = numpy.average(data_band[:,:self.half_x], axis = 0) + power1 = numpy.sum(data_ave) + + dist1 = 0.0 + if (power1 > 0.0): + total_good += 1 + self.y_off1 = numpy.sum(x * data_ave) / power1 - self.half_x + dist1 = abs(self.y_off1) + + # Moment for the object in the right half of the picture. + data_ave = numpy.average(data_band[:,self.half_x:], axis = 0) + power2 = numpy.sum(data_ave) + + dist2 = 0.0 + if (power2 > 0.0): + total_good += 1 + self.y_off2 = numpy.sum(x * data_ave) / power2 + dist2 = abs(self.y_off2) + + # The moment calculation is too fast. This is to slow things + # down so that (hopefully) the camera doesn't freeze up. + time.sleep(0.02) + + return [total_good, dist1, dist2] + + def getImage(self): + return [self.image, self.x_off1, self.y_off1, self.x_off2, self.y_off2, self.sigma] + + def getZeroDist(self): + return self.zero_dist + + def qpdScan(self, reps = 4): + """ + Returns [power, offset, is_good] + """ + power_total = 0.0 + offset_total = 0.0 + good_total = 0.0 + for i in range(reps): + [power, n_good, offset] = self.singleQpdScan() + power_total += power + good_total += n_good + offset_total += offset + + power_total = power_total/float(reps) + if (good_total > 0): + return [power_total, offset_total/good_total, True] + else: + return [power_total, 0, False] + + def setAOI(self): + """ + Set the camera AOI to current AOI. + """ + self.cam.setAOI(self.x_start, + self.y_start, + self.x_width, + self.y_width) + + def shutDown(self): + """ + Save the current camera AOI location and offset. Shutdown the camera. + """ + if self.offset_file: + with open(self.offset_file, "w") as fp: + fp.write(str(self.x_start) + "," + str(self.y_start)) + self.cam.shutDown() + + def singleQpdScan(self): + """ + Perform a single measurement of the focus lock offset and camera sum signal. + + Returns [power, total_good, offset] + """ + data = self.capture().copy() + + # The power number is the sum over the camera AOI minus the background. + power = numpy.sum(data.astype(numpy.int64)) - self.background + + # (Simple) Check for duplicate frames. + if (power == self.last_power): + #print("> UC480-QPD: Duplicate image detected!") + time.sleep(0.05) + return [self.last_power, 0, 0] + + self.last_power = power + + # Determine offset by fitting gaussians to the two beam spots. + # In the event that only beam spot can be fit then this will + # attempt to compensate. However this assumes that the two + # spots are centered across the mid-line of camera ROI. + # + if (self.fit_mode == 1): + [total_good, dist1, dist2] = self.doFit(data) + + # Determine offset by moments calculation. + else: + [total_good, dist1, dist2] = self.doMoments(data) + + # Calculate offset. + # + + # No good fits. + if (total_good == 0): + return [power, 0.0, 0.0] + + # One good fit. + elif (total_good == 1): + if self.allow_single_fits: + return [power, 1.0, ((dist1 + dist2) - 0.5*self.zero_dist)] + else: + return [power, 0.0, 0.0] + + # Two good fits. This gets twice the weight of one good fit + # if we are averaging. + else: + return [power, 2.0, 2.0*((dist1 + dist2) - self.zero_dist)] + + +class CameraQPDCorrFit(CameraQPD): + """ + This version uses storm-analyis to do the peak finding and + image correlation to do the peak fitting. + """ + def __init__(self, **kwds): + super().__init__(**kwds) + + assert (cl2DG is not None), "Correlation fitting not available." + + self.fit_hl = None + self.fit_hr = None + + def doFit(self, data): + dist1 = 0 + dist2 = 0 + self.x_off1 = 0.0 + self.y_off1 = 0.0 + self.x_off2 = 0.0 + self.y_off2 = 0.0 + + if self.fit_hl is None: + roi_size = int(3.0 * self.sigma) + self.fit_hl = cl2DG.CorrLockFitter(roi_size = roi_size, + sigma = self.sigma, + threshold = 10) + self.fit_hr = cl2DG.CorrLockFitter(roi_size = roi_size, + sigma = self.sigma, + threshold = 10) + + total_good = 0 + [x1, y1, status] = self.fit_hl.findFitPeak(data[:,:self.half_x]) + if status: + total_good += 1 + self.x_off1 = x1 - self.half_y + self.y_off1 = y1 - self.half_x + dist1 = abs(self.y_off1) + + [x2, y2, status] = self.fit_hr.findFitPeak(data[:,-self.half_x:]) + if status: + total_good += 1 + self.x_off2 = x2 - self.half_y + self.y_off2 = y2 + dist2 = abs(self.y_off2) + + return [total_good, dist1, dist2] + + def shutDown(self): + super().shutDown() + + if self.fit_hl is not None: + self.fit_hl.cleanup() + self.fit_hr.cleanup() + + +class CameraQPDSAFit(CameraQPD): + """ + This version uses the storm-analysis project to do the fitting. + """ + def __init__(self, **kwds): + super().__init__(**kwds) + + assert (saLPF is not None), "Storm-analysis fitting not available." + + self.fit_hl = None + self.fit_hr = None + + def doFit(self, data): + dist1 = 0 + dist2 = 0 + self.x_off1 = 0.0 + self.y_off1 = 0.0 + self.x_off2 = 0.0 + self.y_off2 = 0.0 + + if self.fit_hl is None: + self.fit_hl = saLPF.LockPeakFinder(offset = 5.0, + sigma = self.sigma, + threshold = 10) + self.fit_hr = saLPF.LockPeakFinder(offset = 5.0, + sigma = self.sigma, + threshold = 10) + + total_good = 0 + [x1, y1, status] = self.fit_hl.findFitPeak(data[:,:self.half_x]) + if status: + total_good += 1 + self.x_off1 = x1 - self.half_y + self.y_off1 = y1 - self.half_x + dist1 = abs(self.y_off1) + + [x2, y2, status] = self.fit_hr.findFitPeak(data[:,-self.half_x:]) + if status: + total_good += 1 + self.x_off2 = x2 - self.half_y + self.y_off2 = y2 + dist2 = abs(self.y_off2) + + return [total_good, dist1, dist2] + + def shutDown(self): + super().shutDown() + + if self.fit_hl is not None: + self.fit_hl.cleanup() + self.fit_hr.cleanup() + + +class CameraQPDScipyFit(CameraQPD): + """ + This version uses scipy to do the fitting. + """ + def __init__(self, fit_mutex = False, **kwds): + super().__init__(**kwds) + + self.fit_mutex = fit_mutex + + def doFit(self, data): + dist1 = 0 + dist2 = 0 + self.x_off1 = 0.0 + self.y_off1 = 0.0 + self.x_off2 = 0.0 + self.y_off2 = 0.0 + + # numpy finder/fitter. + # + # Fit first gaussian to data in the left half of the picture. + total_good =0 + [max_x, max_y, params, status] = self.fitGaussian(data[:,:self.half_x]) + if status: + total_good += 1 + self.x_off1 = float(max_x) + params[2] - self.half_y + self.y_off1 = float(max_y) + params[3] - self.half_x + dist1 = abs(self.y_off1) + + # Fit second gaussian to data in the right half of the picture. + [max_x, max_y, params, status] = self.fitGaussian(data[:,-self.half_x:]) + if status: + total_good += 1 + self.x_off2 = float(max_x) + params[2] - self.half_y + self.y_off2 = float(max_y) + params[3] + dist2 = abs(self.y_off2) + + return [total_good, dist1, dist2] + + def fitGaussian(self, data): + if (numpy.max(data) < 25): + return [False, False, False, False] + x_width = data.shape[0] + y_width = data.shape[1] + max_i = data.argmax() + max_x = int(max_i/y_width) + max_y = int(max_i%y_width) + if (max_x > (self.fit_size-1)) and (max_x < (x_width - self.fit_size)) and (max_y > (self.fit_size-1)) and (max_y < (y_width - self.fit_size)): + if self.fit_mutex: + self.fit_mutex.lock() + #[params, status] = npLPF.fitSymmetricGaussian(data[max_x-self.fit_size:max_x+self.fit_size,max_y-self.fit_size:max_y+self.fit_size], 8.0) + #[params, status] = npLPF.fitFixedEllipticalGaussian(data[max_x-self.fit_size:max_x+self.fit_size,max_y-self.fit_size:max_y+self.fit_size], 8.0) + [params, status] = npLPF.fitFixedEllipticalGaussian(data[max_x-self.fit_size:max_x+self.fit_size,max_y-self.fit_size:max_y+self.fit_size], self.sigma) + if self.fit_mutex: + self.fit_mutex.unlock() + params[2] -= self.fit_size + params[3] -= self.fit_size + return [max_x, max_y, params, status] + else: + return [False, False, False, False] + + + + + +# Testing +if (__name__ == "__main__"): + + from PIL import Image + print("- loading DLL") + #loadDLL(r"C:\Program Files\Thorlabs\Scientific Imaging\ThorCam\uc480_64.dll") + loadDLL(r'C:\Program Files\Thorlabs\Scientific Imaging\ThorCam\thorlabs_tsi_camera_sdk.dll') + print("- Creating camera object") + cam = Camera(1) + reps = 100 + + if False: + cam.setAOI(772, 566, 200, 200) + cam.setFrameRate(verbose = True) + for i in range(100): + print("start", i) + for j in range(100): + image = cam.captureImage() + print(" stop") + + #im = Image.fromarray(image) + #im.save("temp.png") + + if False: + cam.setAOI(100, 100, 300, 300) + cam.setPixelClock(10) + cam.setFrameRate() + cam.startCapture() + st = time.time() + for i in range(reps): + #print i + image = cam.getNextImage() + #print i, numpy.sum(image) + print("time:", time.time() - st) + cam.stopCapture() + + if False: + cam.setAOI(100, 100, 700, 100) + cam.setPixelClock(25) + cam.setFrameRate(verbose = True) + st = time.time() + print("starting") + for i in range(reps): + #print i + image = cam.captureImage() + #print(i, numpy.sum(image)) + elapsed_time = time.time() - st + print("{0:0d} frames in {1:.3f} seconds, {2:.3f} FPS".format(reps, elapsed_time, reps/elapsed_time)) + + if False: + image = cam.captureImage() + im = Image.fromarray(image) + im.save("temp.png") + cam.saveParameters("cam1.ini") + + cam.shutDown() + +# +# The MIT License +# +# Copyright (c) 2013 Zhuang Lab, Harvard University +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# diff --git a/storm_control/sc_hardware/thorlabs/ucScientificCameraModule.py b/storm_control/sc_hardware/thorlabs/ucScientificCameraModule.py new file mode 100644 index 0000000..a23ea18 --- /dev/null +++ b/storm_control/sc_hardware/thorlabs/ucScientificCameraModule.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +""" +ucScientific Camera QPD functionality. + +Bogdan 01/21 +""" + +from PyQt5 import QtCore + +import storm_control.hal4000.halLib.halMessage as halMessage + +import storm_control.sc_hardware.baseClasses.hardwareModule as hardwareModule +import storm_control.sc_hardware.baseClasses.lockModule as lockModule +import storm_control.sc_hardware.thorlabs.ucScientificCamera as uc480Camera + + +class UC480QPDCameraFunctionality(hardwareModule.BufferedFunctionality, lockModule.QPDCameraFunctionalityMixin): + qpdUpdate = QtCore.pyqtSignal(dict) + threadUpdate = QtCore.pyqtSignal(dict) + + def __init__(self, camera = None, reps = None, **kwds): + super().__init__(**kwds) + self.camera = camera + self.scan_thread = UC480ScanThread(camera = self.camera, + device_mutex = self.device_mutex, + qpd_update_signal = self.threadUpdate, + reps = reps, + units_to_microns = self.units_to_microns) + self.threadUpdate.connect(self.handleThreadUpdate) + + def adjustAOI(self, dx, dy): + self.maybeRun(task = self.camera.adjustAOI, + args = [dx, dy]) + + def adjustZeroDist(self, inc): + self.maybeRun(task = self.camera.adjustZeroDist, + args = [inc]) + + def changeFitMode(self, mode): + self.mustRun(task = self.camera.changeFitMode, + args = [mode]) + + def handleThreadUpdate(self, qpd_dict): + # + # Why are we doing this? In testing we found that bouncing the update signal + # from the scan_thread through this class meant we could sample about twice + # as fast as having scan_thread directly emit the qpdUpdate() signal. It is + # not that clear why this should be the case. Perhaps signals are not buffered + # so scan_thread was having to wait for the focus lock control / GUI to + # process the signal before it could start on the next sample? + # + self.qpdUpdate.emit(qpd_dict) + + def getMinimumInc(self): + # + # The minimum step size of AOI adjustments for these cameras is 2 pixels. + # + return 2 + + def getOffset(self): + # + # lockControl.LockControl will call this each time the qpdUpdate signal + # is emitted, but we only want the thread to get started once. + # + if not self.scan_thread.isRunning(): + self.scan_thread.startScan() + + def wait(self): + super().wait() + self.scan_thread.stopScan() + + +class UC480ScanThread(QtCore.QThread): + """ + Handles periodic polling of the camera to determine the current offset. + In testing this approach appeared more performant than starting a new + QRunnable for each scan. + """ + def __init__(self, + camera = None, + device_mutex = None, + qpd_update_signal = None, + reps = None, + units_to_microns = None, + **kwds): + super().__init__(**kwds) + self.camera = camera + self.device_mutex = device_mutex + self.qpd_update_signal = qpd_update_signal + self.reps = reps + self.running = False + self.units_to_microns = units_to_microns + + def isRunning(self): + return self.running + + def run(self): + self.running = True + while(self.running): + [power, offset, is_good] = self.camera.qpdScan(reps = self.reps) + [image, x_off1, y_off1, x_off2, y_off2, sigma] = self.camera.getImage() + self.qpd_update_signal.emit({"is_good" : is_good, # This is the flag for good fit values. + "image" : image, + "offset" : offset * self.units_to_microns, + "sigma" : sigma, + "sum" : power, + "x_off1" : x_off1, + "y_off1" : y_off1, + "x_off2" : x_off2, + "y_off2" : y_off2}) + + def startScan(self): + self.start(QtCore.QThread.NormalPriority) + + def stopScan(self): + self.running = False + self.wait() + + +class UC480Camera(hardwareModule.HardwareModule): + """ + HAL module that interfaces with a Thorlabs UC480 camera. + """ + def __init__(self, module_params = None, qt_settings = None, **kwds): + super().__init__(**kwds) + self.camera = None + self.camera_functionality = None + + configuration = module_params.get("configuration") + #uc480Camera.loadDLL(configuration.get("uc480_dll")) + + # Use the storm-analysis project for finding and image correlation for + # fitting. This is hopefully less sensitive to the fringes than a + # Gaussian fitting approach. + # + if (configuration.get("use_correlation", False)): + print("> using correlation for fitting.") + self.camera = uc480Camera.CameraQPDCorrFit(allow_single_fits = configuration.get("allow_single_fits", False), + background = configuration.get("background"), + camera_id = configuration.get("camera_id"), + ini_file = configuration.get("ini_file"), + offset_file = configuration.get("offset_file"), + pixel_clock = configuration.get("pixel_clock", 30), + sigma = configuration.get("sigma"), + x_width = configuration.get("x_width"), + y_width = configuration.get("y_width")) + + # Use the storm-analysis project for fitting. This is hopefully both faster + # and more accurate than the Numpy/Scipy fitter. + # + elif (configuration.get("use_storm_analysis", False)): + print("> using storm-analysis for fitting.") + self.camera = uc480Camera.CameraQPDSAFit(allow_single_fits = configuration.get("allow_single_fits", False), + background = configuration.get("background"), + camera_id = configuration.get("camera_id"), + ini_file = configuration.get("ini_file"), + offset_file = configuration.get("offset_file"), + pixel_clock = configuration.get("pixel_clock", 30), + sigma = configuration.get("sigma"), + x_width = configuration.get("x_width"), + y_width = configuration.get("y_width")) + + # Use the Numpy/Scipy fitter. + else: + print("> using numpy/scipy for fitting.") + self.camera = uc480Camera.CameraQPDScipyFit(allow_single_fits = configuration.get("allow_single_fits", False), + background = configuration.get("background"), + camera_id = configuration.get("camera_id"), + ini_file = configuration.get("ini_file"), + offset_file = configuration.get("offset_file"), + pixel_clock = configuration.get("pixel_clock", 30), + sigma = configuration.get("sigma"), + x_width = configuration.get("x_width"), + y_width = configuration.get("y_width")) + + self.camera_functionality = UC480QPDCameraFunctionality(camera = self.camera, + device_mutex = QtCore.QMutex(), + parameters = configuration.get("parameters"), + reps = configuration.get("reps", 1), + units_to_microns = configuration.get("units_to_microns")) + + def cleanUp(self, qt_settings): + self.camera_functionality.wait() + self.camera.shutDown() + + def getFunctionality(self, message): + if (message.getData()["name"] == self.module_name): + message.addResponse(halMessage.HalMessageResponse(source = self.module_name, + data = {"functionality" : self.camera_functionality})) + + def processMessage(self, message): + + if message.isType("get functionality"): + self.getFunctionality(message)