|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import logging |
| 4 | +import os |
| 5 | +import sys |
| 6 | +import time |
| 7 | +from pathlib import Path |
| 8 | + |
| 9 | +if os.name == 'nt': |
| 10 | + pass |
| 11 | +else: |
| 12 | + pass |
| 13 | + |
| 14 | +import numpy as np |
| 15 | + |
| 16 | +from openlifu.bf.pulse import Pulse |
| 17 | +from openlifu.bf.sequence import Sequence |
| 18 | +from openlifu.db import Database |
| 19 | +from openlifu.geo import Point |
| 20 | +from openlifu.io.LIFUInterface import LIFUInterface |
| 21 | +from openlifu.plan.solution import Solution |
| 22 | + |
| 23 | +# set PYTHONPATH=%cd%\src;%PYTHONPATH% |
| 24 | +# python notebooks/test_watertank.py |
| 25 | + |
| 26 | +""" |
| 27 | +Test script to automate: |
| 28 | +1. Connect to the device. |
| 29 | +2. Test HVController: Turn HV on/off and check voltage. |
| 30 | +3. Test Device functionality. |
| 31 | +""" |
| 32 | + |
| 33 | +# Configure logging |
| 34 | +logger = logging.getLogger(__name__) |
| 35 | +logger.setLevel(logging.INFO) |
| 36 | + |
| 37 | +# Prevent duplicate handlers and cluttered terminal output |
| 38 | +if not logger.hasHandlers(): |
| 39 | + handler = logging.StreamHandler() |
| 40 | + handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) |
| 41 | + logger.addHandler(handler) |
| 42 | + logger.propagate = False |
| 43 | + |
| 44 | +log_interval = 1 # seconds; you can adjust this variable as needed |
| 45 | + |
| 46 | +# set focus |
| 47 | +xInput = 0 |
| 48 | +yInput = 0 |
| 49 | +zInput = 50 |
| 50 | + |
| 51 | +frequency_kHz = 400 # Frequency in kHz |
| 52 | +voltage = 20.0 # Voltage in Volts |
| 53 | +duration_msec = 5/400 # Pulse Duration in milliseconds |
| 54 | +interval_msec = 20 # Pulse Repetition Interval in milliseconds |
| 55 | +num_modules = 2 # Number of modules in the system |
| 56 | + |
| 57 | +use_external_power_supply = False # Select whether to use console or power supply |
| 58 | + |
| 59 | +console_shutoff_temp_C = 70.0 # Console shutoff temperature in Celsius |
| 60 | +tx_shutoff_temp_C = 70.0 # TX device shutoff temperature in Celsius |
| 61 | +ambient_shutoff_temp_C = 70.0 # Ambient shutoff temperature in Celsius |
| 62 | + |
| 63 | +#TODO: script_timeout_minutes = 30 # Prevent unintentionally leaving unit on for too long |
| 64 | +#TODO: log_temp_to_csv_file = True # Log readings to only terminal or both terminal and CSV file |
| 65 | + |
| 66 | +# Fail-safe parameters if the temperature jumps too fast |
| 67 | +rapid_temp_shutoff_C = 40 # Cutoff temperature in Celsius if it jumps too fast |
| 68 | +rapid_temp_shutoff_seconds = 5 # Time in seconds to reach rapid temperature shutoff |
| 69 | +rapid_temp_increase_per_second_shutoff_C = 3 # Rapid temperature climbing shutoff in Celsius |
| 70 | + |
| 71 | +peak_to_peak_voltage = voltage * 2 # Peak to peak voltage for the pulse |
| 72 | + |
| 73 | +here = Path(__file__).parent.resolve() |
| 74 | +db_path = here / ".." / "db_dvc" |
| 75 | +db = Database(db_path) |
| 76 | +arr = db.load_transducer(f"openlifu_{num_modules}x400_evt1_005") |
| 77 | +arr.sort_by_pin() |
| 78 | + |
| 79 | +target = Point(position=(xInput,yInput,zInput), units="mm") |
| 80 | +focus = target.get_position(units="mm") |
| 81 | +distances = np.sqrt(np.sum((focus - arr.get_positions(units="mm"))**2, 1)).reshape(1,-1) |
| 82 | +tof = distances*1e-3 / 1500 |
| 83 | +delays = tof.max() - tof |
| 84 | +print(f"TOF Max = {tof.max()*1e6} us") |
| 85 | + |
| 86 | +apodizations = np.ones((1, arr.numelements())) |
| 87 | +#active_element = 25 |
| 88 | +#active_element = np.arange(65,128) |
| 89 | +#apodizations = np.zeros((1, arr.numelements())) |
| 90 | +#apodizations[:, active_element-1] = 1 |
| 91 | + |
| 92 | +logger.info("Starting LIFU Test Script...") |
| 93 | +interface = LIFUInterface(ext_power_supply=use_external_power_supply) |
| 94 | +tx_connected, hv_connected = interface.is_device_connected() |
| 95 | + |
| 96 | +if not use_external_power_supply and not tx_connected: |
| 97 | + logger.warning("TX device not connected. Attempting to turn on 12V...") |
| 98 | + interface.hvcontroller.turn_12v_on() |
| 99 | + |
| 100 | + # Give time for the TX device to power up and enumerate over USB |
| 101 | + time.sleep(2) |
| 102 | + |
| 103 | + # Cleanup and recreate interface to reinitialize USB devices |
| 104 | + interface.stop_monitoring() |
| 105 | + del interface |
| 106 | + time.sleep(1) # Short delay before recreating |
| 107 | + |
| 108 | + logger.info("Reinitializing LIFU interface after powering 12V...") |
| 109 | + interface = LIFUInterface(ext_power_supply=use_external_power_supply) |
| 110 | + |
| 111 | + # Re-check connection |
| 112 | + tx_connected, hv_connected = interface.is_device_connected() |
| 113 | + |
| 114 | +if not use_external_power_supply: |
| 115 | + if hv_connected: |
| 116 | + logger.info(f" HV Connected: {hv_connected}") |
| 117 | + else: |
| 118 | + logger.error("❌ HV NOT fully connected.") |
| 119 | + sys.exit(1) |
| 120 | +else: |
| 121 | + logger.info(" Using external power supply") |
| 122 | + |
| 123 | +if tx_connected: |
| 124 | + logger.info(f" TX Connected: {tx_connected}") |
| 125 | + logger.info("✅ LIFU Device fully connected.") |
| 126 | +else: |
| 127 | + logger.error("❌ TX NOT fully connected.") |
| 128 | + sys.exit(1) |
| 129 | + |
| 130 | +stop_logging = False # flag to signal the logging thread to stop |
| 131 | + |
| 132 | +# Verify communication with the devices |
| 133 | +if not interface.txdevice.ping(): |
| 134 | + logger.error("Failed to ping the transmitter device.") |
| 135 | + sys.exit(1) |
| 136 | + |
| 137 | +if not use_external_power_supply and not interface.hvcontroller.ping(): |
| 138 | + logger.error("Failed to ping the console device.") |
| 139 | + sys.exit(1) |
| 140 | + |
| 141 | +if not use_external_power_supply: |
| 142 | + try: |
| 143 | + console_firmware_version = interface.hvcontroller.get_version() |
| 144 | + logger.info(f"Console Firmware Version: {console_firmware_version}") |
| 145 | + except Exception as e: |
| 146 | + logger.error(f"Error querying console firmware version: {e}") |
| 147 | +try: |
| 148 | + tx_firmware_version = interface.txdevice.get_version() |
| 149 | + logger.info(f"TX Firmware Version: {tx_firmware_version}") |
| 150 | +except Exception as e: |
| 151 | + logger.error(f"Error querying TX firmware version: {e}") |
| 152 | + |
| 153 | +logger.info("Enumerate TX7332 chips") |
| 154 | +num_tx_devices = interface.txdevice.enum_tx7332_devices() |
| 155 | +if num_tx_devices == 0: |
| 156 | + raise ValueError("No TX7332 devices found.") |
| 157 | +elif num_tx_devices == num_modules*2: |
| 158 | + logger.info(f"Number of TX7332 devices found: {num_tx_devices}") |
| 159 | + numelements = 32*num_tx_devices |
| 160 | +else: |
| 161 | + raise Exception(f"Number of TX7332 devices found: {num_tx_devices} != 2x{num_modules}") |
| 162 | + |
| 163 | +logger.info(f'Apodizations: {apodizations}') |
| 164 | +logger.info(f'Delays: {delays}') |
| 165 | + |
| 166 | +pulse = Pulse(frequency=frequency_kHz*1e3, duration=duration_msec*1e-3) |
| 167 | + |
| 168 | +sequence = Sequence( |
| 169 | + pulse_interval=interval_msec*1e-3, |
| 170 | + pulse_count=int(60/(interval_msec*1e-3)), |
| 171 | + pulse_train_interval=0, |
| 172 | + pulse_train_count=1 |
| 173 | +) |
| 174 | + |
| 175 | +pin_order = np.argsort([el.pin for el in arr.elements]) |
| 176 | +solution = Solution( |
| 177 | + delays = delays[:, pin_order], |
| 178 | + apodizations = apodizations[:, pin_order], |
| 179 | + transducer=arr, |
| 180 | + pulse = pulse, |
| 181 | + voltage=voltage, |
| 182 | + sequence = sequence |
| 183 | +) |
| 184 | +profile_index = 1 |
| 185 | +profile_increment = True |
| 186 | +trigger_mode = "single" |
| 187 | + |
| 188 | +if use_external_power_supply: |
| 189 | + logger.info(f"Using external power supply. Ensure HV is turned on and set to {voltage}V before starting the trigger.") |
| 190 | + |
| 191 | +interface.set_solution( |
| 192 | + solution=solution, |
| 193 | + profile_index=profile_index, |
| 194 | + profile_increment=profile_increment, |
| 195 | + trigger_mode=trigger_mode) |
| 196 | + |
| 197 | +logger.info("Get Trigger") |
| 198 | +trigger_setting = interface.txdevice.get_trigger_json() |
| 199 | +if trigger_setting: |
| 200 | + logger.info(f"Trigger Setting: {trigger_setting}") |
| 201 | +else: |
| 202 | + logger.error("Failed to get trigger setting.") |
| 203 | + sys.exit(1) |
| 204 | + |
| 205 | +duty_cycle = int((duration_msec/interval_msec) * 100) |
| 206 | +if duty_cycle > 50: |
| 207 | + logger.warning("❗❗ Duty cycle is above 50% ❗❗") |
| 208 | + |
| 209 | +logger.info(f"User parameters set: \n\ |
| 210 | + Module Invert: {arr.module_invert}\n\ |
| 211 | + Frequency: {frequency_kHz}kHz\n\ |
| 212 | + Voltage Per Rail: {voltage}V\n\ |
| 213 | + Voltage Peak to Peak: {peak_to_peak_voltage}V\n\ |
| 214 | + Duration: {duration_msec}ms\n\ |
| 215 | + Interval: {interval_msec}ms\n\ |
| 216 | + Duty Cycle: {duty_cycle}%\n\ |
| 217 | + Use External Power Supply: {use_external_power_supply}\n\ |
| 218 | + Initial Temp Safety Shutoff: Increase to {rapid_temp_shutoff_C}°C within {rapid_temp_shutoff_seconds}s of starting.\n\ |
| 219 | + General Temp Safety Shutoff: Increase of {rapid_temp_increase_per_second_shutoff_C}°C within {log_interval}s at any point.\n") |
| 220 | + |
| 221 | +def turn_off_console_and_tx(): |
| 222 | + if not use_external_power_supply: |
| 223 | + logger.info("Attempting to turn off High Voltage...") |
| 224 | + interface.hvcontroller.turn_hv_off() |
| 225 | + if interface.hvcontroller.get_hv_status(): |
| 226 | + logger.error("High Voltage is still on.") |
| 227 | + else: |
| 228 | + logger.info("High Voltage successfully turned off.") |
| 229 | + |
| 230 | + logger.info("Attempting to turn off 12V...") |
| 231 | + interface.hvcontroller.turn_12v_off() |
| 232 | + if interface.hvcontroller.get_12v_status(): |
| 233 | + logger.error("12V is still on.") |
| 234 | + else: |
| 235 | + logger.info("12V successfully turned off.") |
| 236 | + |
| 237 | +logger.info("turning HV ON") |
| 238 | +interface.hvcontroller.turn_hv_on() |
| 239 | +s = input("Press any key to start") |
| 240 | +logger.info("Sending Single Trigger...") |
| 241 | +interface.txdevice.start_trigger() |
| 242 | +time.sleep(0.1) |
| 243 | +interface.txdevice.stop_trigger() |
| 244 | +interface.hvcontroller.turn_hv_off() |
| 245 | +turn_off_console_and_tx() |
| 246 | +logger.info("Finished") |
0 commit comments