diff --git a/lantz/drivers/legacy/andor/ccd.py b/lantz/drivers/legacy/andor/ccd.py index 0f1e675..a897472 100644 --- a/lantz/drivers/legacy/andor/ccd.py +++ b/lantz/drivers/legacy/andor/ccd.py @@ -17,19 +17,18 @@ - Andor SDK 2.96 Manual - :copyright: 2015 by Lantz Authors, see AUTHORS for more details. + :copyright: 2016 by Lantz Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import numpy as np import ctypes as ct from collections import namedtuple -from lantz import Driver, Feat, Action, DictFeat +from lantz import Feat, Action, DictFeat from lantz.errors import InstrumentError from lantz.foreign import LibraryDriver from lantz import Q_ -degC = Q_(1, 'degC') us = Q_(1, 'us') MHz = Q_(1, 'MHz') seg = Q_(1, 's') @@ -50,7 +49,7 @@ 20014: 'DRV_ERROR_UP_FIFO', 20015: 'DRV_ERROR_PATTERN', 20017: 'DRV_ACQUISITION_ERRORS', - 20018: 'Computer unable to read the data via the ISA slot at the required rate.', + 20018: 'PC unable to read the data via the ISA slot at the required rate.', 20019: 'DRV_ACQ_DOWNFIFO_FULL', 20020: 'RV_PROC_UNKNOWN_INSTRUCTION', 20021: 'DRV_ILLEGAL_OP_CODE', @@ -114,6 +113,7 @@ 20121: 'DRV_ERROR_NOHANDLE', 20130: 'DRV_GATING_NOT_AVAILABLE', 20131: 'DRV_FPGA_VOLTAGE_ERROR', + 20099: 'DRV_BINNING_ERROR', 20100: 'DRV_INVALID_AMPLIFIER', 20101: 'DRV_INVALID_COUNTCONVERT_MODE' } @@ -171,10 +171,14 @@ def initialize(self): self.frame_transfer_mode_state = False self.frame_transfer_mode = self.frame_transfer_mode_state + self.crop_mode_state = False + self.crop_mode_shape = self.detector_shape + self.crop_mode_binning = (1, 1) + self.fan_mode_index = 'onfull' self.fan_mode = self.fan_mode_index - self.EM_gain_mode_index = 'DAC255' + self.EM_gain_mode_index = 'RealGain' self.EM_gain_mode = self.EM_gain_mode_index self.cooled_on_shutdown_value = False @@ -200,7 +204,7 @@ def initialize(self): self.preamp_index = 0 self.preamp = self.preamp_index - self.temperature_sp = 0 * degC + self.temperature_sp = 0 self.temperature_setpoint = self.temperature_sp self.auxout = np.zeros(4, dtype=bool) @@ -219,7 +223,7 @@ def finalize(self): self.free_int_mem() self.lib.ShutDown() - ### SYSTEM INFORMATION + # SYSTEM INFORMATION @Feat(read_once=True) def ncameras(self): @@ -403,7 +407,7 @@ def count_convert_available(self, mode): else: return False - ### SHUTTER # I couldn't find a better way to do this... sorry + # SHUTTER # I couldn't find a better way to do this... sorry @Action() def shutter(self, typ, mode, ext_closing, ext_opening, ext_mode): """ This function expands the control offered by SetShutter to allow an @@ -434,12 +438,6 @@ def shutter(self, typ, mode, ext_closing, ext_opening, ext_mode): 5 Open for any series int closingtime: time shutter takes to close (milliseconds) int openingtime: Time shutter takes to open (milliseconds) - int mode: - 0 Fully Auto - 1 Permanently Open - 2 Permanently Closed - 4 Open for FVB series - 5 Open for any series """ self.lib.SetShutterEx(ct.c_int(typ), ct.c_int(mode), ct.c_int(ext_closing), ct.c_int(ext_opening), @@ -460,9 +458,9 @@ def has_mechanical_shutter(self): self.lib.IsInternalMechanicalShutter(ct.pointer(state)) return bool(state.value) - ### TEMPERATURE + # TEMPERATURE (EVERYTHING IS IN DEGC) - @Feat(read_once=True, units='degC') + @Feat(read_once=True) def min_temperature(self): """ This function returns the valid range of temperatures in centigrads to which the detector can be cooled. @@ -471,7 +469,7 @@ def min_temperature(self): self.lib.GetTemperatureRange(ct.pointer(mini), ct.pointer(maxi)) return mini.value - @Feat(read_once=True, units='degC') + @Feat(read_once=True) def max_temperature(self): """ This function returns the valid range of temperatures in centigrads to which the detector can be cooled. @@ -489,16 +487,16 @@ def temperature_status(self): ans = self.lib.GetTemperatureF(ct.pointer(temp)) return _ERRORS[ans] - @Feat(units='degC') + @Feat() def temperature(self): """ This function returns the temperature of the detector to the - nearest degree. It also gives the status of cooling process. + nearest celsius degree. It also gives the status of cooling process. """ temp = ct.c_float() self.lib.GetTemperatureF(ct.pointer(temp)) return temp.value - @Feat(units='degC') + @Feat() def temperature_setpoint(self): return self.temperature_sp @@ -552,7 +550,7 @@ def fan_mode(self, mode): if ans == 20002: self.fan_mode_index = mode - ### FILTERS + # FILTERS @Feat() def averaging_factor(self): @@ -649,7 +647,7 @@ def cr_filter_enabled(self): def cr_filter_enabled(self, value): self.lib.SetFilterMode(ct.c_int(value)) - ### PHOTON COUNTING MODE + # PHOTON COUNTING MODE @Feat(values={True: 1, False: 0}) # FIXME: untested def photon_counting_mode(self): @@ -688,7 +686,7 @@ def set_photon_counting_thres(self, mini, maxi): """ self.lib.SetPhotonCountingThreshold(ct.c_long(mini), ct.c_long(maxi)) - ### FAST KINETICS MODE + # FAST KINETICS MODE @Feat(units='s') def FK_exposure_time(self): @@ -702,7 +700,7 @@ def FK_exposure_time(self): self.lib.GetFKExposureTime(ct.pointer(f)) return f.value - ### ACQUISITION HANDLING + # ACQUISITION HANDLING @Feat(values={'Single Scan': 1, 'Accumulate': 2, 'Kinetics': 3, 'Fast Kinetics': 4, 'Run till abort': 5}) @@ -917,7 +915,7 @@ def readout_packing(self, state): if ans == 20002: self.readout_packing_state = state - ### DATA HANDLING + # DATA HANDLING @Feat(read_once=True) def min_image_length(self): @@ -960,7 +958,7 @@ def acquired_data16(self, shape): large enough to hold the complete data set. """ size = np.array(shape).prod() - arr = np.ascontiguousarray(np.zeros(size, dtype=np.int16)) + arr = np.ascontiguousarray(np.zeros(size, dtype=np.uint16)) self.lib.GetAcquiredData16(arr.ctypes.data_as(ct.POINTER(ct.c_int16)), ct.c_ulong(size)) return arr.reshape(shape) @@ -982,7 +980,7 @@ def oldest_image16(self, shape): """ 16-bit version of the GetOldestImage function. """ size = np.array(shape).prod() - array = np.ascontiguousarray(np.zeros(size, dtype=np.int16)) + array = np.ascontiguousarray(np.zeros(size, dtype=np.uint16)) self.lib.GetOldestImage16(array.ctypes.data_as(ct.POINTER(ct.c_int16)), ct.c_ulong(size)) return array.reshape(shape) @@ -1003,7 +1001,7 @@ def most_recent_image16(self, shape): """ 16-bit version of the GetMostRecentImage function. """ size = np.array(shape).prod() - arr = np.ascontiguousarray(np.zeros(size, dtype=np.int16)) + arr = np.ascontiguousarray(np.zeros(size, dtype=np.uint16)) pt = ct.POINTER(ct.c_int16) self.lib.GetMostRecentImage16(arr.ctypes.data_as(pt), ct.c_ulong(size)) return arr.reshape(shape) @@ -1034,7 +1032,7 @@ def images16(self, first, last, shape, validfirst, validlast): """ 16-bit version of the GetImages function. """ size = shape[0] * shape[1] * (1 + last - first) - array = np.ascontiguousarray(np.zeros(size, dtype=np.int16)) + array = np.ascontiguousarray(np.zeros(size, dtype=np.uint16)) self.lib.GetImages16(ct.c_long(first), ct.c_long(last), array.ctypes.data_as(ct.POINTER(ct.c_int16)), ct.c_ulong(size), @@ -1124,7 +1122,7 @@ def save_raw(self, filename, typ): self.lib.SaveAsRaw(ct.c_char_p(str.encode(filename)), ct.c_int(self.savetypes[typ])) - ### EXPOSURE SETTINGS + # EXPOSURE SETTINGS @Feat() def acquisition_timings(self): @@ -1144,7 +1142,7 @@ def acquisition_timings(self): kine = ct.c_float() self.lib.GetAcquisitionTimings(ct.pointer(exp), ct.pointer(accum), ct.pointer(kine)) - return exp.value, accum.value, kine.value + return exp.value * seg, accum.value * seg, kine.value * seg @Action() def set_exposure_time(self, time): @@ -1278,7 +1276,29 @@ def frame_transfer_mode(self, state): if ans == 20002: self.frame_transfer_mode_state = state - ### AMPLIFIERS, GAIN, SPEEDS + @Feat(values={True: 1, False: 0}) + def crop_mode(self): + """ This function effectively reduces the dimensions of the CCD by + excluding some rows or columns to achieve higher throughput. In + isolated crop mode iXon, Newton and iKon cameras can operate in either + Full Vertical Binning or Imaging read modes. iDus can operate in Full + Vertical Binning read mode only. + Note: It is important to ensure that no light falls on the excluded + region otherwise the acquired data will be corrupted. + """ + return self.crop_mode_state + + @crop_mode.setter + def crop_mode(self, state): + ans = self.lib.SetIsolatedCropMode(ct.c_int(state), + ct.c_int(self.crop_mode_shape[1]), + ct.c_int(self.crop_mode_shape[0]), + ct.c_int(self.crop_mode_binning[1]), + ct.c_int(self.crop_mode_binning[0])) + if ans == 20002: + self.crop_mode_state = state + + # AMPLIFIERS, GAIN, SPEEDS @Feat(read_once=True) def n_preamps(self): @@ -1330,7 +1350,7 @@ def true_preamp(self, index): index = ct.c_int(index) gain = ct.c_float() self.lib.GetPreAmpGain(index, ct.pointer(gain)) - return gain + return gain.value @Feat() def preamp(self): @@ -1629,7 +1649,7 @@ def n_vert_shift_speeds(self): self.lib.GetNumberVSSpeeds(ct.pointer(n)) return n.value - def true_vert_shift_speed(self, index=0): + def true_vert_shift_speed(self, index): """As your Andor SDK system may be capable of operating at more than one vertical shift speed this function will return the actual speeds available. The value returned is in microseconds. @@ -1650,7 +1670,7 @@ def vert_shift_speed(self, index): self.vert_shift_speed_index = index self.lib.SetVSSpeed(ct.c_int(index)) - ### BASELINE + # BASELINE @Feat(values={True: 1, False: 0}) def baseline_clamp(self): @@ -1682,7 +1702,7 @@ def baseline_offset(self, value): if ans == 20002: self.baseline_offset_value = value - ### BIT DEPTH + # BIT DEPTH def bit_depth(self, ch): """ This function will retrieve the size in bits of the dynamic range @@ -1693,7 +1713,7 @@ def bit_depth(self, ch): self.lib.GetBitDepth(ch, ct.pointer(depth)) return depth.value - ### TRIGGER + # TRIGGER @Feat(values={True: 1, False: 0}) def adv_trigger_mode(self): @@ -1759,7 +1779,7 @@ def trigger_level(self, value): """ self.lib.SetTriggerLevel(ct.c_float(value)) - ### AUXPORT + # AUXPORT @DictFeat(values={True: not(0), False: 0}, keys=list(range(1, 5))) def in_aux_port(self, port): @@ -1890,11 +1910,8 @@ def flush(self): self.lib.AT_Flush(self.AT_H) if __name__ == '__main__': - from matplotlib import pyplot as plt from lantz import Q_ - import time - degC = Q_(1, 'degC') us = Q_(1, 'us') MHz = Q_(1, 'MHz') s = Q_(1, 's') @@ -1912,12 +1929,20 @@ def flush(self): andor.set_exposure_time(0.03 * s) andor.trigger_mode = 'Internal' andor.amp_typ = 0 - andor.horiz_shift_speed = 0 + andor.horiz_shift_speed = 3 andor.vert_shift_speed = 0 - andor.shutter(0, 0, 0, 0, 0) +# andor.shutter(0, 1, 0, 0, 0) + andor.preamp = 0 + andor.EM_advanced_enabled = False + andor.EM_gain_mode = 'RealGain' + andor.set_n_accum(1) # No accumulation of exposures +# andor.set_accum_time(0 * s) # Minimum accumulation and kinetic + andor.set_kinetic_cycle_time(0 * s) # times + andor.frame_transfer_mode = True + andor.crop_mode = True # # Temperature stabilization -# andor.temperature_setpoint = -30 * degC +# andor.temperature_setpoint = -30 # andor.cooler_on = True # stable = 'Temperature has stabilized at set point.' # print('Temperature set point =', andor.temperature_setpoint) @@ -1927,13 +1952,22 @@ def flush(self): # print('Temperature has stabilized at set point') # Acquisition - andor.start_acquisition() - time.sleep(2) - data = andor.most_recent_image(shape=andor.detector_shape) - andor.abort_acquisition() - - plt.imshow(data, cmap='gray', interpolation='None') - plt.colorbar() - plt.show() - - print(data.min(), data.max(), data.mean()) +# andor.start_acquisition() +# time.sleep(2) +# shape = andor.detector_shape +# data = andor.most_recent_image(shape=shape) +# +# i, j = andor.new_images_index +# n = j - i +## stack = np.zeros((n, shape[0], shape[1]), +# +# stack = andor.images16(i, j, andor.detector_shape, 1, n) +# +# andor.abort_acquisition() +# andor.shutter(0, 2, 0, 0, 0) + +# plt.imshow(data, cmap='gray', interpolation='None') +# plt.colorbar() +# plt.show() + +# print(data.min(), data.max(), data.mean()) diff --git a/lantz/drivers/legacy/cobolt/cobolt0601.py b/lantz/drivers/legacy/cobolt/cobolt0601.py index 2facf23..91defb4 100644 --- a/lantz/drivers/legacy/cobolt/cobolt0601.py +++ b/lantz/drivers/legacy/cobolt/cobolt0601.py @@ -3,7 +3,7 @@ lantz.drivers.legacy.cobolt.cobolt0601 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: 2015 by Lantz Authors, see AUTHORS for more details. + :copyright: 2017 by Lantz Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ @@ -201,6 +201,21 @@ def mod_mode(self): """ return self.query('gom?')[1:] + +class Cobolt0601_f2(Cobolt0601): + """Driver for any Cobolt 06-01 Series laser, new firmware. + """ + @Feat(units='mW') + def power_mod(self): + """Laser modulated power (mW). + """ + return float(self.query('glmp?')) + + @power_mod.setter + def power_mod(self, value): + self.query('slmp {}'.format(value)) + + if __name__ == '__main__': import argparse import lantz.log diff --git a/lantz/drivers/legacy/labjack/__init__.py b/lantz/drivers/legacy/labjack/__init__.py index 42bb30f..8837b69 100644 --- a/lantz/drivers/legacy/labjack/__init__.py +++ b/lantz/drivers/legacy/labjack/__init__.py @@ -4,10 +4,10 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :company: LabJack - :description: LabJacks are USB/Ethernet based measurement and automation - devices which provide analog inputs/outputs, digital inputs/outputs, and more. - They serve as an inexpensive and easy to use interface between computers and - the physical world. + :description: LabJacks are USB/Ethernet based measurement and automation + devices which provide analog inputs/outputs, digital inputs/outputs, and + more. They serve as an inexpensive and easy to use interface between + computers and the physical world. :website: http://www.labjack.com/ ---- @@ -16,7 +16,7 @@ :license: BSD, see LICENSE for more details. """ -from .u12 import U12 - -__all__ = ['U12'] +# from .u12 import U12 +from .t7 import T7 +__all__ = ['T7'] diff --git a/lantz/drivers/legacy/labjack/_internal/u12.py b/lantz/drivers/legacy/labjack/_internal/u12.py index 1725c51..e4326ec 100644 --- a/lantz/drivers/legacy/labjack/_internal/u12.py +++ b/lantz/drivers/legacy/labjack/_internal/u12.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Name: u12.py Desc: Defines the U12 class, which makes working with a U12 much easier. The @@ -2530,7 +2529,7 @@ def getWinVersion(self): >>> dev = U12() >>> dev.getWinVersion() - >>> {'majorVersion': 5, 'minorVersion': 1, 'platformID': 2, 'buildNumber': 2600, 'servicePackMajor': 2, 'servicePackMinor': 0} + >>> {'majorVersion': 5L, 'minorVersion': 1L, 'platformID': 2L, 'buildNumber': 2600L, 'servicePackMajor': 2L, 'servicePackMinor': 0L} """ # Create ctypes diff --git a/lantz/drivers/legacy/labjack/t7.py b/lantz/drivers/legacy/labjack/t7.py new file mode 100644 index 0000000..4d239fa --- /dev/null +++ b/lantz/drivers/legacy/labjack/t7.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +""" + :copyright: 2014 by Lantz Authors, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" + + +from lantz import Feat, DictFeat +from lantz import Driver + +from labjack.ljm import ljm + +from lantz import Q_ + +V = Q_(1, 'V') + + +class T7(Driver): + + """ + Driver for the Labjack T7 data acquisition device. + http://labjack.com/support/t7 + For details about the commands, refer to the users guide. + """ + + def __init__(self): + """ + Opens first found labjack + """ + self.handle = ljm.openS("ANY", "ANY", "ANY") + # TODO: handle cases for more than one labjack system. + self.constants = ljm.constants + + def finalize(self): + super().finalize() + ljm.close(self.handle) + + @Feat(read_once=True) + def idn(self): + return ('Labjack T7, serial number ' + + str(int(ljm.eReadName(self.handle, "SERIAL_NUMBER")))) + + @DictFeat(units='volts', keys=list(range(0, 14))) + def analog_in(self, key): + """ + AIN#(0:3) + Returns the voltage of the specified analog input. For + more information go to + http://labjack.com/support/datasheets/t7/ain + """ + return ljm.eReadName(self.handle, "AIN{}".format(key)) + + @DictFeat(units='volts', keys=list(range(0, 2)), limits=(0, 5)) + def analog_out(self, key): + """ + DAC#(0:1) + Pass a voltage for the specified analog output. For + more information go to + http://labjack.com/support/datasheets/t7/dac + """ + return ljm.eReadName(self.handle, "DAC{}".format(key)) + + @analog_out.setter + def analog_out(self, key, value): + return ljm.eWriteName(self.handle, "DAC{}".format(key), value) + + @DictFeat(keys=list(range(0, 23)), values={True: 1, False: 0}) + def digital_IO(self, key): + """ + DIO#(0:23) + Read or set the state of 1 bit of digital I/O. + Also configures the direction to input or output. For + more information go to + http://labjack.com/support/datasheets/t7/digital-io + + There are four different types of ports, FIO#(0:7), EIO#(0:7), + CIO#(0:3), MIO#(0:2) + + FIO vs. EIO vs. CIO vs. MIO + DIO is a generic name used for all digital I/O. The DIO are subdivided + into different groups called FIO, EIO, CIO, and MIO. + + Sometimes these are referred to as different "ports". + For example, FIO is an 8-bit port of digital I/O and EIO is a different + 8-bit port of digital I/O. + The different names (FIO vs. EIO vs. CIO vs. MIO) have little meaning, + and generally you can call these all DIO0-DIO22 and consider them + all the same. + + The source impedance of an FIO line is about 550 ohms, whereas the + source impedance of EIO/CIO/MIO lines is about 180 ohms. + Source impedance might be important when sourcing or sinking + substantial currents, such as when controlling relays. + """ + return ljm.eReadName(self.handle, "DIO{}".format(key)) + + @digital_IO.setter + def digital_IO(self, key, state): + return ljm.eWriteName(self.handle, "DIO{}".format(key), state) + + def writeName(self, name, value): + """ Write one value specified by name.""" + return ljm.eWriteName(self.handle, name, value) + + def writeNames(self, names, values): + """ Write multiple values specified by name.""" + return ljm.eWriteNames(self.handle, len(names), names, values) + + def address(self, name): + """ + Takes a Modbus register name as input and produces the corresponding + Modbus address and type. These two values can serve as input to + functions that have Address and Type as input parameters. + + Name [in] + A null-terminated c-string register identifier. This register + identifiers can be a register name or a register alternate name. + + Address [out] + Output parameter containing the address specified by Name. + + Type [out] + Output parameter containing the type specified by Name. + """ + return ljm.nameToAddress(name) + + def addresses(self, names): + """ + Takes a list of Modbus register names as input and produces two lists + as output - the corresponding Modbus addresses and types. These two + lists can serve as input to functions that have Address/aAddresses and + Type/aTypes as input parameters. + + NumFrames [in] + The number of names in aNames and the allocated size of aAddresses and + aTypes. + + aNames [in] + An array of null-terminated c-string register identifiers. These + register identifiers can be register names or register alternate names. + + aAddresses [out] + An output array containing the addresses specified by aNames in the + same order, must be allocated to the size NumFrames before calling + LJM_NamesToAddresses. + + aTypes [out] + An output array containing the types specified by aNames in the same + order, must be allocated to the size NumFrames before calling + LJM_NamesToAddresses. + """ + return ljm.namesToAddresses(len(names), names) + + def streamStart(self, scansPerRead, scanList, scanRate): + """ Initializes a stream object and begins streaming. This function + creates a buffer in memory that holds data from the device, so that + higher data throughput can be achieved. + ScansPerRead [in] + The number of scans returned by each call to the LJM_eStreamRead + function. Increase this parameter to get more data per call of + LJM_eStreamRead. A typical value would be equal to ScanRate or + 1/2 ScanRate, which results in a read once or twice per second. + + NumAddresses [in] + The number of addresses in aScanList. The size of the aScanList array. + + aScanList [in] + An array of addresses to stream. The scan list is the list of things + that are to be buffered by LJM, and returned with LJM_eStreamRead. Find + addresses in the Modbus Map. For example, to stream AIN3 add the + address 6 to the scan list. + + ScanRate [in/out] + A pointer that sets the desired number of scans per second. Upon + successful return of this function, ScanRate will be updated to the + actual scan rate that the device will use. Keep in mind that data rate + limits are specified in Samples/Second which is equal to + NumAddresses * Scans/Second. + + """ + scanRate = ljm.eStreamStart(self.handle, scansPerRead, len(scanList), + scanList, scanRate) + return scanRate + + def streamRead(self): + """ Returns data from an initialized and running LJM stream buffer. + Waits for data to become available, if necessary. + """ + return ljm.eStreamRead(self.handle) + + def streamStop(self): + """ Stops LJM from storing any more data from the device. LJM will + maintain any previously collected data in the buffer to be read. Stops + the device from collecting data in stream mode. + """ + return ljm.eStreamStop(self.handle) + +if __name__ == '__main__': + + """ + This code tests the driver. + """ + + placa = T7() + """ + An instance of the labjack T7 is created + """ + + print(placa.idn) + """ + Serial number is printed + """ + + for i in range(0, 4): + print(placa.analog_in[i]) + """ + Voltages of analog in ports are read + """ + + for i in range(0, 2): + placa.analog_out[i] = 1 * V + + """ + Random voltages are set in the analog out ports + """ + + for i in range(0, 2): + print(placa.analog_out[i]) + + """ + Voltages of analog out ports are read + """ + + for i in range(0, 2): + placa.analog_out[i] = (i+1)*V + + """ + Specific voltages are set in the analog out ports + """ + + for i in range(0, 2): + print(placa.analog_out[i]) + + """ + Voltages of analog out ports are read again + """ + + for i in range(0, 23): + print(placa.digital_IO[i]) + + """ + Voltages of digital in/out ports are read + """ + + placa.finalize() diff --git a/lantz/drivers/legacy/laserquantum/__init__.py b/lantz/drivers/legacy/laserquantum/__init__.py new file mode 100644 index 0000000..6045940 --- /dev/null +++ b/lantz/drivers/legacy/laserquantum/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.legacy.laserquantum + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :company: Laser Quantum + :description: Laser products. + :website: http://www.laserquantum.com/ + + ---- + + :copyright: 2015 by Lantz Authors, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" + +from .ventus import Ventus + +__all__ = ['Ventus'] diff --git a/lantz/drivers/legacy/laserquantum/ventus.py b/lantz/drivers/legacy/laserquantum/ventus.py new file mode 100644 index 0000000..b8c2b38 --- /dev/null +++ b/lantz/drivers/legacy/laserquantum/ventus.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.legacy.laserquantum.ventus + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2015 by Lantz Authors, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" + +from lantz import Action, Feat +from lantz.drivers.legacy.serial import SerialDriver +from lantz import Q_ + +mW = Q_(1, 'mW') + + +class Ventus(SerialDriver): + """Driver for the Laser Quantum Ventus 532 nm 1.5 W laser + """ + + ENCODING = 'ascii' + + RECV_TERMINATION = '\r\n' + SEND_TERMINATION = '\r' + + BAUDRATE = 19200 + BYTESIZE = 8 + PARITY = 'none' + STOPBITS = 1 + + #: flow control flags + RTSCTS = False + DSRDTR = False + XONXOFF = False + + def initialize(self): + super().initialize() + self.power_sp_state = self.power + self.power_sp = self.power_sp_state + + @Feat(read_once=True) + def idn(self): + """Identification of the device + """ + return 'Ventus 532 nm, 1.5W' + + @Feat() + def status(self): + """Returns status of interlock circuitry + """ + return self.query('STAT?') + + # ENABLE LASER + @Feat(values={True: 'ENABLED', False: 'DISABLED'}) + def enabled(self): + """Method for turning on the laser + """ + return self.query('STATUS?') + + @enabled.setter + def enabled(self, value): + if value == 'ENABLED': + self.query('ON') + else: + self.query('OFF') + + # LASER'S CONTROL MODE AND SET POINT + + @Feat(values={'APC': 'POWER', 'ACC': 'CURRENT'}) + def ctl_mode(self): + """To handle laser diode current (mA) in Active Current Control Mode + """ + return self.query('CONTROL?') + + @ctl_mode.setter + def ctl_mode(self, value): + self.query('CONTROL={}'.format(value)) + + @Feat(limits=(0, 100)) + def current_sp(self): + """# is 0 to 100. This sets the current to the laser diodes as a + percentage of the current maximum. For example, to set a diode current + of 85% of maximum, type CURRENT=85, followed by a carriage RETURN + """ + if self.ctl_mode == 'CURRENT': + return self.query('CURRENT?') # CHECK FORMAT + else: + return 'Laser not in current mode' + + @current_sp.setter + def current_sp(self, value): + self.query('CURRENT={}'.format(value)) + +# @Feat(units='mW') + @Feat() + def power_sp(self): + """To handle output power set point (mW) in APC Mode + """ + return self.power_sp_state + + @power_sp.setter + def power_sp(self, value): + self.query('POWER={}'.format(value)) + self.power_sp_state = value * mW + + # LASER'S CURRENT STATUS + + @Feat(units='mW') + def power(self): + """To get the laser emission power (mW) + """ + raw = self.query('POWER?') + return int(raw.split('mW')[0]) + + @Feat(units='degC') + def laserTemp(self): + """Returns the temperature of the laser head in degrees Celsius + """ + raw = self.query('LASTEMP?') + return float(raw.split('C')[0]) + + @Feat(units='degC') + def psuTemp(self): + """Returns the temperature of the PSU in degrees Celsius + """ + raw = self.query('PSUTEMP?') + return float(raw.split('C')[0]) + + @Feat() + def timers(self): + """Returns the PSU ‘on’ time, Laser Enabled Time and Laser Operation + Time + """ + raw1 = self.query('TIMERS?') + raw2 = self.query('TIMERS?') + raw3 = self.query('TIMERS?') + return raw1, raw2, raw3 + + # UNTESTED + def recalibrate(self, value): + """Recalibrates the internal laser power meter to the measured power + in mW + """ + self.query('ACTP={}'.format(value.magnitude)) + + # UNTESTED + @Action() + def store(self): + """Stores the recalibrated ACTP power into long term memory + """ + self.query('WRITE') + + +if __name__ == '__main__': + import argparse + import lantz.log + + parser = argparse.ArgumentParser(description='Test Kentech HRI') + parser.add_argument('-i', '--interactive', action='store_true', + default=False, help='Show interactive GUI') + parser.add_argument('-p', '--port', type=str, default='COM10', + help='Serial port to connect to') + + args = parser.parse_args() + lantz.log.log_to_screen(lantz.log.DEBUG) + with Ventus(args.port) as inst: + if args.interactive: + from lantz.ui.qtwidgets import start_test_app + start_test_app(inst) + else: + # Add your test code here + print('Non interactive mode') + print(inst.query('POWER?')) + print(inst.power) +# print(inst.shg_tuning) diff --git a/lantz/drivers/legacy/prior/__init__.py b/lantz/drivers/legacy/prior/__init__.py index 93571e1..2f29af2 100644 --- a/lantz/drivers/legacy/prior/__init__.py +++ b/lantz/drivers/legacy/prior/__init__.py @@ -9,10 +9,11 @@ ---- - :copyright: 2015 by Lantz Authors, see AUTHORS for more details. + :copyright: 2016 by Lantz Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from .nanoscanz import NanoScanZ +from .proscaniii import ProScanIII -__all__ = ['NanoScanZ'] +__all__ = ['NanoScanZ', 'ProScanIII'] diff --git a/lantz/drivers/legacy/prior/nanoscanz.py b/lantz/drivers/legacy/prior/nanoscanz.py index d3a100a..088889f 100644 --- a/lantz/drivers/legacy/prior/nanoscanz.py +++ b/lantz/drivers/legacy/prior/nanoscanz.py @@ -9,17 +9,17 @@ from lantz import Action, Feat from lantz.drivers.legacy.serial import SerialDriver -from lantz.errors import InstrumentError + class NanoScanZ(SerialDriver): """Driver for the NanoScanZ Nano Focusing Piezo Stage from Prior. """ ENCODING = 'ascii' - + RECV_TERMINATION = '\r' SEND_TERMINATION = '\r' - + BAUDRATE = 9600 BYTESIZE = 8 PARITY = 'none' @@ -30,35 +30,11 @@ class NanoScanZ(SerialDriver): DSRDTR = False XONXOFF = False - - def query(self, command, *, send_args=(None, None), recv_args=(None, None)): - """Send query to the stage and return the answer, after handling - possible errors. - - :param command: command to be sent to the instrument - :type command: string - - :param send_args: (termination, encoding) to override class defaults - :param recv_args: (termination, encoding) to override class defaults - """ - ans = super().query(command, send_args=send_args, recv_args=recv_args) - if ans[0] == 'E': - code = ans[2] - if code == '8': - raise InstrumentError('Value out of range') - elif code == '4': - raise InstrumentError('Command parse error, ie wrong number of parameters') - elif code == '5': - raise InstrumentError('Unknown command') - elif code == '2': - raise InstrumentError('Invalid checksum') - - return ans - - @Feat(values={9600,19200,38400}) + @Feat(values={9600, 19200, 38400}) def baudrate(self): """Reports and sets the baud rate. - NOTE: DO NOT change the baud rate of the Piezo controller when daisy chained to ProScan. + NOTE: DO NOT change the baud rate of the Piezo controller when daisy + chained to ProScan. """ return self.query('BAUD') @@ -66,86 +42,72 @@ def baudrate(self): def baudrate(self, value): self.query('BAUD {}'.format(value)) - - @Feat(values={True: '4', False: '00000'}) + @Feat(values={True: '4', False: '0'}) def moving(self): """Returns the movement status, 0 stationary, 4 moving """ return self.query('$') - + @Feat(read_once=True) def idn(self): """Identification of the device """ - return self.query('DATE') +' '+ self.query('SERIAL') - - @Feat(units = 'micrometer') + return self.query('DATE') + ' ' + self.query('SERIAL') + + @Feat(units='micrometer') def position(self): """Gets and sets current position. - If the value is set to z = 0, the display changes to REL 0 (relative display mode). To return to ABS mode use inst.move_absolute(0) and then inst.position = 0. Thus, the stage will return to 0 micrometers and the display screen will switch to ABS mode. + If the value is set to z = 0, the display changes to REL 0 + (relative display mode). To return to ABS mode use + inst.move_absolute(0) and then inst.position = 0. Thus, the stage will + return to 0 micrometers and the display screen will switch to ABS mode. """ return self.query('PZ') @position.setter def position(self, value): self.query('PZ {}'.format(value)) - + @Action() - def zero_position(self): + def goZero(self): """Move to zero including any position redefinitions done by the position Feat """ self.query('M') - - - @Action(units = 'micrometer', limits=(100,)) - def move_absolute(self, value): + + @Action(units='micrometer', limits=(100,)) + def moveAbs(self, value): """Move to absolute position n, range (0,100). - This is a "real" absolute position and is independent of any relative offset added by the position Feat. + This is a "real" absolute position and is independent of any relative + offset added by the position Feat. """ - self.query('V {}'.format()) - - + self.query('V {}'.format(value)) + @Action() - def move_relative(self, value): - """ - Move the stage position relative to the current position by an amount determined by 'value'. - If value is given in micrometer, thats the amount the stage is going to move, in microns. - If value is given in steps, the stage will move a distance value.magnitude * step. The step is defined by the step Feat - """ + def moveRel(self, value): """ - try: - u = value.units - if value.magnitude > 0: - self.query('U {}'.format(value.magnitude)) - if value.magnitude < 0: - self.query('D {}'.format(-value.magnitude)) - except: - if isinstance(value, int): - if value > 0: - for x in range(0,value): - self.query('U') - elif value < 0: - for x in range(0,value): - self.query('D') - else: - raise ValueError('Specify the translation distance in micrometer unit') + Move the stage position relative to the current position by an amount + determined by 'value'. + If value is given in micrometer, thats the amount the stage is going to + move, in microns. + If value is given in steps, the stage will move a distance + value.magnitude * step. The step is defined by the step Feat """ - try: - if value.units == 'micrometer': - if value.magnitude > 0: - self.query('U {}'.format(value.magnitude)) - elif value.magnitude < 0: - self.query('D {}'.format(-value.magnitude)) - elif value.units == 'steps': - if value.magnitude > 0: - for x in range(0,value.magnitude): - self.query('U') - elif value.magnitude < 0: - for x in range(0,-value.magnitude): - self.query('D') - except: - raise ValueError('Specify the translation distance in micrometers or steps') - + # Relative movement in microns + if value.magnitude > 0: + self.query('U {}'.format(value.magnitude)) + elif value.magnitude < 0: + self.query('D {}'.format(-value.magnitude)) + + @Action() + def moveRelSteps(self, value): + # Relative movement in 'steps' + value = int(value) + if value > 0: + for x in range(0, value): + self.query('U') + elif value.magnitude < 0: + for x in range(0, -value): + self.query('D') @Feat(units='micrometer') def step(self): @@ -154,40 +116,29 @@ def step(self): return self.query('C') @step.setter - def step (self, value): + def step(self, value): self.query('C {}'.format(value)) - - - + @Feat(read_once=True) def software_version(self): """Software version - """ + """ return self.query('VER') -class NanoScanZ_chained(NanoScanZ): - """This is needed when the NanoScanZ controller is connected to a ProScanII controller through its RS-232-2 input - """ - def write(self, command, termination=None, encoding=None): - super().write('<' + command, termination=termination, encoding=encoding) - - def read(self, termination=None, encoding=None, recv_chunk=None): - return super().read(termination=termination, encoding=encoding)[1:] - if __name__ == '__main__': import argparse - import lantz.log +# import lantz.log parser = argparse.ArgumentParser(description='Test Prior NanoScanZ') parser.add_argument('-i', '--interactive', action='store_true', default=False, help='Show interactive GUI') - parser.add_argument('-p', '--port', type=str, default='17', + parser.add_argument('-p', '--port', type=str, default='COM5', help='Serial port to connect to') args = parser.parse_args() - lantz.log.log_to_screen(lantz.log.DEBUG) - with NanoScanZ(args.port) as inst: +# lantz.log.log_to_screen(lantz.log.DEBUG) + with NanoScanZ('COM5') as inst: if args.interactive: from lantz.ui.app import start_test_app start_test_app(inst) @@ -195,12 +146,11 @@ def read(self, termination=None, encoding=None, recv_chunk=None): from lantz import Q_ # Add your test code here print('Non interactive mode') - + um = Q_(1, 'micrometer') - + print(inst.idn) - print(inst.moving) - print(inst.position) - print(inst.step) - inst.step = 1 * um - print(inst.step) +# print(inst.position) +# print(inst.step) +# inst.step = 1 * um +# print(inst.step) diff --git a/lantz/drivers/legacy/prior/proscaniii.py b/lantz/drivers/legacy/prior/proscaniii.py new file mode 100644 index 0000000..a8a64db --- /dev/null +++ b/lantz/drivers/legacy/prior/proscaniii.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.legacy.prior.proscaniii + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2016 by Lantz Authors, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" + +import comtypes.client as com +from lantz import Driver, Feat, Action + + +class ProScanIII(Driver): + + def __init__(self, port, *args, **kwargs): + + self.lib = com.GetModule("Prior.dll") + self.port = port + + self.controller = com.CreateObject(self.lib.Scan()) + self.controller.Connect(self.port) + self.zobject = com.CreateObject(self.lib.Z()) + + @Feat(units='um', limits=(-10000, 10000)) + def zPosition(self): + '''Gets and sets current position. + If the value is set to z = 0, the display changes to REL 0 (relative + display mode). To return to ABS mode use inst.move_absolute(0) and then + inst.position = 0. Thus, the stage will return to 0 micrometers and the + display screen will switch to ABS mode. + ''' + return self.zobject.Position + + @zPosition.setter + def zPosition(self, value): + '''Gets and sets current position. + If the value is set to z = 0, the display changes to REL 0 (relative + display mode). To return to ABS mode use inst.move_absolute(0) and then + inst.position = 0. Thus, the stage will return to 0 micrometers and the + display screen will switch to ABS mode. + ''' + self.zobject.MoveToAbsolute(value) + + @Action() + def zMoveRelative(self, value): + try: + value.units + if value.to('um').magnitude > 0: + return self.zobject.MoveUp(value.to('um').magnitude) + + else: + return self.zobject.MoveDown(-value.to('um').magnitude) + except: + if value > 0: + return self.zobject.MoveUp(value) + + else: + return self.zobject.MoveDown(-value) + + @Feat() + def zUmPerRevolution(self): + return self.zobject.MicronsPerMotorRevolution + + @zUmPerRevolution.setter + def zUmPerRevolution(self, value): + self.zobject.MicronsPerMotorRevolution = value + + @Feat(values={'left': 1, 'right': -1}) + def zHostPosition(self): + return self.zobject.HostDirection + + @zHostPosition.setter + def zHostPosition(self, value): + self.zobject.HostDirection = value + + def finalize(self): + self.controller.DisConnect() + + +if __name__ == '__main__': + + from lantz import Q_ + + um = Q_(1, 'um') + + aa = ProScanIII(12) +# +# with NanoScanZ(12) as nano: +# print(nano.position) +# nano.position = -650 * um +# print(nano.position) +# print(nano.hostPosition) +# nano.hostPosition = 'left' +# print(nano.hostPosition) diff --git a/lantz/drivers/legacy/rgblasersystems/minilasevo.py b/lantz/drivers/legacy/rgblasersystems/minilasevo.py index f7e2d94..f0fda8e 100644 --- a/lantz/drivers/legacy/rgblasersystems/minilasevo.py +++ b/lantz/drivers/legacy/rgblasersystems/minilasevo.py @@ -3,7 +3,7 @@ lantz.drivers.legacy.rgblasersystems.minilasevo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :copyright: 2015 by Lantz Authors, see AUTHORS for more details. + :copyright: 2016 by Lantz Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ @@ -11,6 +11,7 @@ from lantz.drivers.legacy.serial import SerialDriver from lantz.errors import InstrumentError + class MiniLasEvo(SerialDriver): """Driver for any RGB Lasersystems MiniLas Evo laser. """ @@ -30,8 +31,8 @@ class MiniLasEvo(SerialDriver): DSRDTR = False XONXOFF = False - - def query(self, command, *, send_args=(None, None), recv_args=(None, None)): + def query(self, command, *, send_args=(None, None), + recv_args=(None, None)): """Send query to the laser and return the answer, after handling possible errors. @@ -44,7 +45,7 @@ def query(self, command, *, send_args=(None, None), recv_args=(None, None)): ans = super().query(command, send_args=send_args, recv_args=recv_args) # TODO: Echo handling code = ans[0] - if code !=0: + if code != 0: if code == '1': raise InstrumentError('Command invalid') elif code == '2': @@ -141,7 +142,7 @@ def control_mode(self): return ans # TEMPERATURE - + @Feat() def temperature(self): """Current temperature in ºC @@ -161,7 +162,7 @@ def temperature_max(self): return self.query('LTP?') # ENABLED REQUEST - + @Feat(values={True: '1', False: '0'}) def enabled(self): """Method for turning on the laser @@ -173,11 +174,9 @@ def enabled(self, value): self.query('O=' + value) # LASER POWER - + def initialize(self): super().initialize() - self.enabled = True - self.power = 0 self.feats.power.limits = (0, self.maximum_power.magnitude) @Feat(units='mW') @@ -187,12 +186,12 @@ def maximum_power(self): return float(self.query('LP?')) @Feat(units='mW') - def power(self): + def power_sp(self): """Gets and sets the emission power """ return float(self.query('P?')) - @power.setter + @power_sp.setter def power(self, value): self.query('P={:.1f}'.format(value)) @@ -221,7 +220,3 @@ def power(self, value): inst.power = 0 print(inst.idn) """ - - - - diff --git a/lantz/drivers/mpb/vfl.py b/lantz/drivers/mpb/vfl.py index 523a268..424eea8 100644 --- a/lantz/drivers/mpb/vfl.py +++ b/lantz/drivers/mpb/vfl.py @@ -19,8 +19,8 @@ class VFL(MessageBasedDriver): DEFAULTS = {'ASRL': {'write_termination': '\rD >', 'read_termination': '\r\n', - 'baud_rate': 1200, - 'bytesize': 8, + 'baud_rate': 9600, +# 'bytesize': 8, 'parity': constants.Parity.none, 'stop_bits': constants.StopBits.one, 'encoding': 'ascii', @@ -197,7 +197,7 @@ def tune_shg_stop(self): args = parser.parse_args() lantz.log.log_to_screen(lantz.log.DEBUG) - with VFL.from_serial_port(args.port) as inst: + with VFL('COM3') as inst: if args.interactive: from lantz.ui.qtwidgets import start_test_app start_test_app(inst) @@ -206,3 +206,4 @@ def tune_shg_stop(self): print('Non interactive mode') print(inst.idn) print(inst.shg_tuning) +# \ No newline at end of file diff --git a/lantz/drivers/piezosystemjena/__init__.py b/lantz/drivers/piezosystemjena/__init__.py new file mode 100644 index 0000000..99f1765 --- /dev/null +++ b/lantz/drivers/piezosystemjena/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from .nv401 import nv401 + +__all__ = ['nv401'] diff --git a/lantz/drivers/piezosystemjena/nv401.py b/lantz/drivers/piezosystemjena/nv401.py new file mode 100644 index 0000000..c5ebd02 --- /dev/null +++ b/lantz/drivers/piezosystemjena/nv401.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +from lantz import Action, Feat +from lantz.messagebased import MessageBasedDriver +from lantz.errors import InstrumentError +from pyvisa import constants +import time + + +class nv401(MessageBasedDriver): + """Driver for the nv401. + """ + + DEFAULTS = {'ASRL': {'write_termination': '\r', + 'read_termination': '\r', + 'baud_rate': 9600, + 'parity': constants.Parity.none, + 'stop_bits': constants.StopBits.one, + 'encoding': 'ascii', + 'timeout': 1000 + }} + + #: flow control flags + # RTSCTS = False + # DSRDTR = False + # XONXOFF = False + + def initialize(self): + super().initialize() + self.write('i1') + self.write('cl') + time.sleep(0.5) + + def query(self, command, *, send_args=(None, None), recv_args=(None, None)): + """Send query to the stage and return the answer, after handling + possible errors. + + :param command: command to be sent to the instrument + :type command: string + + """ + ans = super().query(command, send_args=send_args, recv_args=recv_args) + if 'err' in ans: + code = ans.split(',')[1] + if code == '1': + raise InstrumentError('unknown command') + elif code == '2': + raise InstrumentError('too many characters in the command') + elif code == '3': + raise InstrumentError('too many characters in the parameter') + elif code == '4': + raise InstrumentError('too many parameter') + elif code == '5': + raise InstrumentError('wrong character in parameter') + elif code == '6': + raise InstrumentError('wrong separator') + elif code == '7': + raise InstrumentError('overload') + return ans + + @Feat(units='micrometer') + def position(self): + ans = self.query('rd') + return float(ans.split(',')[1]) + + @position.setter + def position(self, value): + self.write('wr,{}'.format(round(value, 3))) + + @Action() + def zero_position(self): + self.write('wr,{}'.format(0)) + + @Action(units='micrometer', limits=(100,)) + def moveAbsolute(self, value): + self.write('wr,{}'.format(round(value, 3))) + + @Action(units='micrometer') + def moveRelative(self, value): + cur_pos = float(self.query('rd').split(',')[1]) + self.write('wr,{}'.format(round(cur_pos + value, 3))) \ No newline at end of file