Skip to content

Commit 4a93fda

Browse files
authored
Merge pull request #275 from open-ephys/issue-112-2
UCLA miniscope V4 support
2 parents 9bfdc9a + 45e01d9 commit 4a93fda

File tree

6 files changed

+720
-3
lines changed

6 files changed

+720
-3
lines changed

OpenEphys.Onix1/Bno055DataFrame.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ internal unsafe Bno055DataFrame(ulong clock, Bno055DataPayload* payload)
5151
/// <summary>
5252
/// Gets the 3D orientation in Euler angle format with units of degrees.
5353
/// </summary>
54-
/// <remark>
54+
/// <remarks>
5555
/// The Tait-Bryan formalism is used:
5656
/// <list type="bullet">
5757
/// <item><description>Yaw: 0 to 360 degrees.</description></item>
5858
/// <item><description>Roll: -180 to 180 degrees</description></item>
5959
/// <item><description>Pitch: -90 to 90 degrees</description></item>
6060
/// </list>
61-
/// </remark>
61+
/// </remarks>
6262
public Vector3 EulerAngle { get; }
6363

6464
/// <summary>

OpenEphys.Onix1/ConfigurePortController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ protected virtual bool CheckLinkState(DeviceContext device)
3131

3232
protected abstract bool ConfigurePortVoltage(DeviceContext device);
3333

