From 90f03c089f1a677b2fb509f0d829b3f302c4d07a Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Mon, 9 Feb 2026 10:20:48 -0600 Subject: [PATCH 1/5] ADD: Halo photonics lidar adaptive scanning code. --- docs/.DS_Store | Bin 6148 -> 6148 bytes .../user_guide/show_me_the_lakebreeze.rst | 17 ++ examples/README.txt | 4 +- notebooks/lake_breeze_detection_example.ipynb | 7 +- pyproject.toml | 1 + src/adam/testing/__init__.py | 1 + src/adam/testing/fake_lidar.py | 41 +++ src/adam/triggering/__init__.py | 1 + src/adam/triggering/halo_lidar.py | 249 ++++++++++++++++++ 9 files changed, 314 insertions(+), 7 deletions(-) create mode 100644 src/adam/testing/__init__.py create mode 100644 src/adam/testing/fake_lidar.py create mode 100644 src/adam/triggering/__init__.py create mode 100644 src/adam/triggering/halo_lidar.py diff --git a/docs/.DS_Store b/docs/.DS_Store index 375e40c5f8d1ace69802c263d6aff31c98d45125..9a874b5768f336915163bb88cd434575b859f936 100644 GIT binary patch delta 101 zcmZoMXfc=|&Zs)EP*|6dfq{XUp_rkFAvvWuIVUMUKL;cP224;IBml$$3>()+uur@Y nzL}jvfP+zW Date: Mon, 9 Feb 2026 16:03:14 -0600 Subject: [PATCH 2/5] ADD: Capabilities for using adaptie scanning on Halo Photonics lidars. --- src/adam/__init__.py | 6 +- src/adam/testing/__init__.py | 9 +- src/adam/testing/fake_lidar.py | 37 +++++-- src/adam/triggering/__init__.py | 2 +- src/adam/triggering/halo_lidar.py | 159 +++++++++++++-------------- src/adam/util/instrument_steering.py | 22 +++- tests/test_adaptive_scanning.py | 119 ++++++++++++++++++++ tests/test_util.py | 13 ++- 8 files changed, 257 insertions(+), 110 deletions(-) create mode 100644 tests/test_adaptive_scanning.py diff --git a/src/adam/__init__.py b/src/adam/__init__.py index 49b70f9..d3afaaa 100644 --- a/src/adam/__init__.py +++ b/src/adam/__init__.py @@ -1,12 +1,14 @@ """Top-level package for ATMOS Analogue Digital Twin.""" -__author__ = """Robert Jackson, Seongha Park""" -__version__ = '0.3.1' +__author__ = """Robert Jackson, Bhupendra Raut, Seongha Park""" +__version__ = '0.4.0' from . import io # noqa from . import model # noqa from . import vis # noqa from . import util # noqa +from . import testing # noqa +from . import triggering # noqa diff --git a/src/adam/testing/__init__.py b/src/adam/testing/__init__.py index 9982438..bc0015a 100644 --- a/src/adam/testing/__init__.py +++ b/src/adam/testing/__init__.py @@ -1 +1,8 @@ -from .fake_lidar import FakeSFTP # noqa \ No newline at end of file +import os + +from .fake_lidar import FakeSFTP, FakeSSHClient # noqa + +TEST_RHI_FILE = os.path.join(os.path.dirname(__file__), "data/test_scan_rhi.txt") +TEST_PPI_FILE = os.path.join(os.path.dirname(__file__), "data/test_scan_ppi.txt") +TEST_PPI_TRIGGERED_SCAN = os.path.join(os.path.dirname(__file__), "data/test_scan_ppi_lakebreeze_close.txt") +TEST_RHI_TRIGGERED_SCAN = os.path.join(os.path.dirname(__file__), "data/test_scan_rhi_lakebreeze.txt") diff --git a/src/adam/testing/fake_lidar.py b/src/adam/testing/fake_lidar.py index 310dd57..ba69723 100644 --- a/src/adam/testing/fake_lidar.py +++ b/src/adam/testing/fake_lidar.py @@ -1,27 +1,42 @@ import os +import logging class FakeSFTP: """ A fake SFTP client for testing purposes. It simulates the behavior of an SFTP client by storing files in the current directory. """ - def __init__(self): - if not os.path.exists("C:/Lidar/System/Scan parameters/"): - os.makedirs("C:/Lidar/System/Scan parameters/") - if not os.path.exists("C:/Users/End User/DynScan/"): - os.makedirs("C:/Users/End User/DynScan/") + def __init__(self): + self.wd = os.path.dirname(__file__) + logging.info(f"Working directory for fake SFTP: {self.wd}") + scan_params_path = os.path.join(self.wd, "C:/Lidar/System/Scan parameters/") + dynscan_path = os.path.join(self.wd, "C:/Users/End User/DynScan/") + os.makedirs(scan_params_path, exist_ok=True) + os.makedirs(dynscan_path, exist_ok=True) self.files = [] - def listdir(self, path): return os.listdir("." + path) - def get(self, remote, local): open(local, "wb").write(self.files[remote]) + + def listdir(self, path): return os.listdir(os.path.join(self.wd, path)) + def get(self, remote, local): + remote_path = os.path.join(self.wd, remote) + with open(remote_path, "rb") as f: + data = f.read() + with open(local, "wb") as f: + f.write(data) def put(self, local, remote): - with open("." + remote, "wb") as f: + logging.info(f"Putting file {local} to {remote} in fake SFTP at {self.wd}.") + if remote.startswith("/"): + remote = remote[1:] + remote_path = os.path.join(self.wd, remote) + with open(remote_path, "wb") as f: f.write(open(local, "rb").read()) - self.files += "." + remote + self.files.append(remote_path) def close(self): for file in self.files: os.remove(file) - os.removedirs("C:/Lidar/System/Scan parameters/") - os.removedirs("C:/Users/End User/DynScan/") + logging.info("Removed files from fake SFTP.") + os.removedirs(os.path.join(self.wd, "C:/Lidar/System/Scan parameters/")) + logging.info("Removed scan parameters directory from fake SFTP.") + os.removedirs(os.path.join(self.wd, "C:/Users/End User/DynScan/")) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() diff --git a/src/adam/triggering/__init__.py b/src/adam/triggering/__init__.py index 6c7ddc8..4ae7898 100644 --- a/src/adam/triggering/__init__.py +++ b/src/adam/triggering/__init__.py @@ -1 +1 @@ -from .halo_lidar import make_scan_file, send_scan_file, get_file, trigger_lidar_ppis_from_mask, trigger_lidar_RHI_from_mask # noqa \ No newline at end of file +from .halo_lidar import make_scan_file, send_scan, trigger_lidar_ppis_from_mask, trigger_lidar_rhi_from_mask # noqa \ No newline at end of file diff --git a/src/adam/triggering/halo_lidar.py b/src/adam/triggering/halo_lidar.py index e213020..8b8bffd 100644 --- a/src/adam/triggering/halo_lidar.py +++ b/src/adam/triggering/halo_lidar.py @@ -3,6 +3,7 @@ import datetime import os import xarray as xr +import logging from ..util import azimuth_point @@ -10,7 +11,8 @@ def make_scan_file(elevations, azimuths, out_file_name, azi_speed=1., el_speed=0.1, wait=0, acceleration=30, repeat=7, - rays_per_point=2, dyn_csm=False): + rays_per_point=20, dyn_csm=False, + AZ_COUNTS_PER_ROT=500000, EL_COUNTS_PER_ROT=250000): """ Makes a CSM scanning strategy file for a Halo Photonics Doppler Lidar. @@ -25,10 +27,28 @@ def make_scan_file(elevations, azimuths, If this is a 2-tuple, then this script will generate a PPI from min_azi to max_azi. out_file_name: str The output name of the file. - beam_width: float - The spacing between beams. + azi_speed: float + The speed of the azimuth motor in degrees per second. + el_speed: float + The speed of the elevation motor in degrees per second. + wait: int + The wait time in milliseconds at each point in the scan. + acceleration: int + The acceleration of the motor in ticks per second squared. This is a constant that depends on the lidar hardware. + repeat: int + The number of times to repeat the scan strategy. This is used to ensure that the lidar collects enough data points for each scan. + rays_per_point: int + The number of rays to collect at each point in the scan. This is used to ensure + that the lidar collects enough data points for each scan. dyn_csm: bool Set to True to send CSM assuming Dynamic CSM mode + AZ_COUNTS_PER_ROT: int + The number of counts per rotation for the azimuth motor. This is a constant that depends + on the lidar hardware and is used to convert from degrees to the encoded values that the lidar uses for its scan strategy. + EL_COUNTS_PER_ROT: int + The number of counts per rotation for the elevation motor. This is a constant that depends + on the lidar hardware and is used to convert from degrees to the encoded values that the lidar uses + for its scan strategy. Returns ------- @@ -95,84 +115,29 @@ def send_scan(file_name, lidar_ip_addr, lidar_uname, lidar_pwd, out_file_name='u ssh.connect(lidar_ip_addr, username=lidar_uname, password=lidar_pwd) print("Connected to the Lidar!") close_client = True - - with ssh.open_sftp() as sftp: + + if close_client: + with ssh.open_sftp() as sftp: + if dyn_csm is False: + logging.info(f"Writing {out_file_name} on lidar.") + sftp.put(file_name, "/C:/Lidar/System/Scan parameters/%s" % out_file_name) + else: + sftp.put(file_name, f"/C:/Users/End User/DynScan/{out_file_name}") + else: + sftp = ssh.open_sftp() if dyn_csm is False: print(f"Writing {out_file_name} on lidar.") sftp.put(file_name, "/C:/Lidar/System/Scan parameters/%s" % out_file_name) else: sftp.put(file_name, f"/C:/Users/End User/DynScan/{out_file_name}") - if close_client: - ssh.close() - -def get_file(time, lidar_ip_addr, lidar_uname, lidar_pwd, client=None): - """ - Gets all files from the lidar within the previous and current hour. If no files are found, prints a message and returns. - - Parameters - ---------- - time: datetime - The time for which to retrieve the file. This function will look for files from the previous hour and the current hour. - lidar_ip_addr: - IP address of the lidar. - lidar_uname: - The username of the lidar. - lidar_password: - The lidar's password. - client: paramiko.SSHClient, optional - An optional SSH client to use for the connection. If not provided, a new client will be created and closed within this function. - - Returns - ------- - None - This function does not return anything. It retrieves files from the lidar and saves them to the current directory. - - """ - if client is not None: - ssh = client - close_client = False - else: - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(lidar_ip_addr, username=lidar_uname, password=lidar_pwd) - print("Connected to the Lidar!") - close_client = True - - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - print("Connecting to %s" % lidar_ip_addr) - ssh.connect(lidar_ip_addr, username=lidar_uname, password=lidar_pwd) - print("Connected to the Lidar!") - year = time.year - day = time.day - month = time.month - hour = time.hour - prev_hour = time - datetime.timedelta(hours=1) - file_path = "/C:/Lidar/Data/Proc/%d/%d%02d/%d%02d%02d/" % (year, year, month, year, month, day) - print(file_path) - with ssh.open_sftp() as sftp: - file_list = sftp.listdir(file_path) - time_string = '%d%02d%02d_%02d' % (year, month, day, hour) - time_string_prev = '%d%02d%02d_%02d' % (prev_hour.year, prev_hour.month, prev_hour.day, prev_hour.hour) - file_name = None - - for f in file_list: - if time_string in f or time_string_prev in f: - file_name = f - base, name = os.path.split(file_name) - print(print(file_name)) - sftp.get(os.path.join(file_path, file_name), name) - if file_name is None: - print("%s not found!" % str(time)) - if close_client: - ssh.close() - - return if close_client: ssh.close() + def trigger_lidar_ppis_from_mask(rad_scan, lidar_lat, lidar_lon, lidar_ip_addr, lidar_uname, lidar_pwd, elevations, - az_width=30., az_res=2., out_file_name='user.txt', dyn_csm=False): + az_width=30., out_file_name='user.txt', dyn_csm=False, + max_distance=5000, client=None): """ Triggers a PPI scan on the lidar using a scan strategy generated from a lake breeze mask. @@ -198,20 +163,32 @@ def trigger_lidar_ppis_from_mask(rad_scan, lidar_lat, lidar_lon, lidar_ip_addr, az_width: float The width of the azimuth scan in degrees. The scan will be centered around the azimuth of the largest lake breeze region as determined by the model's radar image and the location of the lidar. - az_res: float - The resolution of the azimuth scan in degrees. This determines how many points will be in the scan. - For example, if az_width is 30 and az_res is 2, then - there will be 15 points in the azimuth scan (from -15 to +15 degrees around the center azimuth). + max_distance: float + The maximum distance from the lidar to the lake breeze region for the scan to be triggered. + client: paramiko.SSHClient, optional + An optional SSH client to use for the connection. If not provided, a new client will be created + and closed within the send_scan function. + + Returns + ------- + bool + Returns True if the scan was triggered, and False if the scan was not triggered due to the distance from the lidar + to the lake breeze region being greater than max_distance. """ - middle_azimuth = azimuth_point(lidar_lat, lidar_lon, rad_scan) - azimuths = np.arange(middle_azimuth - az_width/2, middle_azimuth + az_width/2, az_res) + middle_azimuth, lat, lon, dist = azimuth_point(lidar_lon, lidar_lat, rad_scan) + if dist > max_distance: # If the distance is greater than max_distance, don't trigger the scan + logging.info(f"Distance from lidar to lake breeze region is {dist} meters. Not triggering scan.") + return False + azimuths = np.array([middle_azimuth - az_width/2, middle_azimuth + az_width/2]) make_scan_file(elevations, azimuths, out_file_name, dyn_csm=dyn_csm) - send_scan(out_file_name, lidar_ip_addr, lidar_uname, lidar_pwd, out_file_name=out_file_name, dyn_csm=dyn_csm) + send_scan(out_file_name, lidar_ip_addr, lidar_uname, lidar_pwd, out_file_name=out_file_name, dyn_csm=dyn_csm, + client=client) + return True -def trigger_lidar_RHI_from_mask(rad_scan, lidar_lat, lidar_lon, lidar_ip_addr, lidar_uname, lidar_pwd, elevations, - out_file_name='user.txt', dyn_csm=False): +def trigger_lidar_rhi_from_mask(rad_scan, lidar_lat, lidar_lon, lidar_ip_addr, lidar_uname, lidar_pwd, elevations, + out_file_name='user.txt', dyn_csm=False, max_distance=5000, client=None): """ Triggers a PPI scan on the lidar using a scan strategy generated from a lake breeze mask. @@ -241,9 +218,25 @@ def trigger_lidar_RHI_from_mask(rad_scan, lidar_lat, lidar_lon, lidar_ip_addr, l The resolution of the azimuth scan in degrees. This determines how many points will be in the scan. For example, if az_width is 30 and az_res is 2, then there will be 15 points in the azimuth scan (from -15 to +15 degrees around the center azimuth). + max_distance: float + The maximum distance from the lidar to the lake breeze region for the scan to be triggered. + client: paramiko.SSHClient, optional + An optional SSH client to use for the connection. If not provided, a new client will be created + and closed within the send_scan function. + + Returns + ------- + bool + Returns True if the scan was triggered, and False if the scan was not triggered due to + the distance from the lidar to the lake breeze region being greater than max_distance. """ - middle_azimuth = azimuth_point(lidar_lat, lidar_lon, rad_scan) - azimuths = middle_azimuth + middle_azimuth, lat, lon, dist = azimuth_point(lidar_lon, lidar_lat, rad_scan) + if dist > max_distance: # If the distance is greater than max_distance, don't trigger the scan + logging.info(f"Distance from lidar to lake breeze region is {dist} meters. Not triggering scan.") + return False + azimuths = [middle_azimuth] make_scan_file(elevations, azimuths, out_file_name, dyn_csm=dyn_csm) - send_scan(out_file_name, lidar_ip_addr, lidar_uname, lidar_pwd, out_file_name=out_file_name, dyn_csm=dyn_csm) \ No newline at end of file + send_scan(out_file_name, lidar_ip_addr, lidar_uname, lidar_pwd, out_file_name=out_file_name, dyn_csm=dyn_csm, + client=client) + return True \ No newline at end of file diff --git a/src/adam/util/instrument_steering.py b/src/adam/util/instrument_steering.py index b5f81e5..bccb65b 100644 --- a/src/adam/util/instrument_steering.py +++ b/src/adam/util/instrument_steering.py @@ -1,9 +1,9 @@ import numpy as np - +import logging from adam.io import RadarImage from scipy.ndimage import center_of_mass, label -def azimuth_point(instrument_lat, instrument_lon, +def azimuth_point(instrument_lon, instrument_lat, radar_image: RadarImage, index=None, area_threshold=20): """ @@ -58,10 +58,20 @@ def azimuth_point(instrument_lat, instrument_lon, num_x = len(radar_image.grid_x) center_x = radar_image.grid_x[int(center[1])] center_y = radar_image.grid_y[int(center[0])] - instrument_x = radar_image.grid_x[num_x - lon_index] + logging.info(f"Center of mass: {center}, Center lat/lon: {center_y}, {center_x}") + instrument_x = radar_image.grid_x[lon_index] instrument_y = radar_image.grid_y[lat_index] - - angle = np.atan2((center_x - instrument_x), (center_y - instrument_y)) + logging.info(f"Instrument lat/lon: {instrument_y}, {instrument_x}") + angle = np.arctan2((center_x - instrument_x), (center_y - instrument_y)) deg_angle = np.rad2deg(angle) deg_angle = (deg_angle + 360) % 360 - return deg_angle, lats[int(center[0])], lons[int(center[1])] \ No newline at end of file + + # Get the distance from the instrument to the nearest point in the lake breeze region + x, y = np.meshgrid(radar_image.grid_x, radar_image.grid_y) + dist = np.sqrt((x - instrument_x)**2 + (y - instrument_y)**2) + dist = dist[mask == 1] + dist = np.min(dist) + return deg_angle, lats[int(center[0])], lons[int(center[1])], dist + + + \ No newline at end of file diff --git a/tests/test_adaptive_scanning.py b/tests/test_adaptive_scanning.py new file mode 100644 index 0000000..4e6a136 --- /dev/null +++ b/tests/test_adaptive_scanning.py @@ -0,0 +1,119 @@ +import numpy as np +import os + +def test_make_scan_file(): + from adam.triggering.halo_lidar import make_scan_file + from adam.testing import TEST_RHI_FILE, TEST_PPI_FILE + elevations = [0, 90] + azimuths = [90] + out_file_name = 'test_scan_rhi.txt' + make_scan_file(elevations, azimuths, el_speed=2, out_file_name=out_file_name) + + with open(out_file_name, 'r') as f: + lines = f.readlines() + with open(TEST_RHI_FILE, 'r') as f: + expected_lines = f.readlines() + + assert len(lines) == len(expected_lines) + for i, (line, expected_line) in enumerate(zip(lines, expected_lines)): + assert line == expected_line, f"Line {i} does not match expected output.\nGot: {line}\nExpected: {expected_line}" + + elevations = [0, 5, 10] + azimuths = [90, 180] + out_file_name = 'test_scan_ppi.txt' + make_scan_file(elevations, azimuths, el_speed=2, out_file_name=out_file_name) + + with open(out_file_name, 'r') as f: + lines = f.readlines() + with open(TEST_PPI_FILE, 'r') as f: + expected_lines = f.readlines() + + assert len(lines) == len(expected_lines) + for i, (line, expected_line) in enumerate(zip(lines, expected_lines)): + assert line == expected_line, f"Line {i} does not match expected output.\nGot: {line}\nExpected: {expected_line}" + +def test_send_scan(): + from adam.triggering.halo_lidar import send_scan + from adam.testing import TEST_RHI_FILE + from adam.testing.fake_lidar import FakeSSHClient + lidar_ip_addr = None + lidar_uname = None + lidar_pwd = None + in_file_name = TEST_RHI_FILE + out_file_name = 'test_rhi_scan.txt' + out_file_name2 = 'test_rhi_scan_copy.txt' + with FakeSSHClient() as client: + send_scan(in_file_name, lidar_ip_addr, lidar_uname, lidar_pwd, out_file_name=out_file_name, + client=client) + with open(client.sftp.files[0], 'r') as f: + lines = f.readlines() + with open(TEST_RHI_FILE, 'r') as f: + expected_lines = f.readlines() + assert len(lines) == len(expected_lines) + for i, (line, expected_line) in enumerate(zip(lines, expected_lines)): + assert line == expected_line, f"Line {i} does not match expected output.\nGot: {line}\nExpected: {expected_line}" + +def test_trigger_lidar_ppis_from_mask(): + import torch + import adam + torch.manual_seed(42) + rad_scan = adam.io.preprocess_radar_image('KLOT', '2025-07-15T18:00:00') + rad_scan = adam.model.infer_lake_breeze( + rad_scan, model_name='lakebreeze_best_model_fcn_resnet50') + result = adam.triggering.trigger_lidar_ppis_from_mask(rad_scan, 41.70101404798476, -87.99577278662817, + None, None, None, elevations=[0, 5, 10], az_width=30., + out_file_name='test_scan_ppi_lakebreeze.txt', dyn_csm=False) + assert result is False, "Expected the scan to not be triggered due to distance from lidar to lake breeze region being greater than max_distance." + + rad_scan = adam.io.preprocess_radar_image('KLOT', '2025-04-24T20:03:23') + rad_scan = adam.model.infer_lake_breeze( + rad_scan, model_name='lakebreeze_model_fcn_resnet50_no_augmentation') + with adam.testing.FakeSSHClient() as client: + result = adam.triggering.trigger_lidar_ppis_from_mask( + rad_scan, 41.70101404798476, -87.99577278662817, + None, None, None, elevations=[0, 5, 10], az_width=30., + out_file_name='test_scan_ppi_lakebreeze_close.txt', dyn_csm=False, client=client) + assert result is True, "Expected the scan to be triggered since the distance from lidar to lake breeze region is less than max_distance." + client.sftp.get(client.sftp.files[0], 'test_scan_ppi_lakebreeze_close_copy.txt') + with open('test_scan_ppi_lakebreeze_close_copy.txt', 'r') as f: + lines = f.readlines() + with open(adam.testing.TEST_PPI_TRIGGERED_SCAN, 'r') as f: + expected_lines = f.readlines() + assert len(lines) == len(expected_lines) + for i, (line, expected_line) in enumerate(zip(lines, expected_lines)): + assert line == expected_line, f"Line {i} does not match expected output.\nGot: {line}\nExpected: {expected_line}" + os.remove('test_scan_ppi_lakebreeze_close_copy.txt') + os.remove('test_scan_ppi_lakebreeze_close.txt') + os.remove('test_scan_ppi.txt') + +def test_trigger_lidar_rhi_from_mask(): + import torch + import adam + torch.manual_seed(42) + rad_scan = adam.io.preprocess_radar_image('KLOT', '2025-07-15T18:00:00') + rad_scan = adam.model.infer_lake_breeze( + rad_scan, model_name='lakebreeze_best_model_fcn_resnet50') + result = adam.triggering.trigger_lidar_rhi_from_mask(rad_scan, 41.70101404798476, -87.99577278662817, + None, None, None, elevations=[0, 45], + out_file_name='test_scan_rhi_lakebreeze.txt', dyn_csm=False) + assert result is False, "Expected the scan to not be triggered due to distance from lidar to lake breeze region being greater than max_distance." + rad_scan = adam.io.preprocess_radar_image('KLOT', '2025-04-24T20:03:23') + rad_scan = adam.model.infer_lake_breeze( + rad_scan, model_name='lakebreeze_model_fcn_resnet50_no_augmentation') + with adam.testing.FakeSSHClient() as client: + result = adam.triggering.trigger_lidar_rhi_from_mask(rad_scan, 41.70101404798476, -87.99577278662817, + None, None, None, elevations=[0, 45], + out_file_name='test_scan_rhi_lakebreeze.txt', dyn_csm=False, client=client) + assert result is True, "Expected the scan to be triggered since the distance from lidar to lake breeze region" \ + " is less than max_distance." + client.sftp.get(client.sftp.files[0], 'test_scan_rhi_lakebreeze_copy.txt') + with open('test_scan_rhi_lakebreeze_copy.txt', 'r') as f: + lines = f.readlines() + with open(adam.testing.TEST_RHI_TRIGGERED_SCAN, 'r') as f: + expected_lines = f.readlines() + assert len(lines) == len(expected_lines) + for i, (line, expected_line) in enumerate(zip(lines, expected_lines)): + assert line == expected_line, f"Line {i} does not match expected output.\nGot: {line}\nExpected: {expected_line}" + os.remove('test_scan_rhi_lakebreeze_copy.txt') + os.remove('test_scan_rhi_lakebreeze.txt') + os.remove('test_scan_rhi.txt') \ No newline at end of file diff --git a/tests/test_util.py b/tests/test_util.py index ded4cba..e8ca5fc 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -5,10 +5,11 @@ def test_instrument_steering(): torch.manual_seed(42) - rad_scan = adam.io.preprocess_radar_image('KLOT', '2025-07-15T18:00:00') + rad_scan = adam.io.preprocess_radar_image('KLOT', '2025-04-24T20:03:23') rad_scan = adam.model.infer_lake_breeze( - rad_scan, model_name='lakebreeze_best_model_fcn_resnet50') - angle, lat, lon = adam.util.azimuth_point(-87.99577278662817, 41.70101404798476, rad_scan) - np.testing.assert_almost_equal(angle, 40.34, decimal=0) - np.testing.assert_almost_equal(lat, 41.9694, decimal=2) - np.testing.assert_almost_equal(lon, -87.7528, decimal=2) \ No newline at end of file + rad_scan, model_name='lakebreeze_model_fcn_resnet50_no_augmentation') + angle, lat, lon, dist = adam.util.azimuth_point(-87.99577278662817, 41.70101404798476, rad_scan) + np.testing.assert_almost_equal(angle, 224.61, decimal=0) + np.testing.assert_almost_equal(lat, 41.68, decimal=2) + np.testing.assert_almost_equal(lon, -88.01, decimal=2) + np.testing.assert_almost_equal(dist, 0, decimal=2) \ No newline at end of file From 0f816d375e74377259d7c11a313dd60ccba62567 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Mon, 9 Feb 2026 16:11:42 -0600 Subject: [PATCH 3/5] ADD: Document references and test dataset. --- docs/source/dev_reference/index.rst | 22 +++++++++++++++++++ src/adam/testing/data/test_scan_ppi.txt | 15 +++++++++++++ .../data/test_scan_ppi_lakebreeze_close.txt | 15 +++++++++++++ src/adam/testing/data/test_scan_rhi.txt | 7 ++++++ .../testing/data/test_scan_rhi_lakebreeze.txt | 7 ++++++ 5 files changed, 66 insertions(+) create mode 100644 src/adam/testing/data/test_scan_ppi.txt create mode 100644 src/adam/testing/data/test_scan_ppi_lakebreeze_close.txt create mode 100644 src/adam/testing/data/test_scan_rhi.txt create mode 100644 src/adam/testing/data/test_scan_rhi_lakebreeze.txt diff --git a/docs/source/dev_reference/index.rst b/docs/source/dev_reference/index.rst index 4cc188f..8f60b93 100644 --- a/docs/source/dev_reference/index.rst +++ b/docs/source/dev_reference/index.rst @@ -43,6 +43,28 @@ Main module for visualization. :undoc-members: :show-inheritance: +======================== +:mod:`triggering` Module +======================== + +Main module for triggering lidar scans. + +.. automodule:: adam.triggering + :members: + :undoc-members: + :show-inheritance: + +======================== +:mod:`testing` Module +======================== + +Main module for testing. + +.. automodule:: adam.triggering + :members: + :undoc-members: + :show-inheritance: + ================== :mod:`util` Module ================== diff --git a/src/adam/testing/data/test_scan_ppi.txt b/src/adam/testing/data/test_scan_ppi.txt new file mode 100644 index 0000000..e5c81ec --- /dev/null +++ b/src/adam/testing/data/test_scan_ppi.txt @@ -0,0 +1,15 @@ +7 +6 +20 +A.1=30,S.1=1388,P.1=-125000*A.2=30,S.2=1388,P.2=0 +W0 +A.1=30,S.1=1388,P.1=-250000*A.2=30,S.2=1388,P.2=0 +W0 +A.1=30,S.1=1388,P.1=-125000*A.2=30,S.2=1388,P.2=-3472 +W0 +A.1=30,S.1=1388,P.1=-250000*A.2=30,S.2=1388,P.2=-3472 +W0 +A.1=30,S.1=1388,P.1=-125000*A.2=30,S.2=1388,P.2=-6944 +W0 +A.1=30,S.1=1388,P.1=-250000*A.2=30,S.2=1388,P.2=-6944 +W0 diff --git a/src/adam/testing/data/test_scan_ppi_lakebreeze_close.txt b/src/adam/testing/data/test_scan_ppi_lakebreeze_close.txt new file mode 100644 index 0000000..8e5e30e --- /dev/null +++ b/src/adam/testing/data/test_scan_ppi_lakebreeze_close.txt @@ -0,0 +1,15 @@ +7 +6 +20 +A.1=30,S.1=1388,P.1=-279857*A.2=30,S.2=69,P.2=0 +W0 +A.1=30,S.1=1388,P.1=-321524*A.2=30,S.2=69,P.2=0 +W0 +A.1=30,S.1=1388,P.1=-279857*A.2=30,S.2=69,P.2=-3472 +W0 +A.1=30,S.1=1388,P.1=-321524*A.2=30,S.2=69,P.2=-3472 +W0 +A.1=30,S.1=1388,P.1=-279857*A.2=30,S.2=69,P.2=-6944 +W0 +A.1=30,S.1=1388,P.1=-321524*A.2=30,S.2=69,P.2=-6944 +W0 diff --git a/src/adam/testing/data/test_scan_rhi.txt b/src/adam/testing/data/test_scan_rhi.txt new file mode 100644 index 0000000..ef48413 --- /dev/null +++ b/src/adam/testing/data/test_scan_rhi.txt @@ -0,0 +1,7 @@ +7 +2 +20 +A.1=30,S.1=1388,P.1=-125000*A.2=30,S.2=1388,P.2=0 +W0 +A.1=30,S.1=1388,P.1=-125000*A.2=30,S.2=1388,P.2=-62500 +W0 diff --git a/src/adam/testing/data/test_scan_rhi_lakebreeze.txt b/src/adam/testing/data/test_scan_rhi_lakebreeze.txt new file mode 100644 index 0000000..71036e1 --- /dev/null +++ b/src/adam/testing/data/test_scan_rhi_lakebreeze.txt @@ -0,0 +1,7 @@ +7 +2 +20 +A.1=30,S.1=1388,P.1=-300691*A.2=30,S.2=69,P.2=0 +W0 +A.1=30,S.1=1388,P.1=-300691*A.2=30,S.2=69,P.2=-31250 +W0 From 9ea6315a19c0c88672644a0b1dcbec53b3cb8500 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Mon, 9 Feb 2026 16:20:57 -0600 Subject: [PATCH 4/5] ADD: Docstrings for testing and triggering modules. --- src/adam/testing/__init__.py | 21 +++++++++++++++++++++ src/adam/triggering/__init__.py | 15 +++++++++++++++ tests/test_adaptive_scanning.py | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/adam/testing/__init__.py b/src/adam/testing/__init__.py index bc0015a..845a545 100644 --- a/src/adam/testing/__init__.py +++ b/src/adam/testing/__init__.py @@ -1,3 +1,24 @@ +""" +============================ +adam.testing (adam.testing) +============================ + +.. currentmodule:: adam.testing + +This module handles testing utilities for ADAM. + + + +.. autosummary:: + :toctree: generated/ + + FakeSSHClient + FakeSFTP + TEST_RHI_FILE + TEST_PPI_FILE + TEST_PPI_TRIGGERED_SCAN + TEST_RHI_TRIGGERED_SCAN +""" import os from .fake_lidar import FakeSFTP, FakeSSHClient # noqa diff --git a/src/adam/triggering/__init__.py b/src/adam/triggering/__init__.py index 4ae7898..f278395 100644 --- a/src/adam/triggering/__init__.py +++ b/src/adam/triggering/__init__.py @@ -1 +1,16 @@ +""" +============================== +ADAM Triggering Module +============================== +.. currentmodule:: adam.triggering + +This module handles the generation of scan strategies and triggering of the lidar based on radar data. +.. autosummary:: + + :toctree: generated/ + make_scan_file + send_scan + trigger_lidar_ppis_from_mask + trigger_lidar_rhi_from_mask +""" from .halo_lidar import make_scan_file, send_scan, trigger_lidar_ppis_from_mask, trigger_lidar_rhi_from_mask # noqa \ No newline at end of file diff --git a/tests/test_adaptive_scanning.py b/tests/test_adaptive_scanning.py index 4e6a136..a460146 100644 --- a/tests/test_adaptive_scanning.py +++ b/tests/test_adaptive_scanning.py @@ -33,7 +33,7 @@ def test_make_scan_file(): assert line == expected_line, f"Line {i} does not match expected output.\nGot: {line}\nExpected: {expected_line}" def test_send_scan(): - from adam.triggering.halo_lidar import send_scan + from adam.triggering.halo_lidar import send_scanpy from adam.testing import TEST_RHI_FILE from adam.testing.fake_lidar import FakeSSHClient lidar_ip_addr = None From c5317587df3f0a4082b21403302b5d9d624ed9a8 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Mon, 9 Feb 2026 16:24:24 -0600 Subject: [PATCH 5/5] FIX: Docstring for triggering. --- src/adam/triggering/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/adam/triggering/__init__.py b/src/adam/triggering/__init__.py index f278395..0685d3b 100644 --- a/src/adam/triggering/__init__.py +++ b/src/adam/triggering/__init__.py @@ -5,9 +5,11 @@ .. currentmodule:: adam.triggering This module handles the generation of scan strategies and triggering of the lidar based on radar data. + .. autosummary:: :toctree: generated/ + make_scan_file send_scan trigger_lidar_ppis_from_mask