src.acoustools.Levitator

  1from ctypes import CDLL, POINTER
  2import ctypes
  3
  4import torch, os
  5
  6from acoustools.Utilities import get_convert_indexes
  7from torch import Tensor
  8
  9
 10class LevitatorController():
 11    '''
 12     Class to enable the manipulation of an OpenMPD style acoustic levitator from python. 
 13    '''
 14
 15    def __init__(self, bin_path:str|None = None, ids:tuple[int] = (1000,999), matBoardToWorld:list[int]|None=None, 
 16                 print_lines:bool=False):
 17        '''
 18        Creates the controller\n
 19        ```python
 20        from acoustools.Levitator import LevitatorController
 21        from acoustools.Utilities import create_points, add_lev_sig, propagate_abs
 22        from acoustools.Solvers import wgs
 23
 24        lev = LevitatorController()
 25
 26        p = create_points(1,1,x=0,y=0,z=0)
 27        x = wgs(p)
 28        print(propagate_abs(x,p))
 29        x = add_lev_sig(x)
 30
 31        lev.levitate(x)
 32        print('Levitating...')
 33        input()
 34        print('Stopping...')
 35        lev.disconnect()
 36        print('Stopped')
 37
 38        ```
 39        THIS CHANGES THE CURRENT WORKING DIRECTORY AND THEN CHANGES IT BACK \n
 40        :param bin_path: The path to the binary files needed. If `None` will use files contained in AcousTools. Default: None.
 41        :param ids: IDs of boards. Default `(1000,999)`. For two board setup will be (Top, Bottom) if `-1` then all messages will be ignored. Use when testing code but no device is conncted 
 42        :param matBoardToWorld: Matric defining the mapping between simulated and real boards. When `None` uses a default setting. Default `None`.
 43        :param print_lines: If False supresses some print messages
 44        '''
 45        
 46        self.mode = 1
 47        '''
 48        @private
 49        '''
 50        if type(ids) == int:
 51            ids = (ids,)
 52
 53        if ids[0] == -1:
 54            self.mode = 0
 55            print('Virtual Levitator mode - no messages will be sent')
 56        else:
 57            if bin_path is None:
 58                self.bin_path = os.path.dirname(__file__)+"/../../bin/x64/"
 59            
 60            cwd = os.getcwd()
 61            os.chdir(self.bin_path)
 62            files = os.listdir()
 63            
 64            for id in ids:
 65                if 'board_'+str(id)+'.pat' not in files:
 66                    data_file = open('board_master.pat','r')
 67                    data = data_file.read()
 68
 69                    file = open('board_'+str(id)+'.pat','w')
 70                    data_id = data.replace('<XXXXXXXX>',str(id))
 71                    file.write(data_id)
 72                    file.close()
 73                    data_file.close()
 74
 75
 76            print(os.getcwd())
 77            self.levitatorLib = CDLL(self.bin_path+'Levitator.dll')
 78
 79            self.board_number = len(ids)
 80            self.ids = (ctypes.c_int * self.board_number)(*ids)
 81
 82            if matBoardToWorld is None:
 83                if self.board_number == 2:
 84                    self.matBoardToWorld =  (ctypes.c_float * (16*self.board_number)) (
 85                        1, 0, 0, 0,
 86                        0, 1, 0, 0,
 87                        0, 0, 1, 0,
 88                        0, 0, 0, 1,
 89
 90                        1, 0, 0, 0,
 91                        0, 1, 0, 0,
 92                        0, 0, 1, 0,
 93                        0, 0, 0, 1
 94
 95                        
 96                    )
 97                elif self.board_number == 1:
 98                     self.matBoardToWorld =  (ctypes.c_float * (16*self.board_number)) (
 99                        1, 0, 0, 0,
100                        0, 1, 0, 0,
101                        0, 0, 1, 0,
102                        0, 0, 0, 1
103                        )
104                else:
105                    raise ValueError('For number of boards > 2, matBoardToWorld shouldnt be None')
106
107            else:
108                self.matBoardToWorld =  (ctypes.c_float * (16*self.board_number))(*matBoardToWorld)
109            
110
111            self.levitatorLib.connect_to_levitator.argtypes =  [POINTER(ctypes.c_int), POINTER(ctypes.c_float), ctypes.c_int, ctypes.c_bool]
112            self.levitatorLib.connect_to_levitator.restype = ctypes.c_void_p
113            self.controller = self.levitatorLib.connect_to_levitator(self.ids,self.matBoardToWorld,self.board_number,print_lines)
114
115            os.chdir(cwd)
116
117            self.IDX = get_convert_indexes(256*self.board_number).cpu().detach()
118    
119    
120    def send_message(self, phases, amplitudes=None, relative_amplitude=1, num_geometries = 1, sleep_ms = 0, loop=False, num_loops = 0):
121        '''
122        RECCOMENDED NOT TO USE - USE `levitate` INSTEAD\\
123        @private
124        sends messages to levitator
125        '''
126        if self.mode:
127            self.levitatorLib.send_message.argtypes = [ctypes.c_void_p,POINTER(ctypes.c_float), POINTER(ctypes.c_float), ctypes.c_float, ctypes.c_int, ctypes.c_int, ctypes.c_int]
128            self.levitatorLib.send_message(self.controller,phases,amplitudes,relative_amplitude,num_geometries, sleep_ms, loop, num_loops)
129
130    def disconnect(self):
131        '''
132        Disconnects the levitator
133        '''
134        if self.mode:
135            self.levitatorLib.disconnect.argtypes = [ctypes.c_void_p]
136            self.levitatorLib.disconnect(self.controller)
137    
138    def turn_off(self):
139        '''
140        Turns of all transducers
141        '''
142        if self.mode:
143            self.levitatorLib.turn_off.argtypes = [ctypes.c_void_p]
144            self.levitatorLib.turn_off(self.controller)
145        
146    def set_frame_rate(self, frame_rate:int):
147        '''
148        Set a new framerate
149        :param frame_rate: The new frame rate to use. Note OpenMPD cannot use framerates below 157Hz
150        '''
151        if self.mode:
152            self.levitatorLib.set_new_frame_rate.argtypes = [ctypes.c_void_p, ctypes.c_int]
153            new_frame_rate = self.levitatorLib.set_new_frame_rate(self.controller, frame_rate)
154
155    def levitate(self, hologram:list[Tensor]|Tensor, relative_amplitude:int=-1, permute:bool=True, sleep_ms:float = 0, loop:bool=False, num_loops:int=0):
156        '''
157        Send a single phase map to the levitator - This is the recomended function to use as will deal with dtype conversions etc
158        :param hologram: `Torch.Tensor` of phases or list of `Torch.Tensor` of phases, expects a batched dimension in dim 0. If phases is complex then ` phases = torch.angle(hologram)` will be run for phase and ` amp = torch.abs(hologram)` for amplitude, else phases left as is
159        :param relative_amplitude: Single value [0,1] or -1 to set amplitude to. If -1 will ignore Default -1
160        :param permute: Convert between acoustools transducer order and OpenMPD. Default True.
161        :param sleep_ms: Time to wait between frames in ms.
162        :param loop: If True will restart from the start of phases, default False
163        :param num_loops: A set number of times to repeat the phases
164        '''
165
166
167        if self.mode:
168            to_output = []
169            to_output_amplitudes = []
170
171            if type(hologram) is Tensor and hologram.shape[0] > 1:
172                holos = []
173                for h in hologram:
174                    holos.append(h.unsqueeze(0).cpu().detach())
175                hologram = holos
176
177            if type(hologram) is list:
178                #chunk this up - blocks of 32....
179                num_geometries = len(hologram)
180                for phases_elem in hologram:
181                    phases_elem = phases_elem.cpu().detach()
182
183                    if permute:
184                        phases_elem = phases_elem[:,self.IDX]
185
186                    if torch.is_complex(phases_elem):
187                        amp_elem = torch.abs(phases_elem)
188                        phases_elem = torch.angle(phases_elem)
189                        
190                    else:
191                        amp_elem = torch.ones_like(phases_elem)
192            
193                    to_output = to_output + phases_elem.squeeze().tolist()
194                    to_output_amplitudes = to_output_amplitudes + amp_elem.squeeze().tolist()
195            else:
196                num_geometries = 1
197                if permute:
198                        hologram = hologram.cpu().detach()
199                        hologram = hologram[:,self.IDX]
200
201                if torch.is_complex(hologram):
202                        amp = torch.abs(hologram)
203                        hologram = torch.angle(hologram)
204                else:
205                        amp = torch.ones_like(hologram)
206                to_output = hologram[0].squeeze().tolist()
207                to_output_amplitudes = amp[0].squeeze().tolist()
208
209
210            phases = (ctypes.c_float * (256*self.board_number *num_geometries))(*to_output)
211           
212
213            if relative_amplitude == -1:
214                amplitudes = (ctypes.c_float * (256*self.board_number*num_geometries))(*to_output_amplitudes)
215            else:
216                amplitudes = None
217                relative_amplitude = ctypes.c_float(relative_amplitude)
218            
219            self.send_message(phases, amplitudes, 0, num_geometries,sleep_ms=sleep_ms,loop=loop,num_loops=num_loops)
class LevitatorController:
 11class LevitatorController():
 12    '''
 13     Class to enable the manipulation of an OpenMPD style acoustic levitator from python. 
 14    '''
 15
 16    def __init__(self, bin_path:str|None = None, ids:tuple[int] = (1000,999), matBoardToWorld:list[int]|None=None, 
 17                 print_lines:bool=False):
 18        '''
 19        Creates the controller\n
 20        ```python
 21        from acoustools.Levitator import LevitatorController
 22        from acoustools.Utilities import create_points, add_lev_sig, propagate_abs
 23        from acoustools.Solvers import wgs
 24
 25        lev = LevitatorController()
 26
 27        p = create_points(1,1,x=0,y=0,z=0)
 28        x = wgs(p)
 29        print(propagate_abs(x,p))
 30        x = add_lev_sig(x)
 31
 32        lev.levitate(x)
 33        print('Levitating...')
 34        input()
 35        print('Stopping...')
 36        lev.disconnect()
 37        print('Stopped')
 38
 39        ```
 40        THIS CHANGES THE CURRENT WORKING DIRECTORY AND THEN CHANGES IT BACK \n
 41        :param bin_path: The path to the binary files needed. If `None` will use files contained in AcousTools. Default: None.
 42        :param ids: IDs of boards. Default `(1000,999)`. For two board setup will be (Top, Bottom) if `-1` then all messages will be ignored. Use when testing code but no device is conncted 
 43        :param matBoardToWorld: Matric defining the mapping between simulated and real boards. When `None` uses a default setting. Default `None`.
 44        :param print_lines: If False supresses some print messages
 45        '''
 46        
 47        self.mode = 1
 48        '''
 49        @private
 50        '''
 51        if type(ids) == int:
 52            ids = (ids,)
 53
 54        if ids[0] == -1:
 55            self.mode = 0
 56            print('Virtual Levitator mode - no messages will be sent')
 57        else:
 58            if bin_path is None:
 59                self.bin_path = os.path.dirname(__file__)+"/../../bin/x64/"
 60            
 61            cwd = os.getcwd()
 62            os.chdir(self.bin_path)
 63            files = os.listdir()
 64            
 65            for id in ids:
 66                if 'board_'+str(id)+'.pat' not in files:
 67                    data_file = open('board_master.pat','r')
 68                    data = data_file.read()
 69
 70                    file = open('board_'+str(id)+'.pat','w')
 71                    data_id = data.replace('<XXXXXXXX>',str(id))
 72                    file.write(data_id)
 73                    file.close()
 74                    data_file.close()
 75
 76
 77            print(os.getcwd())
 78            self.levitatorLib = CDLL(self.bin_path+'Levitator.dll')
 79
 80            self.board_number = len(ids)
 81            self.ids = (ctypes.c_int * self.board_number)(*ids)
 82
 83            if matBoardToWorld is None:
 84                if self.board_number == 2:
 85                    self.matBoardToWorld =  (ctypes.c_float * (16*self.board_number)) (
 86                        1, 0, 0, 0,
 87                        0, 1, 0, 0,
 88                        0, 0, 1, 0,
 89                        0, 0, 0, 1,
 90
 91                        1, 0, 0, 0,
 92                        0, 1, 0, 0,
 93                        0, 0, 1, 0,
 94                        0, 0, 0, 1
 95
 96                        
 97                    )
 98                elif self.board_number == 1:
 99                     self.matBoardToWorld =  (ctypes.c_float * (16*self.board_number)) (
100                        1, 0, 0, 0,
101                        0, 1, 0, 0,
102                        0, 0, 1, 0,
103                        0, 0, 0, 1
104                        )
105                else:
106                    raise ValueError('For number of boards > 2, matBoardToWorld shouldnt be None')
107
108            else:
109                self.matBoardToWorld =  (ctypes.c_float * (16*self.board_number))(*matBoardToWorld)
110            
111
112            self.levitatorLib.connect_to_levitator.argtypes =  [POINTER(ctypes.c_int), POINTER(ctypes.c_float), ctypes.c_int, ctypes.c_bool]
113            self.levitatorLib.connect_to_levitator.restype = ctypes.c_void_p
114            self.controller = self.levitatorLib.connect_to_levitator(self.ids,self.matBoardToWorld,self.board_number,print_lines)
115
116            os.chdir(cwd)
117
118            self.IDX = get_convert_indexes(256*self.board_number).cpu().detach()
119    
120    
121    def send_message(self, phases, amplitudes=None, relative_amplitude=1, num_geometries = 1, sleep_ms = 0, loop=False, num_loops = 0):
122        '''
123        RECCOMENDED NOT TO USE - USE `levitate` INSTEAD\\
124        @private
125        sends messages to levitator
126        '''
127        if self.mode:
128            self.levitatorLib.send_message.argtypes = [ctypes.c_void_p,POINTER(ctypes.c_float), POINTER(ctypes.c_float), ctypes.c_float, ctypes.c_int, ctypes.c_int, ctypes.c_int]
129            self.levitatorLib.send_message(self.controller,phases,amplitudes,relative_amplitude,num_geometries, sleep_ms, loop, num_loops)
130
131    def disconnect(self):
132        '''
133        Disconnects the levitator
134        '''
135        if self.mode:
136            self.levitatorLib.disconnect.argtypes = [ctypes.c_void_p]
137            self.levitatorLib.disconnect(self.controller)
138    
139    def turn_off(self):
140        '''
141        Turns of all transducers
142        '''
143        if self.mode:
144            self.levitatorLib.turn_off.argtypes = [ctypes.c_void_p]
145            self.levitatorLib.turn_off(self.controller)
146        
147    def set_frame_rate(self, frame_rate:int):
148        '''
149        Set a new framerate
150        :param frame_rate: The new frame rate to use. Note OpenMPD cannot use framerates below 157Hz
151        '''
152        if self.mode:
153            self.levitatorLib.set_new_frame_rate.argtypes = [ctypes.c_void_p, ctypes.c_int]
154            new_frame_rate = self.levitatorLib.set_new_frame_rate(self.controller, frame_rate)
155
156    def levitate(self, hologram:list[Tensor]|Tensor, relative_amplitude:int=-1, permute:bool=True, sleep_ms:float = 0, loop:bool=False, num_loops:int=0):
157        '''
158        Send a single phase map to the levitator - This is the recomended function to use as will deal with dtype conversions etc
159        :param hologram: `Torch.Tensor` of phases or list of `Torch.Tensor` of phases, expects a batched dimension in dim 0. If phases is complex then ` phases = torch.angle(hologram)` will be run for phase and ` amp = torch.abs(hologram)` for amplitude, else phases left as is
160        :param relative_amplitude: Single value [0,1] or -1 to set amplitude to. If -1 will ignore Default -1
161        :param permute: Convert between acoustools transducer order and OpenMPD. Default True.
162        :param sleep_ms: Time to wait between frames in ms.
163        :param loop: If True will restart from the start of phases, default False
164        :param num_loops: A set number of times to repeat the phases
165        '''
166
167
168        if self.mode:
169            to_output = []
170            to_output_amplitudes = []
171
172            if type(hologram) is Tensor and hologram.shape[0] > 1:
173                holos = []
174                for h in hologram:
175                    holos.append(h.unsqueeze(0).cpu().detach())
176                hologram = holos
177
178            if type(hologram) is list:
179                #chunk this up - blocks of 32....
180                num_geometries = len(hologram)
181                for phases_elem in hologram:
182                    phases_elem = phases_elem.cpu().detach()
183
184                    if permute:
185                        phases_elem = phases_elem[:,self.IDX]
186
187                    if torch.is_complex(phases_elem):
188                        amp_elem = torch.abs(phases_elem)
189                        phases_elem = torch.angle(phases_elem)
190                        
191                    else:
192                        amp_elem = torch.ones_like(phases_elem)
193            
194                    to_output = to_output + phases_elem.squeeze().tolist()
195                    to_output_amplitudes = to_output_amplitudes + amp_elem.squeeze().tolist()
196            else:
197                num_geometries = 1
198                if permute:
199                        hologram = hologram.cpu().detach()
200                        hologram = hologram[:,self.IDX]
201
202                if torch.is_complex(hologram):
203                        amp = torch.abs(hologram)
204                        hologram = torch.angle(hologram)
205                else:
206                        amp = torch.ones_like(hologram)
207                to_output = hologram[0].squeeze().tolist()
208                to_output_amplitudes = amp[0].squeeze().tolist()
209
210
211            phases = (ctypes.c_float * (256*self.board_number *num_geometries))(*to_output)
212           
213
214            if relative_amplitude == -1:
215                amplitudes = (ctypes.c_float * (256*self.board_number*num_geometries))(*to_output_amplitudes)
216            else:
217                amplitudes = None
218                relative_amplitude = ctypes.c_float(relative_amplitude)
219            
220            self.send_message(phases, amplitudes, 0, num_geometries,sleep_ms=sleep_ms,loop=loop,num_loops=num_loops)