34-
private bool ConfigurePortVoltageOverride(DeviceContext device, double voltage)
34+
protected virtual bool ConfigurePortVoltageOverride(DeviceContext device, double voltage)
3535
{
3636
device.WriteRegister(PortController.PORTVOLTAGE, (uint)(voltage * 10));
3737
Thread.Sleep(500);
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.Threading;
5+
using oni;
6+
7+
namespace OpenEphys.Onix1
8+
{
9+
/// <summary>
10+
/// Configures a UCLA Miniscope V4 on the specified port.
11+
/// </summary>
12+
/// <remarks>
13+
/// The UCLA Miniscope V4 is a miniaturized fluorescent microscope for performing single-photon calcium
14+
/// imaging in freely moving animals. It has the following features:
15+
/// <list type="bullet">
16+
/// <item><description>A Python-480 0.48 Megapixel CMOS image sensor.</description></item>
17+
/// <item><description>A BNO055 9-axis IMU for real-time, 3D orientation tracking.</description></item>
18+
/// <item><description>An electrowetting lens for remote focal plane adjustment.</description></item>
19+
/// <item><description>An excitation LED with adjustable brightness control and optional exposure-driven
20+
/// interleaving to reduce photobleaching.</description></item>
21+
/// </list>
22+
/// </remarks>
23+
public class ConfigureUclaMiniscopeV4 : MultiDeviceFactory
24+
{
25+
26+
const double MaxVoltage = 5.6;
27+
28+
PortName port;
29+
readonly ConfigureUclaMiniscopeV4PortController PortControl = new();
30+
31+
/// <summary>
32+
/// Initialize a new instance of a <see cref="ConfigureUclaMiniscopeV4"/> class.
33+
/// </summary>
34+
public ConfigureUclaMiniscopeV4()
35+
{
36+
Port = PortName.PortA;
37+
PortControl.HubConfiguration = HubConfiguration.Passthrough;
38+
}
39+
40+
/// <summary>
41+
/// Gets or sets the Miniscope camera configuration.
42+
/// </summary>
43+
[Category(DevicesCategory)]
44+
[TypeConverter(typeof(SingleDeviceFactoryConverter))]
45+
public ConfigureUclaMiniscopeV4Camera Camera { get; set; } = new();
46+
47+
/// <summary>
48+
/// Gets or sets the Bno055 9-axis inertial measurement unit configuration.
49+
/// </summary>
50+
[Category(DevicesCategory)]
51+
[TypeConverter(typeof(PolledBno055SingleDeviceFactoryConverter))]
52+
[Description("Specifies the configuration for the Bno055 device.")]
53+
public ConfigurePolledBno055 Bno055 { get; set; } =
54+
new ConfigurePolledBno055 { AxisMap = Bno055AxisMap.ZYX, AxisSign = Bno055AxisSign.MirrorX | Bno055AxisSign.MirrorY | Bno055AxisSign.MirrorZ };
55+
56+
57+
/// <summary>
58+
/// Gets or sets the port.
59+
/// </summary>
60+
/// <remarks>
61+
/// The port is the physical connection to the ONIX breakout board and must be specified prior to operation.
62+
/// </remarks>
63+
[Description("Specifies the physical connection of the miniscope to the ONIX breakout board.")]
64+
[Category(ConfigurationCategory)]
65+
public PortName Port
66+
{
67+
get { return port; }
68+
set
69+
{
70+
port = value;
71+
var offset = (uint)port << 8;
72+
PortControl.DeviceAddress = (uint)port;
73+
Camera.DeviceAddress = offset + 0;
74+
Bno055.DeviceAddress = offset + 1;
75+
76+
// Hack: we configure the camera using the port controller below. configuration is super
77+
// unreliable, so we do a bunch of retries in the logic PortController logic. We don't want to
78+
// reperform configuration in the Camera object after this. So we capture a reference to the
79+
// camera here in order to inform it we have already performed configuration.
80+
PortControl.Camera = Camera;
81+
}
82+
}
83+
84+
/// <summary>
85+
/// Gets or sets the port voltage override.
86+
/// </summary>
87+
/// <remarks>
88+
/// <para>
89+
/// If defined, it will override automated voltage discovery and apply the specified voltage to the miniscope.
90+
/// If left blank, an automated headstage detection algorithm will attempt to communicate with the miniscope and
91+
/// apply an appropriate voltage for stable operation. Because ONIX allows any coaxial tether to be used, some of
92+
/// which are thin enough to result in a significant voltage drop, its may be required to manually specify the
93+
/// port voltage.
94+
/// </para>
95+
/// <para>
96+
/// Warning: this device requires 4.0 to 5.0V, measured at the miniscope, for proper operation. Supplying higher
97+
/// voltages may result in damage.
98+
/// </para>
99+
/// </remarks>
100+
[Description("If defined, it will override automated voltage discovery and apply the specified voltage " +
101+
"to the miniscope. Warning: this device requires 4.0 to 5.0V, measured at the scope, for proper operation. " +
102+
"Supplying higher voltages may result in damage to the miniscope.")]
103+
[Category(ConfigurationCategory)]
104+
public double? PortVoltage
105+
{
106+
get => PortControl.PortVoltage;
107+
set => PortControl.PortVoltage = value;
108+
}
109+
110+
internal override IEnumerable<IDeviceConfiguration> GetDevices()
111+
{
112+
yield return PortControl;
113+
yield return Camera;
114+
yield return Bno055;
115+
}
116+
117+
class ConfigureUclaMiniscopeV4PortController : ConfigurePortController
118+
{
119+
internal ConfigureUclaMiniscopeV4Camera Camera;
120+
121+
protected override bool ConfigurePortVoltage(DeviceContext device)
122+
{
123+
const double MinVoltage = 5.2;
124+
const double VoltageIncrement = 0.05;
125+
126+
for (var voltage = MinVoltage; voltage <= MaxVoltage; voltage += VoltageIncrement)
127+
{
128+
SetPortVoltage(device, voltage);
129+
if (CheckLinkStateWithRetry(device))
130+
{
131+
return true;
132+
}
133+
}
134+
135+
return false;
136+
}
137+
138+
void SetPortVoltage(DeviceContext device, double voltage)
139+
{
140+
if (voltage > MaxVoltage)
141+
{
142+
throw new ArgumentException($"The port voltage must be set to a value less than {MaxVoltage} " +
143+
$"volts to prevent damage to the miniscope.");
144+
}
145+
146+
const int WaitUntilVoltageSettles = 400;
147+
device.WriteRegister(PortController.PORTVOLTAGE, 0);
148+
Thread.Sleep(WaitUntilVoltageSettles);
149+
device.WriteRegister(PortController.PORTVOLTAGE, (uint)(10 * voltage));
150+
Thread.Sleep(WaitUntilVoltageSettles);
151+
}
152+
153+
override protected bool CheckLinkState(DeviceContext device)
154+
{
155+
var ds90ub9x = device.Context.GetPassthroughDeviceContext(DeviceAddress << 8, typeof(DS90UB9x));
156+
ConfigureUclaMiniscopeV4Camera.ConfigureDeserializer(ds90ub9x);
157+
158+
const int FailureToWriteRegister = -6;
159+
try
160+
{
161+
ConfigureUclaMiniscopeV4Camera.ConfigureCameraSystem(ds90ub9x, Camera.FrameRate, Camera.InterleaveLed);
162+
}
163+
catch (ONIException ex) when (ex.Number == FailureToWriteRegister)
164+
{
165+
return false;
166+
}
167+
168+
var linkState = device.ReadRegister(PortController.LINKSTATE);
169+
return (linkState & PortController.LINKSTATE_SL) != 0;
170+
}
171+
172+
bool CheckLinkStateWithRetry(DeviceContext device)
173+
{
174+
const int TotalTries = 10;
175+
for (int i = 0; i < TotalTries; i++)
176+
{
177+
if (CheckLinkState(device))
178+
{
179+
Camera.Configured = true;
180+
return true;
181+
}
182+
}
183+
184+
return false;
185+
}
186+
187+
override protected bool ConfigurePortVoltageOverride(DeviceContext device, double voltage)
188+
{
189+
const int TotalTries = 3;
190+
for (int i = 0; i < TotalTries; i++)
191+
{
192+
SetPortVoltage(device, voltage);
193+
if (CheckLinkStateWithRetry(device))
194+
return true;
195+
}
196+
197+
return false;
198+
}
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)