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

Creates the controller - reccomended to use in a with block

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

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

with LevitatorController(ids=-1) as lev:

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

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):
136    def disconnect(self):
137        '''
138        Disconnects the levitator
139        '''
140        if self.mode:
141            self.levitatorLib.disconnect.argtypes = [ctypes.c_void_p]
142            self.levitatorLib.disconnect(self.controller)

Disconnects the levitator

def turn_off(self):
144    def turn_off(self):
145        '''
146        Turns of all transducers
147        '''
148        if self.mode:
149            self.levitatorLib.turn_off.argtypes = [ctypes.c_void_p]
150            self.levitatorLib.turn_off(self.controller)

Turns of all transducers

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