Class to enable the manipulation of an OpenMPD style acoustic levitator from python.

LevitatorController( bin_path: str | None = None, ids: tuple[int] = (1000, 999), matBoardToWorld: list[int] | None = None, print_lines: bool = False)
 16    def __init__(self, bin_path:str|None = None, ids:tuple[int] = (1000,999), matBoardToWorld:list[int]|None=None, 
 17                 print_lines:bool=False):
 18        '''
 19        Creates the controller\n
 20        ```python
 21        from acoustools.Levitator import LevitatorController
 22        from acoustools.Utilities import create_points, add_lev_sig, propagate_abs
 23        from acoustools.Solvers import wgs
 24
 25        lev = LevitatorController()
 26
 27        p = create_points(1,1,x=0,y=0,z=0)
 28        x = wgs(p)
 29        print(propagate_abs(x,p))
 30        x = add_lev_sig(x)
 31
 32        lev.levitate(x)
 33        print('Levitating...')
 34        input()
 35        print('Stopping...')
 36        lev.disconnect()
 37        print('Stopped')
 38
 39        ```
 40        THIS CHANGES THE CURRENT WORKING DIRECTORY AND THEN CHANGES IT BACK \n
 41        :param bin_path: The path to the binary files needed. If `None` will use files contained in AcousTools. Default: None.
 42        :param ids: IDs of boards. Default `(1000,999)`. For two board setup will be (Top, Bottom) if `-1` then all messages will be ignored. Use when testing code but no device is conncted 
 43        :param matBoardToWorld: Matric defining the mapping between simulated and real boards. When `None` uses a default setting. Default `None`.
 44        :param print_lines: If False supresses some print messages
 45        '''
 46        
 47        self.mode = 1
 48        '''
 49        @private
 50        '''
 51        if type(ids) == int:
 52            ids = (ids,)
 53
 54        if ids[0] == -1:
 55            self.mode = 0
 56            print('Virtual Levitator mode - no messages will be sent')
 57        else:
 58            if bin_path is None:
 59                self.bin_path = os.path.dirname(__file__)+"/../../bin/x64/"
 60            
 61            cwd = os.getcwd()
 62            os.chdir(self.bin_path)
 63            files = os.listdir()
 64            
 65            for id in ids:
 66                if 'board_'+str(id)+'.pat' not in files:
 67                    data_file = open('board_master.pat','r')
 68                    data = data_file.read()
 69
 70                    file = open('board_'+str(id)+'.pat','w')
 71                    data_id = data.replace('<XXXXXXXX>',str(id))
 72                    file.write(data_id)
 73                    file.close()
 74                    data_file.close()
 75
 76
 77            print(os.getcwd())
 78            self.levitatorLib = CDLL(self.bin_path+'Levitator.dll')
 79
 80            self.board_number = len(ids)
 81            self.ids = (ctypes.c_int * self.board_number)(*ids)
 82
 83            if matBoardToWorld is None:
 84                if self.board_number == 2:
 85                    self.matBoardToWorld =  (ctypes.c_float * (16*self.board_number)) (
 86                        1, 0, 0, 0,
 87                        0, 1, 0, 0,
 88                        0, 0, 1, 0,
 89                        0, 0, 0, 1,
 90
 91                        1, 0, 0, 0,
 92                        0, 1, 0, 0,
 93                        0, 0, 1, 0,
 94                        0, 0, 0, 1
 95
 96                        
 97                    )
 98                elif self.board_number == 1:
 99                     self.matBoardToWorld =  (ctypes.c_float * (16*self.board_number)) (
100                        1, 0, 0, 0,
101                        0, 1, 0, 0,
102                        0, 0, 1, 0,
103                        0, 0, 0, 1
104                        )
105                else:
106                    raise ValueError('For number of boards > 2, matBoardToWorld shouldnt be None')
107
108            else:
109                self.matBoardToWorld =  (ctypes.c_float * (16*self.board_number))(*matBoardToWorld)
110            
111
112            self.levitatorLib.connect_to_levitator.argtypes =  [POINTER(ctypes.c_int), POINTER(ctypes.c_float), ctypes.c_int, ctypes.c_bool]
113            self.levitatorLib.connect_to_levitator.restype = ctypes.c_void_p
114            self.controller = self.levitatorLib.connect_to_levitator(self.ids,self.matBoardToWorld,self.board_number,print_lines)
115
116            os.chdir(cwd)
117
118            self.IDX = get_convert_indexes(256*self.board_number).cpu().detach()

