Skip to content

Commit a69c034

Browse files
Merge pull request #1012 from TheDeanLab/1008-laser-digital-only-modulation-doesnt-work-1
2 parents f80592e + 7613e84 commit a69c034

File tree

4 files changed

+125
-51
lines changed

4 files changed

+125
-51
lines changed

src/navigate/model/device_startup_functions.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,20 +1238,41 @@ def start_lasers(
12381238
if plugin_devices is None:
12391239
plugin_devices = {}
12401240

1241+
analog, digital, modulation = None, None, None
1242+
12411243
if is_synthetic:
12421244
device_type = "SyntheticLaser"
12431245

12441246
else:
1245-
device_type = configuration["configuration"]["microscopes"][microscope_name][
1247+
analog = configuration["configuration"]["microscopes"][microscope_name][
12461248
"lasers"
1247-
][id]["power"]["hardware"]["type"]
1249+
][id]["power"]["hardware"].get("type", None)
12481250

1249-
if device_type == "NI":
1251+
digital = configuration["configuration"]["microscopes"][microscope_name][
1252+
"lasers"
1253+
][id]["onoff"]["hardware"].get("type", None)
1254+
1255+
device_type = analog
1256+
1257+
if analog == "NI" or digital == "NI":
12501258
if device_connection is not None:
12511259
return device_connection
12521260
from navigate.model.devices.lasers.ni import LaserNI
12531261

1254-
return LaserNI(microscope_name, device_connection, configuration, id)
1262+
if analog == "NI" and digital == "NI":
1263+
modulation = "mixed"
1264+
elif analog == "NI":
1265+
modulation = "analog"
1266+
elif digital == "NI":
1267+
modulation = "digital"
1268+
1269+
return LaserNI(
1270+
microscope_name=microscope_name,
1271+
device_connection=device_connection,
1272+
configuration=configuration,
1273+
laser_id=id,
1274+
modulation_type=modulation,
1275+
)
12551276

12561277
elif device_type.lower() == "syntheticlaser" or device_type.lower() == "synthetic":
12571278
if device_connection is not None:

src/navigate/model/devices/lasers/ni.py

Lines changed: 91 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def __init__(
6161
device_connection: Any,
6262
configuration: Dict[str, Any],
6363
laser_id: int,
64+
modulation_type="digital",
6465
) -> None:
6566
"""Initialize the LaserNI class.
6667
@@ -74,13 +75,74 @@ def __init__(
7475
The device configuration.
7576
laser_id : int
7677
The laser id.
78+
modulation_type : str
79+
The modulation type of the laser - Analog, Digital, or Mixed.
7780
"""
7881
super().__init__(microscope_name, device_connection, configuration, laser_id)
7982

83+
#: str: The modulation type of the laser - Analog, Digital, or Mixed.
84+
self.modulation_type = modulation_type
85+
8086
#: str: Modulation type of the laser - Analog or Digital.
81-
self.on_off_type = None
87+
self.digital_port_type = None
88+
89+
#: float: The minimum digital modulation voltage.
90+
self.laser_min_do = None
91+
92+
#: float: The maximum digital modulation voltage.
93+
self.laser_max_do = None
94+
95+
#: nidaqmx.Task: The laser digital modulation task.
96+
self.laser_do_task = None
97+
98+
#: float: The minimum analog modulation voltage.
99+
self.laser_min_ao = None
100+
101+
#: float: The maximum analog modulation voltage.
102+
self.laser_max_ao = None
103+
104+
#: nidaqmx.Task: The laser analog modulation task.
105+
self.laser_ao_task = None
106+
107+
#: float: Current laser intensity.
108+
self._current_intensity = 0
109+
110+
# Initialize the laser modulation type.
111+
if self.modulation_type == "mixed":
112+
self.initialize_digital_modulation()
113+
self.initialize_analog_modulation()
114+
logger.info(f"{str(self)} initialized with mixed modulation.")
115+
116+
elif self.modulation_type == "analog":
117+
self.initialize_analog_modulation()
118+
logger.info(f"{str(self)} initialized with analog modulation.")
119+
120+
elif self.modulation_type == "digital":
121+
self.initialize_digital_modulation()
122+
logger.info(f"{str(self)} initialized with digital modulation.")
123+
124+
def initialize_analog_modulation(self) -> None:
125+
"""Initialize the analog modulation of the laser."""
126+
try:
127+
laser_ao_port = self.device_config["power"]["hardware"]["channel"]
128+
129+
#: float: The minimum analog modulation voltage.
130+
self.laser_min_ao = self.device_config["power"]["hardware"]["min"]
131+
132+
#: float: The maximum analog modulation voltage.
133+
self.laser_max_ao = self.device_config["power"]["hardware"]["max"]
134+
135+
#: object: The laser analog modulation task.
136+
self.laser_ao_task = nidaqmx.Task()
137+
self.laser_ao_task.ao_channels.add_ao_voltage_chan(
138+
laser_ao_port, min_val=self.laser_min_ao, max_val=self.laser_max_ao
139+
)
140+
except DaqError as e:
141+
logger.debug(f"{str(self)} error:, {e}, {e.error_type}, {e.error_code}")
142+
print(f"{str(self)} error:, {e}, {e.error_type}, {e.error_code}")
82143

83-
# Digital out (if using mixed modulation mode)
144+
def initialize_digital_modulation(self) -> None:
145+
"""Initialize the digital modulation of the laser."""
84146
try:
85147
laser_do_port = self.device_config["onoff"]["hardware"]["channel"]
86148

@@ -94,18 +156,18 @@ def __init__(
94156
self.laser_do_task = nidaqmx.Task()
95157

96158
if "/ao" in laser_do_port:
97-
# Artificial Digital Modulation via an Analog Port
159+
# Perform the digital modulation with an analog output port.
98160
self.laser_do_task.ao_channels.add_ao_voltage_chan(
99161
laser_do_port, min_val=self.laser_min_do, max_val=self.laser_max_do
100162
)
101-
self.on_off_type = "analog"
163+
self.digital_port_type = "analog"
102164

103165
else:
104166
# Digital Modulation via a Digital Port
105167
self.laser_do_task.do_channels.add_do_chan(
106168
laser_do_port, line_grouping=LineGrouping.CHAN_FOR_ALL_LINES
107169
)
108-
self.on_off_type = "digital"
170+
self.digital_port_type = "digital"
109171
except (KeyError, DaqError) as e:
110172
self.laser_do_task = None
111173
if isinstance(e, DaqError):
@@ -115,36 +177,16 @@ def __init__(
115177
print(e.error_code)
116178
print(e.error_type)
117179

118-
#: float: Current laser intensity.
119-
self._current_intensity = 0
120-
121-
# Analog out
122-
try:
123-
laser_ao_port = self.device_config["power"]["hardware"]["channel"]
124-
125-
#: float: The minimum analog modulation voltage.
126-
self.laser_min_ao = self.device_config["power"]["hardware"]["min"]
127-
128-
#: float: The maximum analog modulation voltage.
129-
self.laser_max_ao = self.device_config["power"]["hardware"]["max"]
130-
131-
#: object: The laser analog modulation task.
132-
self.laser_ao_task = nidaqmx.Task()
133-
self.laser_ao_task.ao_channels.add_ao_voltage_chan(
134-
laser_ao_port, min_val=self.laser_min_ao, max_val=self.laser_max_ao
135-
)
136-
except DaqError as e:
137-
logger.debug(f"{str(self)} error:, {e}, {e.error_type}, {e.error_code}")
138-
print(f"{str(self)} error:, {e}, {e.error_type}, {e.error_code}")
139-
140180
def set_power(self, laser_intensity: float) -> None:
141-
"""Sets the laser power.
181+
"""Sets the analog laser power.
142182
143183
Parameters
144184
----------
145185
laser_intensity : float
146186
The laser intensity.
147187
"""
188+
if self.laser_ao_task is None:
189+
return
148190
try:
149191
scaled_laser_voltage = (int(laser_intensity) / 100) * self.laser_max_ao
150192
self.laser_ao_task.write(scaled_laser_voltage, auto_start=True)
@@ -154,34 +196,40 @@ def set_power(self, laser_intensity: float) -> None:
154196

155197
def turn_on(self) -> None:
156198
"""Turns on the laser."""
199+
self.set_power(self._current_intensity)
200+
201+
if self.laser_do_task is None:
202+
return
157203
try:
158-
self.set_power(self._current_intensity)
159-
if self.laser_do_task is not None:
160-
if self.on_off_type == "digital":
161-
self.laser_do_task.write(True, auto_start=True)
162-
elif self.on_off_type == "analog":
163-
self.laser_do_task.write(self.laser_max_do, auto_start=True)
204+
if self.digital_port_type == "digital":
205+
self.laser_do_task.write(True, auto_start=True)
206+
elif self.digital_port_type == "analog":
207+
self.laser_do_task.write(self.laser_max_do, auto_start=True)
164208
except DaqError as e:
165209
logger.exception(e)
166210

167211
def turn_off(self) -> None:
168212
"""Turns off the laser."""
213+
# set ao power to zero
214+
tmp = self._current_intensity
215+
self.set_power(0)
216+
self._current_intensity = tmp
217+
218+
if self.laser_do_task is None:
219+
return
169220
try:
170-
tmp = self._current_intensity
171-
self.set_power(0)
172-
self._current_intensity = tmp
173-
if self.laser_do_task is not None:
174-
if self.on_off_type == "digital":
175-
self.laser_do_task.write(False, auto_start=True)
176-
elif self.on_off_type == "analog":
177-
self.laser_do_task.write(self.laser_min_do, auto_start=True)
221+
if self.digital_port_type == "digital":
222+
self.laser_do_task.write(False, auto_start=True)
223+
elif self.digital_port_type == "analog":
224+
self.laser_do_task.write(self.laser_min_do, auto_start=True)
178225
except DaqError as e:
179226
logger.exception(e)
180227

181228
def close(self) -> None:
182229
"""Close the NI Task before exit."""
183230
try:
184-
self.laser_ao_task.close()
231+
if self.laser_ao_task is not None:
232+
self.laser_ao_task.close()
185233
if self.laser_do_task is not None:
186234
self.laser_do_task.close()
187235
except DaqError as e:

src/navigate/model/microscope.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,10 @@ def prepare_next_channel(self, update_daq_task_flag=True):
746746
self.lasers[str(self.laser_wavelength[self.current_laser_index])].set_power(
747747
channel["laser_power"]
748748
)
749+
logger.info(
750+
f"{self.laser_wavelength[self.current_laser_index]} "
751+
f"nm laser power set to {channel['laser_power']}"
752+
)
749753
# self.lasers[str(self.laser_wavelength[self.current_laser_index])].turn_on()
750754

751755
# stop daq before writing new waveform

test/model/devices/lasers/test_laser_ni.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def setUp(self) -> None:
5050
device_connection=self.device_connection,
5151
configuration=self.configuration,
5252
laser_id=laser_id,
53+
modulation_type="mixed",
5354
)
5455

5556
def tearDown(self):
@@ -67,11 +68,11 @@ def test_set_power(self):
6768
assert self.laser._current_intensity == self.current_intensity
6869

6970
def test_turn_on(self):
70-
self.laser.on_off_type = "digital"
71+
self.laser.digital_port_type = "digital"
7172
self.laser.turn_on()
7273
self.laser.laser_do_task.write.assert_called_with(True, auto_start=True)
7374

74-
self.laser.on_off_type = "analog"
75+
self.laser.digital_port_type = "analog"
7576
self.laser.turn_on()
7677
self.laser.laser_do_task.write.assert_called_with(
7778
self.laser.laser_max_do, auto_start=True
@@ -81,13 +82,13 @@ def test_turn_off(self):
8182
self.current_intensity = random.randint(1, 100)
8283
self.laser._current_intensity = self.current_intensity
8384

84-
self.laser.on_off_type = "digital"
85+
self.laser.digital_port_type = "digital"
8586
self.laser.turn_off()
8687
self.laser.laser_do_task.write.assert_called_with(False, auto_start=True)
8788

8889
assert self.laser._current_intensity == self.current_intensity
8990

90-
self.laser.on_off_type = "analog"
91+
self.laser.digital_port_type = "analog"
9192
self.laser.turn_off()
9293
self.laser.laser_do_task.write.assert_called_with(
9394
self.laser.laser_min_do, auto_start=True

0 commit comments

Comments
 (0)