diff --git a/catkit2/services/bmc_dm/bmc_dm.py b/catkit2/services/bmc_dm/bmc_dm.py
deleted file mode 100644
index 5dd9a0f0a..000000000
--- a/catkit2/services/bmc_dm/bmc_dm.py
+++ /dev/null
@@ -1,153 +0,0 @@
-from catkit2.testbed.service import Service
-from catkit2.testbed.tracing import trace_interval
-
-import time
-import sys
-import os
-import threading
-import numpy as np
-from astropy.io import fits
-
-try:
- sdk_path = os.environ.get('CATKIT_BOSTON_SDK_PATH')
- if sdk_path is not None:
- sys.path.append(sdk_path)
-
- import bmc
-except ImportError:
- print('To use Boston DMs, you need to set the CATKIT_BOSTON_SDK_PATH environment variable.')
- raise
-
-class BmcDm(Service):
- def __init__(self):
- super().__init__('bmc_dm')
-
- self.serial_number = self.config['serial_number']
- self.command_length = self.config['command_length']
- self.flat_map_fname = self.config['flat_map_fname']
- self.gain_map_fname = self.config['gain_map_fname']
- self.max_volts = self.config['max_volts']
-
- self.startup_maps = self.config.get('startup_maps', {})
-
- self.lock = threading.Lock()
-
- self.channels = {}
- self.channel_threads = {}
- for channel in self.config['channels']:
- self.add_channel(channel)
-
- channel_names = list(channel.lower() for channel in self.config['channels'])
- self.make_property('channels', lambda: channel_names)
-
- self.total_voltage = self.make_data_stream('total_voltage', 'float64', [self.command_length], 20)
- self.total_surface = self.make_data_stream('total_surface', 'float64', [self.command_length], 20)
-
- def add_channel(self, channel_name):
- self.channels[channel_name] = self.make_data_stream(channel_name.lower(), 'float64', [self.command_length], 20)
-
- # Get the right default flat map.
- if channel_name in self.startup_maps:
- flatmap = fits.getdata(self.startup_maps[channel_name]).astype('float64')
- else:
- flatmap = np.zeros(self.command_length)
-
- self.channels[channel_name].submit_data(flatmap)
-
- def main(self):
- self.channel_threads = {}
-
- # Start channel monitoring threads
- for channel_name in self.channels.keys():
- thread = threading.Thread(target=self.monitor_channel, args=(channel_name,))
- thread.start()
-
- self.channel_threads[channel_name] = thread
-
- while not self.should_shut_down:
- time.sleep(0.01)
-
- for thread in self.channel_threads.values():
- thread.join()
- self.channel_threads = {}
-
- def monitor_channel(self, channel_name):
- while not self.should_shut_down:
- try:
- self.channels[channel_name].get_next_frame(10)
- except Exception:
- # Timed out. This is used to periodically check the shutdown flag.
- continue
-
- self.update_dm()
-
- def update_dm(self):
- with trace_interval('update dm surface'):
- with trace_interval('compute total surface'):
- # Add up all channels to get the total surface.
- total_surface = 0
- for stream in self.channels.values():
- total_surface += stream.get_latest_frame().data
-
- # Apply the command on the DM.
- self.send_surface(total_surface)
-
- def send_surface(self, total_surface):
- with trace_interval('send surface'):
- # Submit this surface to the total surface data stream.
- self.total_surface.submit_data(total_surface)
-
- with trace_interval('compute voltages'):
- # Compute the voltages from the request total surface.
- voltages = self.flat_map + total_surface * self.gain_map_inv
- voltages /= self.max_volts
- voltages = np.clip(voltages, 0, 1)
-
- dac_bit_depth = self.config['dac_bit_depth']
-
- discretized_voltages = voltages
- if dac_bit_depth is not None:
- discretized_voltages = (np.floor(voltages * (2**dac_bit_depth))) / (2**dac_bit_depth)
-
- with trace_interval('send data'):
- with self.lock:
- status = self.device.send_data(voltages)
-
- if status != bmc.NO_ERR:
- raise RuntimeError(f'Failed to send data: {self.device.error_string(status)}.')
-
- # Submit these voltages to the total voltage data stream.
- self.total_voltage.submit_data(discretized_voltages)
-
- def open(self):
- self.flat_map = fits.getdata(self.flat_map_fname)
- self.gain_map = fits.getdata(self.gain_map_fname)
-
- with np.errstate(divide='ignore', invalid='ignore'):
- self.gain_map_inv = 1 / self.gain_map
- self.gain_map_inv[np.abs(self.gain_map) < 1e-10] = 0
-
- self.device = bmc.BmcDm()
- status = self.device.open_dm(self.serial_number)
-
- if status != bmc.NO_ERR:
- raise RuntimeError(f'Failed to connect: {self.dm.error_string(status)}.')
-
- command_length = self.device.num_actuators()
- if self.command_length != command_length:
- raise ValueError(f'Command length in config: {self.command_length}. Command length on hardware: {command_length}.')
-
- zeros = np.zeros(self.command_length, dtype='float64')
- self.send_surface(zeros)
-
- def close(self):
- try:
- zeros = np.zeros(self.command_length, dtype='float64')
- self.send_surface(zeros)
- finally:
- self.device.close_dm()
- self.device = None
-
-if __name__ == '__main__':
- service = BmcDm()
- service.run()
diff --git a/catkit2/services/bmc_dm_sim/bmc_dm_sim.py b/catkit2/services/bmc_dm_sim/bmc_dm_sim.py
deleted file mode 100644
index 5a9550339..000000000
--- a/catkit2/services/bmc_dm_sim/bmc_dm_sim.py
+++ /dev/null
@@ -1,120 +0,0 @@
-from catkit2.testbed.service import Service
-
-import threading
-import numpy as np
-from astropy.io import fits
-
-class BmcDmSim(Service):
- def __init__(self):
- super().__init__('bmc_dm_sim')
-
- self.serial_number = self.config['serial_number']
- self.command_length = self.config['command_length']
- self.flat_map_fname = self.config['flat_map_fname']
- self.gain_map_fname = self.config['gain_map_fname']
- self.max_volts = self.config['max_volts']
-
- self.startup_maps = self.config.get('startup_maps', {})
-
- self.flat_map = np.zeros(self.command_length)
- self.gain_map = np.ones(self.command_length)
-
- self.lock = threading.Lock()
-
- self.channels = {}
- self.channel_threads = {}
- for channel in self.config['channels']:
- self.add_channel(channel)
-
- channel_names = [channel.lower() for channel in self.config['channels']]
- self.make_property('channels', lambda: channel_names)
-
- self.total_voltage = self.make_data_stream('total_voltage', 'float64', [self.command_length], 20)
- self.total_surface = self.make_data_stream('total_surface', 'float64', [self.command_length], 20)
-
- def add_channel(self, channel_name):
- self.channels[channel_name] = self.make_data_stream(channel_name, 'float64', [self.command_length], 20)
-
- # Get the right default flat map.
- if channel_name in self.startup_maps:
- flatmap = fits.getdata(self.startup_maps[channel_name]).astype('float64')
- else:
- flatmap = np.zeros(self.command_length)
-
- self.channels[channel_name].submit_data(flatmap)
-
- def main(self):
- self.channel_threads = {}
-
- # Start channel monitoring threads
- for channel_name in self.channels.keys():
- thread = threading.Thread(target=self.monitor_channel, args=(channel_name,))
- thread.start()
-
- self.channel_threads[channel_name] = thread
-
- while not self.should_shut_down:
- self.sleep(0.1)
-
- for thread in self.channel_threads.values():
- thread.join()
- self.channel_threads = {}
-
- def monitor_channel(self, channel_name):
- while not self.should_shut_down:
- try:
- self.channels[channel_name].get_next_frame(10)
- except Exception:
- # Timed out. This is used to periodically check the shutdown flag.
- continue
-
- self.update_dm()
-
- def update_dm(self):
- # Add up all channels to get the total surface.
- total_surface = 0
- for stream in self.channels.values():
- total_surface += stream.get_latest_frame().data
-
- # Apply the command on the DM.
- self.send_surface(total_surface)
-
- def send_surface(self, total_surface):
- # Submit this surface to the total surface data stream.
- self.total_surface.submit_data(total_surface)
-
- # Compute the voltages from the request total surface.
- voltages = self.flat_map + total_surface * self.gain_map_inv
- voltages /= self.max_volts
- voltages = np.clip(voltages, 0, 1)
-
- dac_bit_depth = self.config['dac_bit_depth']
-
- discretized_voltages = voltages
- discretized_surface = total_surface
-
- if dac_bit_depth is not None:
- discretized_voltages = (np.floor(voltages * (2**dac_bit_depth))) / (2**dac_bit_depth)
- discretized_surface = (discretized_voltages * self.max_volts - self.flat_map) * self.gain_map
-
- with self.lock:
- self.testbed.simulator.actuate_dm(dm_name=self.id, new_actuators=discretized_surface)
-
- # Submit these voltages to the total voltage data stream.
- self.total_voltage.submit_data(discretized_voltages)
-
- def open(self):
- self.flat_map = fits.getdata(self.flat_map_fname)
- self.gain_map = fits.getdata(self.gain_map_fname)
-
- with np.errstate(divide='ignore', invalid='ignore'):
- self.gain_map_inv = 1 / self.gain_map
- self.gain_map_inv[np.abs(self.gain_map) < 1e-10] = 0
-
- zeros = np.zeros(self.command_length, dtype='float64')
- self.send_surface(zeros)
-
-
-if __name__ == '__main__':
- service = BmcDmSim()
- service.run()
diff --git a/catkit2/testbed/proxies/__init__.py b/catkit2/testbed/proxies/__init__.py
index 068fea619..6219eec77 100644
--- a/catkit2/testbed/proxies/__init__.py
+++ b/catkit2/testbed/proxies/__init__.py
@@ -2,7 +2,6 @@
'CameraProxy',
'NewportXpsQ8Proxy',
'FlipMountProxy',
- 'BmcDmProxy',
'DeformableMirrorProxy',
'NewportPicomotorProxy',
'NiDaqProxy',
@@ -13,7 +12,6 @@
'OceanopticsSpectroProxy'
]
-from .bmc_dm import *
from .camera import *
from .deformable_mirror import *
from .newport_xps import *
diff --git a/catkit2/testbed/proxies/bmc_dm.py b/catkit2/testbed/proxies/bmc_dm.py
deleted file mode 100644
index 5a732fd74..000000000
--- a/catkit2/testbed/proxies/bmc_dm.py
+++ /dev/null
@@ -1,66 +0,0 @@
-from ..service_proxy import ServiceProxy
-
-import numpy as np
-from astropy.io import fits
-import hcipy
-
-
-class BmcDmProxy(ServiceProxy):
- @property
- def dm_mask(self):
- if not hasattr(self, '_dm_mask'):
- fname = self.config['dm_mask_fname']
-
- self._dm_mask = fits.getdata(fname).astype('bool')
-
- return self._dm_mask
-
- @property
- def num_actuators(self):
- return self.config['num_actuators']
-
- @property
- def actuator_grid(self):
- dims = self.dm_mask.shape[::-1]
-
- return hcipy.make_uniform_grid(dims, dims)
-
- def dm_shapes_to_command(self, dm1_shape, dm2_shape=None):
- command = np.zeros(2048)
-
- if dm2_shape is None:
- command[:952] = dm1_shape[:952]
- command[1024:1024 + 952] = dm1_shape[952:]
- else:
- command[:952] = dm1_shape[self.dm_mask]
- command[1024:1024 + 952] = dm2_shape[self.dm_mask]
-
- return command
-
- def flatten_channels(self, channel_names):
- summed_command = 0
-
- if isinstance(channel_names, str):
- channel_names = [channel_names]
-
- # Get commands from channels, zero each channel, and sum commands
- for channel_name in channel_names:
- summed_command += getattr(self, channel_name).get_latest_frame().data
- self.apply_shape(channel_name, np.zeros(2 * self.num_actuators))
-
- # Return summed command (note that this is not a DM shape)
- return summed_command
-
- def command_to_dm_shapes(self, command):
- dm1_shape = np.zeros((34, 34))
- dm2_shape = np.zeros((34, 34))
-
- dm1_shape[self.dm_mask] = command[:952]
- dm2_shape[self.dm_mask] = command[1024:1024 + 952]
-
- return dm1_shape, dm2_shape
-
- def apply_shape(self, channel, dm1_shape, dm2_shape=None):
- command = self.dm_shapes_to_command(dm1_shape, dm2_shape)
-
- getattr(self, channel).submit_data(command)
diff --git a/docs/index.rst b/docs/index.rst
index 30fdedbec..5a249cf32 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -22,7 +22,6 @@ Catkit2
services/accufiz_interferometer
services/aimtti_plp
services/allied_vision_camera
- services/bmc_dm
services/bmc_deformable_mirror
services/camera_sim
services/deformable_mirror
diff --git a/docs/services/bmc_dm.rst b/docs/services/bmc_dm.rst
deleted file mode 100644
index 4a9874d19..000000000
--- a/docs/services/bmc_dm.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-Boston Deformable Mirror
-========================
-This service operates a pair of identical Boston Micromachines MEMS DMs controlled by the same driver. The following Boston DMs have been tested with catkit2 thus far:
-
-- `BMC DM Kilo-C-1.5 `_
-
-This service is been superseded by the :ref:`here `.
-
-Configuration
--------------
-.. code-block:: YAML
-
- boston_dm:
- service_type: bmc_dm
- simulated_service_type: bmc_dm_sim
- interface: bmc_dm
- requires_safety: true
-
- serial_number: 0000
- command_length: 2048
- num_actuators: 952
- dac_bit_depth: 14
- max_volts: 200
-
- flat_map_fname: !path ../flat_data.fits
- gain_map_fname: !path ../gain_map.fits
- dm_mask_fname: !path ../dm_mask.fits
-
- startup_maps:
- flat: !path ../flat_data.fits
-
- channels:
- - correction_howfs
- - correction_lowfs
- - probe
- - poke
- - aberration
- - atmosphere
- - astrogrid
- - resume
- - flat
-
-Properties
-----------
-``channels``: List of command channel names (dict).
-
-Commands
---------
-
-None.
-
-Datastreams
------------
-``total_voltage``: Array of the total voltage applied to each actuator of the DM.
-
-``total_surface``: Array of the total amplitude of each DM actuator (nanometers).
-
-``channels[channel_name]``: The command (nm surface) per virtual channel, identified by channel name.
diff --git a/setup.py b/setup.py
index 450a651f0..e7a9029c4 100644
--- a/setup.py
+++ b/setup.py
@@ -150,8 +150,6 @@ def build_extension(self, ext):
'allied_vision_camera = catkit2.services.allied_vision_camera.allied_vision_camera',
'bmc_deformable_mirror_hardware = catkit2.services.bmc_deformable_mirror_hardware.bmc_deformable_mirror_hardware',
'bmc_deformable_mirror_sim = catkit2.services.bmc_deformable_mirror_sim.bmc_deformable_mirror_sim',
- 'bmc_dm = catkit2.services.bmc_dm.bmc_dm',
- 'bmc_dm_sim = catkit2.services.bmc_dm_sim.bmc_dm_sim',
'camera_sim = catkit2.services.camera_sim.camera_sim',
'dummy_camera = catkit2.services.dummy_camera.dummy_camera',
'empty_service = catkit2.services.empty_service.empty_service',
@@ -192,7 +190,6 @@ def build_extension(self, ext):
'zwo_camera = catkit2.services.zwo_camera.zwo_camera',
],
'catkit2.proxies': [
- 'bmc_dm = catkit2.testbed.proxies.bmc_dm:BmcDmProxy',
'camera = catkit2.testbed.proxies.camera:CameraProxy',
'deformable_mirror = catkit2.testbed.proxies.deformable_mirror:DeformableMirrorProxy',
'flip_mount = catkit2.testbed.proxies.flip_mount:FlipMountProxy',
diff --git a/tests/config/services.yml b/tests/config/services.yml
index 5de63c155..9384515c4 100644
--- a/tests/config/services.yml
+++ b/tests/config/services.yml
@@ -1,10 +1,10 @@
dummy_dm_service:
service_type: dummy_dm_service
requires_safety: false
- interface: bmc_dm
+ interface: deformable_mirror
- num_actuators: 952
- dm_shape: 2048
+ device_actuator_mask_fname: !path ../data/dm_mask.fits
+ num_actuators_all_dms: 1904
dummy_service:
service_type: dummy_service
diff --git a/tests/data/dm_mask.fits b/tests/data/dm_mask.fits
new file mode 100644
index 000000000..fe3cd3d8b
Binary files /dev/null and b/tests/data/dm_mask.fits differ
diff --git a/tests/services/dummy_dm_service/dummy_dm_service.py b/tests/services/dummy_dm_service/dummy_dm_service.py
index 78b916cf2..40ddec65f 100644
--- a/tests/services/dummy_dm_service/dummy_dm_service.py
+++ b/tests/services/dummy_dm_service/dummy_dm_service.py
@@ -2,24 +2,25 @@
import numpy as np
+
class DummyDmService(Service):
def __init__(self):
super().__init__('dummy_dm_service')
self.channel_names = ['correction_howfs', 'correction_lowfs', 'aberration', 'atmosphere']
-
- self.dm_shape = self.config['dm_shape']
+ self.num_actuators_all_dms = self.config['num_actuators_all_dms']
def open(self):
# Make channels streamable
for channel in self.channel_names:
- setattr(self, channel, self.make_data_stream(channel, 'float64', [self.dm_shape], 20))
- getattr(self, channel).submit_data(np.zeros(self.dm_shape,))
+ setattr(self, channel, self.make_data_stream(channel, 'float64', [self.num_actuators_all_dms], 20))
+ getattr(self, channel).submit_data(np.zeros(self.num_actuators_all_dms,))
def main(self):
while not self.should_shut_down:
self.sleep(0.1)
+
if __name__ == '__main__':
service = DummyDmService()
service.run()
diff --git a/tests/test_dm_commands.py b/tests/test_dm_commands.py
index 2bfca6e56..4de4bd0cb 100644
--- a/tests/test_dm_commands.py
+++ b/tests/test_dm_commands.py
@@ -5,8 +5,8 @@ def test_flatten_single_channel(testbed):
# Test with single channel
dm_proxy = testbed.dummy_dm_service
- expected_zero_command = np.zeros(dm_proxy.config['dm_shape'])
- initial_command = np.ones(dm_proxy.config['dm_shape'])
+ expected_zero_command = np.zeros(dm_proxy.num_dms * dm_proxy.num_actuators)
+ initial_command = np.ones(dm_proxy.num_dms * dm_proxy.num_actuators)
dm_proxy.correction_howfs.submit_data(initial_command)
previous_dm_command = dm_proxy.flatten_channels('correction_howfs')
@@ -19,14 +19,14 @@ def test_flatten_multiple_channels(testbed):
# Flatten multiple channels
dm_proxy = testbed.dummy_dm_service
- initial_command = np.ones(dm_proxy.config['dm_shape'])
+ initial_command = np.ones(dm_proxy.num_dms * dm_proxy.num_actuators)
dm_proxy.correction_lowfs.submit_data(initial_command)
dm_proxy.atmosphere.submit_data(initial_command)
dm_proxy.aberration.submit_data(initial_command)
num_channels_summed = 3
- expected_summed_command = num_channels_summed*initial_command
- expected_zero_command = np.zeros(dm_proxy.config['dm_shape'])
+ expected_summed_command = num_channels_summed * initial_command
+ expected_zero_command = np.zeros(dm_proxy.num_dms * dm_proxy.num_actuators)
move_command = dm_proxy.flatten_channels(['correction_lowfs', 'atmosphere', 'aberration'])
assert np.allclose(move_command, expected_summed_command)