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". -->
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -201,6 +201,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -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)