Creates the controller

from acoustools.Levitator import LevitatorController
from acoustools.Utilities import create_points, add_lev_sig, propagate_abs
from acoustools.Solvers import wgs

lev = LevitatorController()

p = create_points(1,1,x=0,y=0,z=0)
x = wgs(p)
print(propagate_abs(x,p))
x = add_lev_sig(x)

lev.levitate(x)
print('Levitating...')
input()
print('Stopping...')
lev.disconnect()
print('Stopped')

THIS CHANGES THE CURRENT WORKING DIRECTORY AND THEN CHANGES IT BACK

Parameters
  • bin_path: The path to the binary files needed. If None will use files contained in AcousTools. Default: None.
  • ids: IDs of boards. Default (1000,999). For two board setup will be (Top, Bottom) if -1 then all messages will be ignored. Use when testing code but no device is conncted
  • matBoardToWorld: Matric defining the mapping between simulated and real boards. When None uses a default setting. Default None.
  • print_lines: If False supresses some print messages
def disconnect(self):
131    def disconnect(self):
132        '''
133        Disconnects the levitator
134        '''
135        if self.mode:
136            self.levitatorLib.disconnect.argtypes = [ctypes.c_void_p]
137            self.levitatorLib.disconnect(self.controller)

Disconnects the levitator

