diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fb4cf7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,316 @@ +# bapsfdaq_180e ignores +.hdf5_files +scope_screen_dump.png + +### Windows template +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +.venvs +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ +.idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119 + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based HTTP Client +.idea/httpRequests +http-client.private.env.json + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# Apifox Helper cache +.idea/.cache/.Apifox_Helper +.idea/ApifoxUploaderProjectSetting.xml + +# Github Copilot persisted session migrations, see: https://github.com/microsoft/copilot-intellij-feedback/issues/712#issuecomment-3322062215 +.idea/**/copilot.data.migration.*.xml + +# Covers VSCode IDE: +# source: https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +!*.code-workspace + +# Built Visual Studio Code Extensions +*.vsix diff --git a/Data_Run_GUI_xy.py b/Data_Run_GUI_xy.py index 068f0dd..594af7b 100644 --- a/Data_Run_GUI_xy.py +++ b/Data_Run_GUI_xy.py @@ -10,22 +10,23 @@ # Author: Yuchen Qian # Oct 2017 # -19 -import numpy +import datetime +import h5py import math -import sys +import numpy import os import os.path +import sys import time -import datetime -from Motor_Control_2D_xy import Motor_Control_2D -from LeCroy_Scope import LeCroy_Scope, WAVEDESC_SIZE -from LeCroy_Scope import EXPANDED_TRACE_NAMES import tkinter -from tkinter import filedialog import tkinter.messagebox -import h5py as h5py + +from tkinter import filedialog + +from LeCroy_Scope import LeCroy_Scope, WAVEDESC_SIZE +from LeCroy_Scope import EXPANDED_TRACE_NAMES +from Motor_Control_2D_xy import Motor_Control_2D dir_path=os.path.dirname(os.path.realpath(__file__)) version_number="03/01/2018 12:37pm" # update this when a change has been made @@ -347,7 +348,7 @@ def move_to_position(self): except ValueError: QMessageBox.about(self, "Error", "Position should be valid numbers.") - def disable(): + def disable(self): self.mc.disable() def stop_now(self): @@ -540,7 +541,7 @@ def get_hdf5_filename(self) -> str: self.hdf5_filename = fn # save it for later return fn - def acquire_displayed_traces(self, scope, datasets, hdr_data, pos_ndx): + def acquire_displayed_traces(self, scope: LeCroy_Scope, datasets, hdr_data, pos_ndx): """ worker for below : acquire enough sweeps for the averaging, then read displayed scope trace data into HDF5 datasets """ @@ -553,17 +554,31 @@ def acquire_displayed_traces(self, scope, datasets, hdr_data, pos_ndx): traces = scope.displayed_traces() for tr in traces: + NPos, NTimes = datasets[tr].shape + signal = scope.acquire(tr) # type: numpy.ndarray try: - NPos,NTimes = datasets[tr].shape - datasets[tr][pos_ndx,0:NTimes] = scope.acquire(tr)[0:NTimes] # sometimes for 10000 the scope hardware returns 10001 samples, so we have to specify [0:NTimes] - #?# datasets[tr].flush() - except KeyError: - print(tr + ' is displayed on the scope but not recorded. To record this channel, please display the trace before starting the data run.') - continue - except TypeError: - print('Not enough points from scope trace') - datasets[tr][pos_ndx,:] = scope.acquire(tr)[:] - continue + datasets[tr][pos_ndx, ...] = signal[...] + except KeyError as err: + print( + tr + + ' is displayed on the scope but not recorded. ' + 'To record this channel, please display the trace ' + 'before starting the data run.' + + err + ) + except TypeError as err: + # the returned signal does not always have exactly NTimes + # samples. It's is off by a few data points. + signal_NTimes = signal.size + if not numpy.isclose(signal_NTimes, NTimes, rtol=0.005, atol=0): + # the difference in the number of time samples is greater + # than 0.5% + raise err + + if NTimes > signal_NTimes: + datasets[tr][pos_ndx, 0:signal_NTimes] = signal[...] + else: + datasets[tr][pos_ndx, ...] = signal[0:NTimes] for tr in traces: try: @@ -571,7 +586,7 @@ def acquire_displayed_traces(self, scope, datasets, hdr_data, pos_ndx): #?# hdr_data[tr].flush() #?# are there consequences in timing or compression size if we do the flush()s recommend for the SWMR function? except KeyError: - continue + pass scope.set_trigger_mode('NORM') # resume triggering @@ -676,7 +691,7 @@ def run(self): pos_ds.attrs['shotperpos'] = num_duplicate_shots # not legacy # create the scope access object, and iterate over positions - with LeCroy_Scope(self.ip_addrs['scope'], verbose=False) as scope: + with LeCroy_Scope(self.ip_addrs['scope'], verbose=False) as scope: # type: LeCroy_Scope if not scope: print('Scope not found at '+self.ip_addrs['scope']) # I think we have raised an exception if this is the case, so we never get here return @@ -776,16 +791,51 @@ def run(self): # at least get one time array recorded for swmr functions if pos[0] == 1: - time_ds[0:NTimes] = scope.time_array()[0:NTimes] - #time_ds.flush() + time_array = scope.time_array() + try: + time_ds[...] = scope.time_array()[...] + except TypeError as err: + # the returned signal does not always have exactly NTimes + # samples. It's is off by a few data points. + time_size = time_array.size + if not numpy.isclose( + time_size, NTimes, rtol=0.005, atol=0 + ): + # the difference in the number of time samples is greater + # than 0.5% + raise err + + if NTimes > time_size: + time_ds[0:time_size] = time_array[...] + else: + time_ds[...] = time_array[0:NTimes] ######### END MAIN ACQUISITION LOOP ######### except KeyboardInterrupt: print('\n______Halted due to Ctrl-C______', ' at', time.ctime()) - # copy the array of time values, corresponding to the last acquired trace, to the times_dataset - time_ds[0:NTimes] = scope.time_array()[0:NTimes] # specify number of points, sometimes scope return extras + # copy the array of time values, corresponding to the last acquired + # trace, to the times_dataset + time_array = scope.time_array() + try: + time_ds[...] = scope.time_array()[...] + except TypeError as err: + # the returned signal does not always have exactly NTimes + # samples. It's is off by a few data points. + time_size = time_array.size + if not numpy.isclose( + time_size, NTimes, rtol=0.005, atol=0 + ): + # the difference in the number of time samples is greater + # than 0.5% + raise err + + if NTimes > time_size: + time_ds[0:time_size] = time_array[...] + else: + time_ds[...] = time_array[0:NTimes] + if type(time_ds) == 'stupid': print(' this is only included to make the linter happy, otherwise it thinks time_ds is not used') @@ -801,12 +851,9 @@ def run(self): datasets[tr].attrs['recorded'] = True datasets[tr].attrs['shots per position'] = self.pos_param["num_shots"] - f.close() # close the HDF5 file self.signals.finished.emit() - #done - class Test_Shot_Thread(QRunnable): diff --git a/LeCroy_Scope.py b/LeCroy_Scope.py index 58218d4..fd8bb74 100644 --- a/LeCroy_Scope.py +++ b/LeCroy_Scope.py @@ -32,17 +32,18 @@ """ -import numpy -import visa -from pyvisa.resources import MessageBasedResource -from pyvisa.errors import VisaIOError import collections +import numpy +import matplotlib.image as mpimg +import pylab as plt +import pyvisa as visa import struct import sys -import pylab as plt -import matplotlib.image as mpimg import time +from pyvisa.resources import MessageBasedResource +from pyvisa.errors import VisaIOError + # the header recorded for each trace # 63 entries, 346 bytes WAVEDESC = collections.namedtuple('WAVEDESC', @@ -209,7 +210,7 @@ def rm_open(self, ipv4_addr) -> bool: if self.verbose: print('<:> constructing resource manager') t0 = time.time() - self.rm = visa.ResourceManager() + self.rm = visa.ResourceManager("@py") t1 = time.time() if self.verbose and (t1-t0 > 1): print(' .............................%6.3g sec' % (t1-t0), end='') @@ -217,7 +218,10 @@ def rm_open(self, ipv4_addr) -> bool: # attempt to open a connection to the scope try: - self.scope = self.rm.open_resource('VICP::'+ipv4_addr+'::INSTR', resource_pyclass=MessageBasedResource) + self.scope = self.rm.open_resource( + 'VICP::'+ipv4_addr+'::INSTR', + resource_pyclass=MessageBasedResource, + ) print('...ok') except Exception: print('\n**** Scope not found at "', ipv4_addr, '"\n') @@ -301,7 +305,7 @@ def validate_channel(self, Cn) -> str: #------------------------------------------------------------------------- - def validate_trace(self, tr) -> str: + def validate_trace(self, tr) -> str: """ convenience function, returns canonical trace label, which is broader than a channel label see valid_trace_names defined at top of file if Cn is an integer, assumes we want a channel name @@ -315,7 +319,6 @@ def validate_trace(self, tr) -> str: err = '**** validate_trace(): trace name "' + tr + '" is unknown' raise(RuntimeError(err)).with_traceback(sys.exc_info()[2]) - #------------------------------------------------------------------------- def max_samples(self, N = 0) -> int: """ mostly used for determining the number of samples the scope expects to acquire. diff --git a/__pycache__/Acquire_Scope_Data_3D.cpython-35.pyc b/__pycache__/Acquire_Scope_Data_3D.cpython-35.pyc deleted file mode 100644 index e505c48..0000000 Binary files a/__pycache__/Acquire_Scope_Data_3D.cpython-35.pyc and /dev/null differ diff --git a/__pycache__/LeCroy_Scope.cpython-35.pyc b/__pycache__/LeCroy_Scope.cpython-35.pyc deleted file mode 100644 index e22f45e..0000000 Binary files a/__pycache__/LeCroy_Scope.cpython-35.pyc and /dev/null differ diff --git a/__pycache__/LeCroy_Scope.cpython-36.pyc b/__pycache__/LeCroy_Scope.cpython-36.pyc deleted file mode 100644 index 3f9eb91..0000000 Binary files a/__pycache__/LeCroy_Scope.cpython-36.pyc and /dev/null differ diff --git a/__pycache__/Motor_Control_1D.cpython-35.pyc b/__pycache__/Motor_Control_1D.cpython-35.pyc deleted file mode 100644 index a6743b4..0000000 Binary files a/__pycache__/Motor_Control_1D.cpython-35.pyc and /dev/null differ diff --git a/__pycache__/Motor_Control_2D.cpython-36.pyc b/__pycache__/Motor_Control_2D.cpython-36.pyc deleted file mode 100644 index 0e8fd8a..0000000 Binary files a/__pycache__/Motor_Control_2D.cpython-36.pyc and /dev/null differ diff --git a/__pycache__/Motor_Control_2D_xy.cpython-36.pyc b/__pycache__/Motor_Control_2D_xy.cpython-36.pyc deleted file mode 100644 index e9916d3..0000000 Binary files a/__pycache__/Motor_Control_2D_xy.cpython-36.pyc and /dev/null differ diff --git a/__pycache__/Motor_Control_3D.cpython-35.pyc b/__pycache__/Motor_Control_3D.cpython-35.pyc deleted file mode 100644 index 641394d..0000000 Binary files a/__pycache__/Motor_Control_3D.cpython-35.pyc and /dev/null differ diff --git a/__pycache__/Single_Motor_Control.cpython-36.pyc b/__pycache__/Single_Motor_Control.cpython-36.pyc deleted file mode 100644 index e9dd3f3..0000000 Binary files a/__pycache__/Single_Motor_Control.cpython-36.pyc and /dev/null differ diff --git a/__pycache__/find_ip_addr.cpython-35.pyc b/__pycache__/find_ip_addr.cpython-35.pyc deleted file mode 100644 index 04068fe..0000000 Binary files a/__pycache__/find_ip_addr.cpython-35.pyc and /dev/null differ diff --git a/__pycache__/find_ip_addr.cpython-36.pyc b/__pycache__/find_ip_addr.cpython-36.pyc deleted file mode 100644 index 80f2a4d..0000000 Binary files a/__pycache__/find_ip_addr.cpython-36.pyc and /dev/null differ diff --git a/__pycache__/wavegen_control.cpython-35.pyc b/__pycache__/wavegen_control.cpython-35.pyc deleted file mode 100644 index f4ec634..0000000 Binary files a/__pycache__/wavegen_control.cpython-35.pyc and /dev/null differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..abc9049 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +h5py +numpy +matplotlib +PyQt5 +pyvisa >= 1.12 +pyvisa-py[vicp, psutil, hislip-discovery, usb-full] +scipy +setuptools < 82.0.0 +setuptools-scm