def turn_off(self):
139    def turn_off(self):
140        '''
141        Turns of all transducers
142        '''
143        if self.mode:
144            self.levitatorLib.turn_off.argtypes = [ctypes.c_void_p]
145            self.levitatorLib.turn_off(self.controller)

Turns of all transducers

def set_frame_rate(self, frame_rate: int):
147    def set_frame_rate(self, frame_rate:int):
148        '''
149        Set a new framerate
150        :param frame_rate: The new frame rate to use. Note OpenMPD cannot use framerates below 157Hz
151        '''
152        if self.mode:
153            self.levitatorLib.set_new_frame_rate.argtypes = [ctypes.c_void_p, ctypes.c_int]
154            new_frame_rate = self.levitatorLib.set_new_frame_rate(self.controller, frame_rate)

Set a new framerate

Parameters
  • frame_rate: The new frame rate to use. Note OpenMPD cannot use framerates below 157Hz
def levitate( self, hologram: list[torch.Tensor] | torch.Tensor, relative_amplitude: int = -1, permute: bool = True, sleep_ms: float = 0, loop: bool = False, num_loops: int = 0):
156    def levitate(self, hologram:list[Tensor]|Tensor, relative_amplitude:int=-1, permute:bool=True, sleep_ms:float = 0, loop:bool=False, num_loops:int=0):
157        '''
158        Send a single phase map to the levitator - This is the recomended function to use as will deal with dtype conversions etc
159        :param hologram: `Torch.Tensor` of phases or list of `Torch.Tensor` of phases, expects a batched dimension in dim 0. If phases is complex then ` phases = torch.angle(hologram)` will be run for phase and ` amp = torch.abs(hologram)` for amplitude, else phases left as is
160        :param relative_amplitude: Single value [0,1] or -1 to set amplitude to. If -1 will ignore Default -1
161        :param permute: Convert between acoustools transducer order and OpenMPD. Default True.
162        :param sleep_ms: Time to wait between frames in ms.
163        :param loop: If True will restart from the start of phases, default False
164        :param num_loops: A set number of times to repeat the phases
165        '''
166
167
168        if self.mode:
169            to_output = []
170            to_output_amplitudes = []
171
172            if type(hologram) is Tensor and hologram.shape[0] > 1:
173                holos = []
174                for h in hologram:
175                    holos.append(h.unsqueeze(0).cpu().detach())
176                hologram = holos
177
178            if type(hologram) is list:
179                #chunk this up - blocks of 32....
180                num_geometries = len(hologram)
181                for phases_elem in hologram:
182                    phases_elem = phases_elem.cpu().detach()
183
184                    if permute:
185                        phases_elem = phases_elem[:,self.IDX]
186
187                    if torch.is_complex(phases_elem):
188                        amp_elem = torch.abs(phases_elem)
189                        phases_elem = torch.angle(phases_elem)
190                        
191                    else:
192                        amp_elem = torch.ones_like(phases_elem)
193            
194                    to_output = to_output + phases_elem.squeeze().tolist()
195                    to_output_amplitudes = to_output_amplitudes + amp_elem.squeeze().tolist()
196            else:
197                num_geometries = 1
198                if permute:
199                        hologram = hologram.cpu().detach()
200                        hologram = hologram[:,self.IDX]
201
202                if torch.is_complex(hologram):
203                        amp = torch.abs(hologram)
204                        hologram = torch.angle(hologram)
205                else:
206                        amp = torch.ones_like(hologram)
207                to_output = hologram[0].squeeze().tolist()
208                to_output_amplitudes = amp[0].squeeze().tolist()
209
210
211            phases = (ctypes.c_float * (256*self.board_number *num_geometries))(*to_output)
212           
213
214            if relative_amplitude == -1:
215                amplitudes = (ctypes.c_float * (256*self.board_number*num_geometries))(*to_output_amplitudes)
216            else:
217                amplitudes = None
218                relative_amplitude = ctypes.c_float(relative_amplitude)
219            
220            self.send_message(phases, amplitudes, 0, num_geometries,sleep_ms=sleep_ms,loop=loop,num_loops=num_loops)

Send a single phase map to the levitator - This is the recomended function to use as will deal with dtype conversions etc

Parameters
  • hologram: Torch.Tensor of phases or list of Torch.Tensor of phases, expects a batched dimension in dim 0. If phases is complex then phases = torch.angle(hologram) will be run for phase and amp = torch.abs(hologram) for amplitude, else phases left as is
  • relative_amplitude: Single value [0,1] or -1 to set amplitude to. If -1 will ignore Default -1
  • permute: Convert between acoustools transducer order and OpenMPD. Default True.
  • sleep_ms: Time to wait between frames in ms.
  • loop: If True will restart from the start of phases, default False
  • num_loops: A set number of times to repeat the phases