From 2c8945a45feb4d583c60894e283452b91cc4f547 Mon Sep 17 00:00:00 2001 From: jonnew Date: Mon, 24 Jun 2024 16:08:24 -0400 Subject: [PATCH 01/15] Add support for UCLA Miniscope V4 - This device needs some work arounds because its clock is completely out of spec for the SERDES it uses. The back channel must be used to configure a PLL on the camera in order to bump the PCLK rate before it will like. For this reason, I mad ConfigureFmcLinkController. CheckLinkState virtual so I could override its behavior with these steps before checking the link state - Added configuraiton and streaming functionality. - There are few outstanding issues: 1. Something is wrong with the DS90UB9x configuration resulting in 1 missing column from each frame and imaging data that "slides" by one column for each image produced 2. The axis map for the BNO needs to be set correctly 3. I was unable to verify that the EWL was functioning. It might be fine, but I was unable to verify. --- .../ConfigureFmcLinkController.cs | 2 +- .../ConfigureUclaMiniscopeV4.cs | 54 +++++ .../ConfigureUclaMiniscopeV4Bno055.cs | 69 ++++++ .../ConfigureUclaMiniscopeV4Camera.cs | 197 ++++++++++++++++++ .../ConfigureUclaMiniscopeV4LinkController.cs | 59 ++++++ .../UclaMiniscopeV4Bno055Data.cs | 59 ++++++ .../UclaMiniscopeV4DataFrame.cs | 28 +++ .../UclaMiniscopeV4ImageData.cs | 98 +++++++++ 8 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Bno055.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4LinkController.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Bno055Data.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4DataFrame.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4ImageData.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs index 5d3442c1..79b45d66 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs @@ -21,7 +21,7 @@ public ConfigureFmcLinkController() "Consult the device datasheet and documentation for allowable voltage ranges.")] public double? PortVoltage { get; set; } = null; - protected bool CheckLinkState(DeviceContext device) + protected virtual bool CheckLinkState(DeviceContext device) { var linkState = device.ReadRegister(FmcLinkController.LINKSTATE); return (linkState & FmcLinkController.LINKSTATE_SL) != 0; diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4.cs new file mode 100644 index 00000000..3ddff61f --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace OpenEphys.Onix +{ + public class ConfigureUclaMiniscopeV4 : HubDeviceFactory + { + PortName port; + readonly ConfigureUclaMiniscopeV4LinkController LinkController = new(); + + public ConfigureUclaMiniscopeV4() + { + Port = PortName.PortA; + LinkController.HubConfiguration = HubConfiguration.Passthrough; + } + + [Category(ConfigurationCategory)] + [TypeConverter(typeof(HubDeviceConverter))] + public ConfigureUclaMiniscopeV4Camera Imager { get; set; } = new(); + + [Category(ConfigurationCategory)] + [TypeConverter(typeof(HubDeviceConverter))] + public ConfigureUclaMiniscopeV4Bno055 Bno055 { get; set; } = new(); + + public PortName Port + { + get { return port; } + set + { + port = value; + var offset = (uint)port << 8; + LinkController.DeviceAddress = (uint)port; + Imager.DeviceAddress = offset + 0; + Bno055.DeviceAddress = offset + 1; + } + } + + [Description("If defined, it will override automated voltage discovery and apply the specified voltage" + + "to the headstage. Warning: this device requires 5.0V to 6.0V for proper operation." + + "Supplying higher voltages may result in damage to the headstage.")] + public double? PortVoltage + { + get => LinkController.PortVoltage; + set => LinkController.PortVoltage = value; + } + + internal override IEnumerable GetDevices() + { + yield return LinkController; + yield return Imager; + yield return Bno055; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Bno055.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Bno055.cs new file mode 100644 index 00000000..7414cf53 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Bno055.cs @@ -0,0 +1,69 @@ +using System; +using System.ComponentModel; + +namespace OpenEphys.Onix +{ + public class ConfigureUclaMiniscopeV4Bno055 : SingleDeviceFactory + { + public ConfigureUclaMiniscopeV4Bno055() + : base(typeof(UclaMiniscopeV4Bno055)) + { + } + + [Category(ConfigurationCategory)] + [Description("Specifies whether the BNO055 device is enabled.")] + public bool Enable { get; set; } = true; + + public override IObservable Process(IObservable source) + { + var enable = Enable; + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + // configure device via the DS90UB9x deserializer device + var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID); + ConfigureDeserializer(device); + ConfigureBno055(device); + var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); + return DeviceManager.RegisterDevice(deviceName, deviceInfo); + }); + } + + static void ConfigureDeserializer(DeviceContext device) + { + // configure deserializer I2C aliases + var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR); + uint alias = UclaMiniscopeV4Bno055.BNO055Address << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID4, alias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias4, alias); + } + + static void ConfigureBno055(DeviceContext device) + { + // setup BNO055 device + // TODO: Correct orientation + var i2c = new I2CRegisterContext(device, UclaMiniscopeV4Bno055.BNO055Address); + i2c.WriteByte(0x3E, 0x00); // Power mode normal + i2c.WriteByte(0x07, 0x00); // Page ID address 0 + i2c.WriteByte(0x3F, 0x00); // Internal oscillator + i2c.WriteByte(0x41, 0b00000110); // Axis map config (configured to match hs64; X => Z, Y => -Y, Z => X) + i2c.WriteByte(0x42, 0b000000010); // Axis sign (negate Y) + i2c.WriteByte(0x3D, 8); // Operation mode is NOF + } + } + + static class UclaMiniscopeV4Bno055 + { + public const int BNO055Address = 0x28; + public const int DataAddress = 0x1A; + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(UclaMiniscopeV4Bno055)) + { + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs new file mode 100644 index 00000000..cbb3e12a --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs @@ -0,0 +1,197 @@ +using System; +using System.ComponentModel; +using System.Reactive.Disposables; + +namespace OpenEphys.Onix +{ + public class ConfigureUclaMiniscopeV4Camera : SingleDeviceFactory + { + public ConfigureUclaMiniscopeV4Camera() + : base(typeof(UclaMiniscopeV4)) + { + } + + [Category(ConfigurationCategory)] + [Description("Specifies whether the camera is enabled.")] + public bool Enable { get; set; } = true; + + [Category(ConfigurationCategory)] + [Description("Only turn on excitation LED during camera exposures.")] + public bool InterleaveLed { get; set; } = false; + + [Category(ConfigurationCategory)] + [Description("Only turn on excitation LED during camera exposures.")] + public UclaMiniscopeV4FramesPerSecond FrameRate { get; set; } = UclaMiniscopeV4FramesPerSecond.Fps30Hz; + + public override IObservable Process(IObservable source) + { + var enable = Enable; + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + // configure device via the DS90UB9x deserializer device + var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID); + device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); + + // configure deserializer, chip states, and camera PLL + ConfigureMiniscope(device); + + // configuration properties + var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); + atMega.WriteByte(0x04, (uint)(InterleaveLed ? 0x00 : 0x03)); + + uint shutterWidth = FrameRate switch + { + UclaMiniscopeV4FramesPerSecond.Fps10Hz => 10000, + UclaMiniscopeV4FramesPerSecond.Fps15Hz => 6667, + UclaMiniscopeV4FramesPerSecond.Fps20Hz => 5000, + UclaMiniscopeV4FramesPerSecond.Fps25Hz => 4000, + UclaMiniscopeV4FramesPerSecond.Fps30Hz => 3300, + _ => 3300 + }; + + WriteCameraRegister(atMega, 200, shutterWidth); + + var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); + var disposable = DeviceManager.RegisterDevice(deviceName, deviceInfo); + var shutdown = Disposable.Create(() => + { + // turn off EWL + var max14574 = new I2CRegisterContext(device, UclaMiniscopeV4.Max14574Address); + max14574.WriteByte(0x03, 0x00); + + // turn off LED + var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); + atMega.WriteByte(1, 0xFF); + }); + return new CompositeDisposable( + shutdown, + disposable); + }); + } + + internal static void ConfigureMiniscope(DeviceContext device) + { + // configure deserializer + device.WriteRegister(DS90UB9x.TRIGGEROFF, 0); + device.WriteRegister(DS90UB9x.READSZ, UclaMiniscopeV4.SensorColumns); + device.WriteRegister(DS90UB9x.TRIGGER, (uint)DS90UB9xTriggerMode.HsyncEdgePositive); + device.WriteRegister(DS90UB9x.SYNCBITS, 0); + device.WriteRegister(DS90UB9x.DATAGATE, (uint)DS90UB9xDataGate.VsyncPositive); + device.WriteRegister(DS90UB9x.MARK,0); + + // configure deserializer I2C aliases + var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR); + uint coaxMode = 0x4 + (uint)DS90UB9xMode.Raw12BitLowFrequency; // 0x4 maintains coax mode + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.PortMode, coaxMode); + + uint alias = UclaMiniscopeV4.AtMegaAddress << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID1, alias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias1, alias); + + alias = UclaMiniscopeV4.Tpl0102Address << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID2, alias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias2, alias); + + alias = UclaMiniscopeV4.Max14574Address << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID3, alias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias3, alias); + + // set up potentiometer + var tpl0102 = new I2CRegisterContext(device, UclaMiniscopeV4.Tpl0102Address); + tpl0102.WriteByte(0x00, 0x72); + tpl0102.WriteByte(0x01, 0x00); + + // turn on EWL + var max14574 = new I2CRegisterContext(device, UclaMiniscopeV4.Max14574Address); + max14574.WriteByte(0x08, 0x7F); + max14574.WriteByte(0x09, 0x02); + + // turn on LED and setup Python480 + var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); + WriteCameraRegister(atMega, 16, 3); // Turn on PLL + WriteCameraRegister(atMega, 32, 0x7007); // Turn on clock managment + WriteCameraRegister(atMega, 199, 666); // Defines granularity (unit = 1/PLL clock) of exposure and reset_length + WriteCameraRegister(atMega, 200, 3300); // Set frame rate to 30 Hz + WriteCameraRegister(atMega, 201, 3000); // Set Exposure + } + + private static void WriteCameraRegister(I2CRegisterContext i2c, uint register, uint value) + { + // ATMega -> Python480 passthrough protocol + var regLow = register & 0xFF; + var regHigh = (register >> 8) & 0xFF; + var valLow = value & 0xFF; + var valHigh = (value >> 8) & 0xFF; + + i2c.WriteByte(0x05, regHigh); + i2c.WriteByte(regLow, valHigh); + i2c.WriteByte(valLow, 0x00); + } + + internal static void SetLedBrightness(DeviceContext device, double percent) + { + var des = device.Context.GetPassthroughDeviceContext(device.Address, DS90UB9x.ID); + + var atMega = new I2CRegisterContext(des, UclaMiniscopeV4.AtMegaAddress); + atMega.WriteByte(0x01, (uint)((percent == 0) ? 0xFF : 0x08)); + + var tpl0102 = new I2CRegisterContext(des, UclaMiniscopeV4.Tpl0102Address); + tpl0102.WriteByte(0x01, (uint)(255 * ((100 - percent) / 100.0))); + } + + internal static void SetSensorGain(DeviceContext device, UclaMiniscopeV4SensorGain gain) + { + var des = device.Context.GetPassthroughDeviceContext(device.Address, DS90UB9x.ID); + + var atMega = new I2CRegisterContext(des, UclaMiniscopeV4.AtMegaAddress); + WriteCameraRegister(atMega, 204, (uint)gain); + } + + internal static void SetLiquidLensVoltage(DeviceContext device, double voltage) + { + var des = device.Context.GetPassthroughDeviceContext(device.Address, DS90UB9x.ID); + + var max14574 = new I2CRegisterContext(des, UclaMiniscopeV4.Max14574Address); + max14574.WriteByte(0x08, (uint)((voltage - 24.4) / 0.0445) >> 2); + max14574.WriteByte(0x09, 0x02); + } + + } + + static class UclaMiniscopeV4 + { + public const int AtMegaAddress = 0x10; + public const int Tpl0102Address = 0x50; + public const int Max14574Address = 0x77; + + public const int SensorRows = 608; + public const int SensorColumns = 608; + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(UclaMiniscopeV4)) + { + } + } + } + + public enum UclaMiniscopeV4SensorGain + { + Low = 0x00E1, + Medium = 0x00E4, + High = 0x0024, + } + + public enum UclaMiniscopeV4FramesPerSecond + { + Fps10Hz, + Fps15Hz, + Fps20Hz, + Fps25Hz, + Fps30Hz, + } + +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4LinkController.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4LinkController.cs new file mode 100644 index 00000000..a5244a11 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4LinkController.cs @@ -0,0 +1,59 @@ +using System.Threading; + +namespace OpenEphys.Onix +{ + class ConfigureUclaMiniscopeV4LinkController : ConfigureFmcLinkController + { + protected override bool ConfigurePortVoltage(DeviceContext device) + { + const uint MinVoltage = 50; + const uint MaxVoltage = 70; + const uint VoltageOffset = 02; + const uint VoltageIncrement = 02; + + for (uint voltage = MinVoltage; voltage <= MaxVoltage; voltage += VoltageIncrement) + { + SetPortVoltage(device, voltage); + if (CheckLinkState(device)) + { + SetPortVoltage(device, voltage + VoltageOffset); + return CheckLinkState(device); + } + } + + return false; + } + + private void SetPortVoltage(DeviceContext device, uint voltage) + { + const int WaitUntilVoltageSettles = 200; + device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0); + Thread.Sleep(WaitUntilVoltageSettles); + device.WriteRegister(FmcLinkController.PORTVOLTAGE, voltage); + Thread.Sleep(WaitUntilVoltageSettles); + } + + override protected bool CheckLinkState(DeviceContext device) + { + try + { + var ds90ub9x = device.Context.GetPassthroughDeviceContext(DeviceAddress << 8, DS90UB9x.ID); + ConfigureUclaMiniscopeV4Camera.ConfigureMiniscope(ds90ub9x); + } + catch (oni.ONIException ex) + { + // this can occur if power is too low, so we need to be able to try again + const int FailureToWriteRegister = -6; + if (ex.Number != FailureToWriteRegister) + { + throw; + } + } + + Thread.Sleep(200); + + var linkState = device.ReadRegister(FmcLinkController.LINKSTATE); + return (linkState & FmcLinkController.LINKSTATE_SL) != 0; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Bno055Data.cs b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Bno055Data.cs new file mode 100644 index 00000000..fbefceca --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Bno055Data.cs @@ -0,0 +1,59 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix +{ + public class UclaMiniscopeV4Bno055Data : Source + { + [TypeConverter(typeof(UclaMiniscopeV4Bno055.NameConverter))] + public string DeviceName { get; set; } + + public override IObservable Generate() + { + // Max of 100 Hz, but limited by I2C bus + var source = Observable.Interval(TimeSpan.FromSeconds(0.01)); + return Generate(source); + } + + public unsafe IObservable Generate(IObservable source) + { + return Observable.Using( + () => DeviceManager.ReserveDevice(DeviceName), + disposable => disposable.Subject.SelectMany( + deviceInfo => Observable.Create(observer => + { + var device = deviceInfo.GetDeviceContext(typeof(UclaMiniscopeV4Bno055)); + var passthrough = device.GetPassthroughDeviceContext(DS90UB9x.ID); + var i2c = new I2CRegisterContext(passthrough, UclaMiniscopeV4Bno055.BNO055Address); + + var pollingObserver = Observer.Create( + _ => + { + Bno055DataFrame frame = default; + device.Context.EnsureContext(() => + { + var data = i2c.ReadBytes(UclaMiniscopeV4Bno055.DataAddress, sizeof(Bno055DataPayload)); + ulong clock = passthrough.ReadRegister(DS90UB9x.LASTI2CL); + clock += (ulong)passthrough.ReadRegister(DS90UB9x.LASTI2CH) << 32; + fixed (byte* dataPtr = data) + { + frame = new Bno055DataFrame(clock, (Bno055DataPayload*)dataPtr); + } + }); + + if (frame != null) + { + observer.OnNext(frame); + } + }, + observer.OnError, + observer.OnCompleted); + return source.SubscribeSafe(pollingObserver); + }))); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4DataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4DataFrame.cs new file mode 100644 index 00000000..99a778fc --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4DataFrame.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; +using OpenCV.Net; + +namespace OpenEphys.Onix +{ + public class UclaMiniscopeV4DataFrame + { + public UclaMiniscopeV4DataFrame(ulong[] clock, ulong[] hubClock, IplImage image) + { + Clock = clock; + HubClock = hubClock; + Image = image; + } + + public ulong[] Clock { get; } + + public ulong[] HubClock { get; } + + public IplImage Image { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + unsafe struct UclaMiniscopeV4ImagerPayload + { + public ulong HubClock; + public fixed short ImageRow[UclaMiniscopeV4.SensorColumns]; + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4ImageData.cs b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4ImageData.cs new file mode 100644 index 00000000..739215f8 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4ImageData.cs @@ -0,0 +1,98 @@ +using System; +using System.ComponentModel; +using System.Drawing.Design; +using System.Linq; +using System.Reactive; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Runtime.InteropServices; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix +{ + public class UclaMiniscopeV4ImageData : Source + { + readonly BehaviorSubject ledBrightness = new(0); + readonly BehaviorSubject sensorGain = new(UclaMiniscopeV4SensorGain.Low); + readonly BehaviorSubject liquidLensVoltage = new(0); + + [TypeConverter(typeof(UclaMiniscopeV4.NameConverter))] + public string DeviceName { get; set; } + + [Description("Excitation LED brightness (0-100%).")] + [Range(0, 100)] + [Precision(1, 1)] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + public double LedBrightness + { + get => ledBrightness.Value; + set => ledBrightness.OnNext(value); + } + + [Description("Camera sensor analog gain.")] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + public UclaMiniscopeV4SensorGain SensorGain + { + get => sensorGain.Value; + set => sensorGain.OnNext(value); + } + + [Description("Liquid lens voltage(Volts RMS).")] + [Range(24.4, 69.7)] + [Precision(1, 1)] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + public double LiquidLensVoltage + { + get => liquidLensVoltage.Value; + set => liquidLensVoltage.OnNext(value); + } + + public unsafe override IObservable Generate() + { + + return Observable.Using( + () => DeviceManager.ReserveDevice(DeviceName), + disposable => disposable.Subject.SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(UclaMiniscopeV4)); + var passthrough = device.GetPassthroughDeviceContext(DS90UB9x.ID); + var scopeData = device.Context.FrameReceived.Where(frame => frame.DeviceAddress == passthrough.Address); + return Observable.Create(observer => + { + var sampleIndex = 0; + var imageBuffer = new short[UclaMiniscopeV4.SensorRows * UclaMiniscopeV4.SensorColumns]; + var hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; + var clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; + + var frameObserver = Observer.Create( + frame => + { + var payload = (UclaMiniscopeV4ImagerPayload*)frame.Data.ToPointer(); + Marshal.Copy(new IntPtr(payload->ImageRow), imageBuffer, sampleIndex * UclaMiniscopeV4.SensorColumns, UclaMiniscopeV4.SensorColumns); + hubClockBuffer[sampleIndex] = payload->HubClock; + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= UclaMiniscopeV4.SensorRows) + { + var imageData = BufferHelper.CopyTranspose(imageBuffer, UclaMiniscopeV4.SensorRows, UclaMiniscopeV4.SensorColumns, Depth.U16); + observer.OnNext(new UclaMiniscopeV4DataFrame(clockBuffer, hubClockBuffer, imageData.GetImage())); + hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; + clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + + return new CompositeDisposable( + ledBrightness.Subscribe(value => ConfigureUclaMiniscopeV4Camera.SetLedBrightness(device, value)), + sensorGain.Subscribe(value => ConfigureUclaMiniscopeV4Camera.SetSensorGain(device, value)), + liquidLensVoltage.Subscribe(value => ConfigureUclaMiniscopeV4Camera.SetLiquidLensVoltage(device, value)), + scopeData.SubscribeSafe(frameObserver) + ); ; + }); + })); + } + } +} From 53730d652fe5e2a66243216ac0c3009b704e8d61 Mon Sep 17 00:00:00 2001 From: jonnew Date: Tue, 25 Jun 2024 10:36:34 -0400 Subject: [PATCH 02/15] Rename UclaMiniscopeV4ImageData UclaMiniscopeV4CameraData - Consistent naming with with ConfigureUclaMiniscopeV4Camera - Capture configuration properties outside of ConfigureDevice in ConfigureUclaMiniscopeV4Camera --- .../ConfigureUclaMiniscopeV4Camera.cs | 26 +++++++++---------- ...geData.cs => UclaMiniscopeV4CameraData.cs} | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) rename OpenEphys.Onix/OpenEphys.Onix/{UclaMiniscopeV4ImageData.cs => UclaMiniscopeV4CameraData.cs} (98%) diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs index cbb3e12a..36450b7d 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs @@ -28,6 +28,17 @@ public override IObservable Process(IObservable source var enable = Enable; var deviceName = DeviceName; var deviceAddress = DeviceAddress; + uint shutterWidth = FrameRate switch + { + UclaMiniscopeV4FramesPerSecond.Fps10Hz => 10000, + UclaMiniscopeV4FramesPerSecond.Fps15Hz => 6667, + UclaMiniscopeV4FramesPerSecond.Fps20Hz => 5000, + UclaMiniscopeV4FramesPerSecond.Fps25Hz => 4000, + UclaMiniscopeV4FramesPerSecond.Fps30Hz => 3300, + _ => 3300 + }; + var interleaveLED = InterleaveLed; + return source.ConfigureDevice(context => { // configure device via the DS90UB9x deserializer device @@ -39,18 +50,7 @@ public override IObservable Process(IObservable source // configuration properties var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); - atMega.WriteByte(0x04, (uint)(InterleaveLed ? 0x00 : 0x03)); - - uint shutterWidth = FrameRate switch - { - UclaMiniscopeV4FramesPerSecond.Fps10Hz => 10000, - UclaMiniscopeV4FramesPerSecond.Fps15Hz => 6667, - UclaMiniscopeV4FramesPerSecond.Fps20Hz => 5000, - UclaMiniscopeV4FramesPerSecond.Fps25Hz => 4000, - UclaMiniscopeV4FramesPerSecond.Fps30Hz => 3300, - _ => 3300 - }; - + atMega.WriteByte(0x04, (uint)(interleaveLED ? 0x00 : 0x03)); WriteCameraRegister(atMega, 200, shutterWidth); var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); @@ -79,7 +79,7 @@ internal static void ConfigureMiniscope(DeviceContext device) device.WriteRegister(DS90UB9x.TRIGGER, (uint)DS90UB9xTriggerMode.HsyncEdgePositive); device.WriteRegister(DS90UB9x.SYNCBITS, 0); device.WriteRegister(DS90UB9x.DATAGATE, (uint)DS90UB9xDataGate.VsyncPositive); - device.WriteRegister(DS90UB9x.MARK,0); + //device.WriteRegister(DS90UB9x.MARK, (uint)DS90UB9xMarkMode.VsyncRising); // TODO: Not sure why this is required given that data is gated by VSYNC HIGH // configure deserializer I2C aliases var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR); diff --git a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4ImageData.cs b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs similarity index 98% rename from OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4ImageData.cs rename to OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs index 739215f8..7a25de8b 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4ImageData.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs @@ -12,7 +12,7 @@ namespace OpenEphys.Onix { - public class UclaMiniscopeV4ImageData : Source + public class UclaMiniscopeV4CameraData : Source { readonly BehaviorSubject ledBrightness = new(0); readonly BehaviorSubject sensorGain = new(UclaMiniscopeV4SensorGain.Low); From c8150215764e7ddbd689b9a1267e1c01873d7b31 Mon Sep 17 00:00:00 2001 From: jonnew Date: Wed, 26 Jun 2024 11:22:13 -0400 Subject: [PATCH 03/15] Address code review in #115 - Address code syntax and naming issues - Add Vsync rising mark to frame data so that we can align images to the first row in the face of asynchronous bonsai fraime collection - There is a larger issue about data integrity that seems to have more to do with the miniscope pcb design than issues with our hardware this library that is documented in #115 --- .../ConfigureUclaMiniscopeV4.cs | 6 ++--- .../ConfigureUclaMiniscopeV4Camera.cs | 24 ++++++++++--------- .../UclaMiniscopeV4CameraData.cs | 12 ++++++++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4.cs index 3ddff61f..dd69e181 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4.cs @@ -16,7 +16,7 @@ public ConfigureUclaMiniscopeV4() [Category(ConfigurationCategory)] [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureUclaMiniscopeV4Camera Imager { get; set; } = new(); + public ConfigureUclaMiniscopeV4Camera Camera { get; set; } = new(); [Category(ConfigurationCategory)] [TypeConverter(typeof(HubDeviceConverter))] @@ -30,7 +30,7 @@ public PortName Port port = value; var offset = (uint)port << 8; LinkController.DeviceAddress = (uint)port; - Imager.DeviceAddress = offset + 0; + Camera.DeviceAddress = offset + 0; Bno055.DeviceAddress = offset + 1; } } @@ -47,7 +47,7 @@ public double? PortVoltage internal override IEnumerable GetDevices() { yield return LinkController; - yield return Imager; + yield return Camera; yield return Bno055; } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs index 36450b7d..07095058 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs @@ -79,24 +79,27 @@ internal static void ConfigureMiniscope(DeviceContext device) device.WriteRegister(DS90UB9x.TRIGGER, (uint)DS90UB9xTriggerMode.HsyncEdgePositive); device.WriteRegister(DS90UB9x.SYNCBITS, 0); device.WriteRegister(DS90UB9x.DATAGATE, (uint)DS90UB9xDataGate.VsyncPositive); - //device.WriteRegister(DS90UB9x.MARK, (uint)DS90UB9xMarkMode.VsyncRising); // TODO: Not sure why this is required given that data is gated by VSYNC HIGH + + // NB: This is required because Bonsai is not garuenteed to capure every frame at the start of acqusition. + // For this reason, the frame start needs to be marked. + device.WriteRegister(DS90UB9x.MARK, (uint)DS90UB9xMarkMode.VsyncRising); // configure deserializer I2C aliases var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR); uint coaxMode = 0x4 + (uint)DS90UB9xMode.Raw12BitLowFrequency; // 0x4 maintains coax mode deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.PortMode, coaxMode); - uint alias = UclaMiniscopeV4.AtMegaAddress << 1; - deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID1, alias); - deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias1, alias); + uint i2cAlias = UclaMiniscopeV4.AtMegaAddress << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID1, i2cAlias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias1, i2cAlias); - alias = UclaMiniscopeV4.Tpl0102Address << 1; - deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID2, alias); - deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias2, alias); + i2cAlias = UclaMiniscopeV4.Tpl0102Address << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID2, i2cAlias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias2, i2cAlias); - alias = UclaMiniscopeV4.Max14574Address << 1; - deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID3, alias); - deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias3, alias); + i2cAlias = UclaMiniscopeV4.Max14574Address << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID3, i2cAlias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias3, i2cAlias); // set up potentiometer var tpl0102 = new I2CRegisterContext(device, UclaMiniscopeV4.Tpl0102Address); @@ -193,5 +196,4 @@ public enum UclaMiniscopeV4FramesPerSecond Fps25Hz, Fps30Hz, } - } diff --git a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs index 7a25de8b..0052fc92 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs @@ -51,7 +51,6 @@ public double LiquidLensVoltage public unsafe override IObservable Generate() { - return Observable.Using( () => DeviceManager.ReserveDevice(DeviceName), disposable => disposable.Subject.SelectMany(deviceInfo => @@ -65,21 +64,30 @@ public unsafe override IObservable Generate() var imageBuffer = new short[UclaMiniscopeV4.SensorRows * UclaMiniscopeV4.SensorColumns]; var hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; var clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; + var awaitingFrameStart = true; var frameObserver = Observer.Create( frame => { var payload = (UclaMiniscopeV4ImagerPayload*)frame.Data.ToPointer(); + + // Await for first row + if (awaitingFrameStart && (payload->ImageRow[0] & 0x8000) == 0) + return; + + awaitingFrameStart = false; Marshal.Copy(new IntPtr(payload->ImageRow), imageBuffer, sampleIndex * UclaMiniscopeV4.SensorColumns, UclaMiniscopeV4.SensorColumns); hubClockBuffer[sampleIndex] = payload->HubClock; clockBuffer[sampleIndex] = frame.Clock; if (++sampleIndex >= UclaMiniscopeV4.SensorRows) { - var imageData = BufferHelper.CopyTranspose(imageBuffer, UclaMiniscopeV4.SensorRows, UclaMiniscopeV4.SensorColumns, Depth.U16); + var imageData = Mat.FromArray(imageBuffer, UclaMiniscopeV4.SensorRows, UclaMiniscopeV4.SensorColumns, Depth.U16, 1); + CV.ConvertScale(imageData.GetRow(0), imageData.GetRow(0), 1.0f, -32768.0f); // Get rid first row's mark bit observer.OnNext(new UclaMiniscopeV4DataFrame(clockBuffer, hubClockBuffer, imageData.GetImage())); hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; sampleIndex = 0; + awaitingFrameStart = true; } }, observer.OnError, From fe15000f5254be761359624f3e4b248cef880ce7 Mon Sep 17 00:00:00 2001 From: jonnew Date: Wed, 26 Jun 2024 14:25:14 -0400 Subject: [PATCH 04/15] Default UCLA miniscope v4 focus to midscale --- OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs index 0052fc92..3ebba792 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs @@ -16,7 +16,7 @@ public class UclaMiniscopeV4CameraData : Source { readonly BehaviorSubject ledBrightness = new(0); readonly BehaviorSubject sensorGain = new(UclaMiniscopeV4SensorGain.Low); - readonly BehaviorSubject liquidLensVoltage = new(0); + readonly BehaviorSubject liquidLensVoltage = new(47); // NB: middle of range [TypeConverter(typeof(UclaMiniscopeV4.NameConverter))] public string DeviceName { get; set; } @@ -39,7 +39,7 @@ public UclaMiniscopeV4SensorGain SensorGain set => sensorGain.OnNext(value); } - [Description("Liquid lens voltage(Volts RMS).")] + [Description("Liquid lens voltage (Volts RMS).")] [Range(24.4, 69.7)] [Precision(1, 1)] [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] @@ -71,7 +71,7 @@ public unsafe override IObservable Generate() { var payload = (UclaMiniscopeV4ImagerPayload*)frame.Data.ToPointer(); - // Await for first row + // Wait for first row if (awaitingFrameStart && (payload->ImageRow[0] & 0x8000) == 0) return; From 72d8e2d57731ef025e8f0b4d6f175cb997ec0756 Mon Sep 17 00:00:00 2001 From: jonnew Date: Sat, 20 Jul 2024 16:12:41 -0400 Subject: [PATCH 05/15] Move miniscope real-time properties to configuration node - Fixes #133 - See #114 for reasoning --- .../ConfigureUclaMiniscopeV4Camera.cs | 43 ++++++++++++++-- ...CameraData.cs => UclaMiniscopeV4Camera.cs} | 50 ++----------------- ...V4DataFrame.cs => UclaMiniscopeV4Image.cs} | 4 +- 3 files changed, 47 insertions(+), 50 deletions(-) rename OpenEphys.Onix/OpenEphys.Onix/{UclaMiniscopeV4CameraData.cs => UclaMiniscopeV4Camera.cs} (58%) rename OpenEphys.Onix/OpenEphys.Onix/{UclaMiniscopeV4DataFrame.cs => UclaMiniscopeV4Image.cs} (80%) diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs index 07095058..8b9e86cd 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs @@ -1,11 +1,18 @@ using System; using System.ComponentModel; +using System.Drawing.Design; using System.Reactive.Disposables; +using System.Reactive.Subjects; +using Bonsai; namespace OpenEphys.Onix { public class ConfigureUclaMiniscopeV4Camera : SingleDeviceFactory { + readonly BehaviorSubject ledBrightness = new(0); + readonly BehaviorSubject sensorGain = new(UclaMiniscopeV4SensorGain.Low); + readonly BehaviorSubject liquidLensVoltage = new(47); // NB: middle of range + public ConfigureUclaMiniscopeV4Camera() : base(typeof(UclaMiniscopeV4)) { @@ -17,11 +24,39 @@ public ConfigureUclaMiniscopeV4Camera() [Category(ConfigurationCategory)] [Description("Only turn on excitation LED during camera exposures.")] - public bool InterleaveLed { get; set; } = false; + public UclaMiniscopeV4FramesPerSecond FrameRate { get; set; } = UclaMiniscopeV4FramesPerSecond.Fps30Hz; [Category(ConfigurationCategory)] [Description("Only turn on excitation LED during camera exposures.")] - public UclaMiniscopeV4FramesPerSecond FrameRate { get; set; } = UclaMiniscopeV4FramesPerSecond.Fps30Hz; + public bool InterleaveLed { get; set; } = false; + + [Description("Excitation LED brightness (0-100%).")] + [Range(0, 100)] + [Precision(1, 1)] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + public double LedBrightness + { + get => ledBrightness.Value; + set => ledBrightness.OnNext(value); + } + + [Description("Camera sensor analog gain.")] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + public UclaMiniscopeV4SensorGain SensorGain + { + get => sensorGain.Value; + set => sensorGain.OnNext(value); + } + + [Description("Liquid lens voltage (Volts RMS).")] + [Range(24.4, 69.7)] + [Precision(1, 1)] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + public double LiquidLensVoltage + { + get => liquidLensVoltage.Value; + set => liquidLensVoltage.OnNext(value); + } public override IObservable Process(IObservable source) { @@ -66,6 +101,9 @@ public override IObservable Process(IObservable source atMega.WriteByte(1, 0xFF); }); return new CompositeDisposable( + ledBrightness.Subscribe(value => SetLedBrightness(device, value)), + sensorGain.Subscribe(value => SetSensorGain(device, value)), + liquidLensVoltage.Subscribe(value => SetLiquidLensVoltage(device, value)), shutdown, disposable); }); @@ -160,7 +198,6 @@ internal static void SetLiquidLensVoltage(DeviceContext device, double voltage) max14574.WriteByte(0x08, (uint)((voltage - 24.4) / 0.0445) >> 2); max14574.WriteByte(0x09, 0x02); } - } static class UclaMiniscopeV4 diff --git a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Camera.cs similarity index 58% rename from OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs rename to OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Camera.cs index 3ebba792..f7d0139e 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4CameraData.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Camera.cs @@ -1,55 +1,20 @@ using System; using System.ComponentModel; -using System.Drawing.Design; using System.Linq; using System.Reactive; -using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Reactive.Subjects; using System.Runtime.InteropServices; using Bonsai; using OpenCV.Net; namespace OpenEphys.Onix { - public class UclaMiniscopeV4CameraData : Source + public class UclaMiniscopeV4Camera : Source { - readonly BehaviorSubject ledBrightness = new(0); - readonly BehaviorSubject sensorGain = new(UclaMiniscopeV4SensorGain.Low); - readonly BehaviorSubject liquidLensVoltage = new(47); // NB: middle of range - [TypeConverter(typeof(UclaMiniscopeV4.NameConverter))] public string DeviceName { get; set; } - [Description("Excitation LED brightness (0-100%).")] - [Range(0, 100)] - [Precision(1, 1)] - [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] - public double LedBrightness - { - get => ledBrightness.Value; - set => ledBrightness.OnNext(value); - } - - [Description("Camera sensor analog gain.")] - [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] - public UclaMiniscopeV4SensorGain SensorGain - { - get => sensorGain.Value; - set => sensorGain.OnNext(value); - } - - [Description("Liquid lens voltage (Volts RMS).")] - [Range(24.4, 69.7)] - [Precision(1, 1)] - [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] - public double LiquidLensVoltage - { - get => liquidLensVoltage.Value; - set => liquidLensVoltage.OnNext(value); - } - - public unsafe override IObservable Generate() + public unsafe override IObservable Generate() { return Observable.Using( () => DeviceManager.ReserveDevice(DeviceName), @@ -58,7 +23,7 @@ public unsafe override IObservable Generate() var device = deviceInfo.GetDeviceContext(typeof(UclaMiniscopeV4)); var passthrough = device.GetPassthroughDeviceContext(DS90UB9x.ID); var scopeData = device.Context.FrameReceived.Where(frame => frame.DeviceAddress == passthrough.Address); - return Observable.Create(observer => + return Observable.Create(observer => { var sampleIndex = 0; var imageBuffer = new short[UclaMiniscopeV4.SensorRows * UclaMiniscopeV4.SensorColumns]; @@ -83,7 +48,7 @@ public unsafe override IObservable Generate() { var imageData = Mat.FromArray(imageBuffer, UclaMiniscopeV4.SensorRows, UclaMiniscopeV4.SensorColumns, Depth.U16, 1); CV.ConvertScale(imageData.GetRow(0), imageData.GetRow(0), 1.0f, -32768.0f); // Get rid first row's mark bit - observer.OnNext(new UclaMiniscopeV4DataFrame(clockBuffer, hubClockBuffer, imageData.GetImage())); + observer.OnNext(new UclaMiniscopeV4Image(clockBuffer, hubClockBuffer, imageData.GetImage())); hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; sampleIndex = 0; @@ -93,12 +58,7 @@ public unsafe override IObservable Generate() observer.OnError, observer.OnCompleted); - return new CompositeDisposable( - ledBrightness.Subscribe(value => ConfigureUclaMiniscopeV4Camera.SetLedBrightness(device, value)), - sensorGain.Subscribe(value => ConfigureUclaMiniscopeV4Camera.SetSensorGain(device, value)), - liquidLensVoltage.Subscribe(value => ConfigureUclaMiniscopeV4Camera.SetLiquidLensVoltage(device, value)), - scopeData.SubscribeSafe(frameObserver) - ); ; + return scopeData.SubscribeSafe(frameObserver); }); })); } diff --git a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4DataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Image.cs similarity index 80% rename from OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4DataFrame.cs rename to OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Image.cs index 99a778fc..28d00f48 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4DataFrame.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/UclaMiniscopeV4Image.cs @@ -3,9 +3,9 @@ namespace OpenEphys.Onix { - public class UclaMiniscopeV4DataFrame + public class UclaMiniscopeV4Image { - public UclaMiniscopeV4DataFrame(ulong[] clock, ulong[] hubClock, IplImage image) + public UclaMiniscopeV4Image(ulong[] clock, ulong[] hubClock, IplImage image) { Clock = clock; HubClock = hubClock; From 62d573a34950c1767ce7806a438ca8a6456fc540 Mon Sep 17 00:00:00 2001 From: jonnew Date: Sat, 20 Jul 2024 16:15:54 -0400 Subject: [PATCH 06/15] Fix description and order of miniscope properties --- .../ConfigureUclaMiniscopeV4Camera.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs index 8b9e86cd..b18b93c8 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs @@ -23,9 +23,17 @@ public ConfigureUclaMiniscopeV4Camera() public bool Enable { get; set; } = true; [Category(ConfigurationCategory)] - [Description("Only turn on excitation LED during camera exposures.")] + [Description("Camera video rate in frames per second.")] public UclaMiniscopeV4FramesPerSecond FrameRate { get; set; } = UclaMiniscopeV4FramesPerSecond.Fps30Hz; + [Description("Camera sensor analog gain.")] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + public UclaMiniscopeV4SensorGain SensorGain + { + get => sensorGain.Value; + set => sensorGain.OnNext(value); + } + [Category(ConfigurationCategory)] [Description("Only turn on excitation LED during camera exposures.")] public bool InterleaveLed { get; set; } = false; @@ -40,14 +48,6 @@ public double LedBrightness set => ledBrightness.OnNext(value); } - [Description("Camera sensor analog gain.")] - [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] - public UclaMiniscopeV4SensorGain SensorGain - { - get => sensorGain.Value; - set => sensorGain.OnNext(value); - } - [Description("Liquid lens voltage (Volts RMS).")] [Range(24.4, 69.7)] [Precision(1, 1)] From 3700e24a36b74b502c4fd433a2f539cc51d9a2dc Mon Sep 17 00:00:00 2001 From: jonnew Date: Fri, 30 Aug 2024 17:13:25 -0400 Subject: [PATCH 07/15] Complete UCLA miniscope V4 supporti - Tested on OEPS miniscope V4.4 - To finalize this, we must test on a large batch of scopes to ensure functionality of all features and reliablity of datastream --- OpenEphys.Onix1/Bno055DataFrame.cs | 7 +- OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs | 52 +++++++- .../ConfigureUclaMiniscopeV4Bno055.cs | 37 ++++- .../ConfigureUclaMiniscopeV4Camera.cs | 126 +++++++++++++++--- OpenEphys.Onix1/ContextTask.cs | 2 +- OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs | 15 ++- OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs | 34 ++++- ...Camera.cs => UclaMiniscopeV4CameraData.cs} | 23 +++- OpenEphys.Onix1/UclaMiniscopeV4CameraFrame.cs | 35 +++++ OpenEphys.Onix1/UclaMiniscopeV4Image.cs | 28 ---- 10 files changed, 291 insertions(+), 68 deletions(-) rename OpenEphys.Onix1/{UclaMiniscopeV4Camera.cs => UclaMiniscopeV4CameraData.cs} (72%) create mode 100644 OpenEphys.Onix1/UclaMiniscopeV4CameraFrame.cs delete mode 100644 OpenEphys.Onix1/UclaMiniscopeV4Image.cs diff --git a/OpenEphys.Onix1/Bno055DataFrame.cs b/OpenEphys.Onix1/Bno055DataFrame.cs index f21364f4..c883050b 100644 --- a/OpenEphys.Onix1/Bno055DataFrame.cs +++ b/OpenEphys.Onix1/Bno055DataFrame.cs @@ -5,7 +5,8 @@ namespace OpenEphys.Onix1 { /// - /// A class that contains 3D orientation data produced by a Bosch BNO055 9-axis inertial measurement unit (IMU). + /// A class that contains 3D orientation data produced by a Bosch BNO055 9-axis inertial measurement unit + /// (IMU). /// public class Bno055DataFrame : DataFrame { @@ -51,14 +52,14 @@ internal unsafe Bno055DataFrame(ulong clock, Bno055DataPayload* payload) /// /// Gets the 3D orientation in Euler angle format with units of degrees. /// - /// + /// /// The Tait-Bryan formalism is used: /// /// Yaw: 0 to 360 degrees. /// Roll: -180 to 180 degrees /// Pitch: -90 to 90 degrees /// - /// + /// public Vector3 EulerAngle { get; } /// diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs index cb35c4d1..74497aa9 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs @@ -4,25 +4,56 @@ namespace OpenEphys.Onix1 { + /// + /// Configures a UCLA Miniscope V4 on the specified port. + /// + /// + /// The UCLA Miniscope V4 is a e miniaturized fluorescent microscope for performing single-photon calcium + /// imaging in freely moving animals. It has the following features: + /// + /// A Python-480 0.48 Megapixel CMOS image sensor. + /// A BNO055 9-axis IMU for real-time, 3D orientation tracking. + /// An electrowetting lens for remote focal plane adjustment. + /// An excitation LED with adjustable brightness control and optional exposure-driven + /// interleaving to reduce photobleaching. + /// + /// public class ConfigureUclaMiniscopeV4 : MultiDeviceFactory { PortName port; readonly ConfigureUclaMiniscopeV4PortController PortControl = new(); + /// + /// Initialize a new instance of a class. + /// public ConfigureUclaMiniscopeV4() { Port = PortName.PortA; PortControl.HubConfiguration = HubConfiguration.Passthrough; } + /// + /// Gets or sets the Miniscope camera configuration. + /// [Category(DevicesCategory)] [TypeConverter(typeof(SingleDeviceFactoryConverter))] public ConfigureUclaMiniscopeV4Camera Camera { get; set; } = new(); + /// + /// Gets or sets the Bno055 9-axis inertial measurement unit configuration. + /// [Category(DevicesCategory)] [TypeConverter(typeof(SingleDeviceFactoryConverter))] public ConfigureUclaMiniscopeV4Bno055 Bno055 { get; set; } = new(); + /// + /// Gets or sets the port. + /// + /// + /// The port is the physical connection to the ONIX breakout board and must be specified prior to operation. + /// + [Description("Specifies the physical connection of the miniscope to the ONIX breakout board.")] + [Category(ConfigurationCategory)] public PortName Port { get { return port; } @@ -36,9 +67,26 @@ public PortName Port } } + /// + /// Gets or sets the port voltage override. + /// + /// + /// + /// If defined, it will override automated voltage discovery and apply the specified voltage to the miniscope. + /// If left blank, an automated headstage detection algorithm will attempt to communicate with the miniscope and + /// apply an appropriate voltage for stable operation. Because ONIX allows any coaxial tether to be used, some of + /// which are thin enough to result in a significant voltage drop, its may be required to manually specify the + /// port voltage. + /// + /// + /// Warning: this device requires 5.0V to 6.0V, measured at the miniscope, for proper operation. Supplying higher + /// voltages may result in damage. + /// + /// [Description("If defined, it will override automated voltage discovery and apply the specified voltage" + - "to the headstage. Warning: this device requires 5.0V to 6.0V for proper operation." + - "Supplying higher voltages may result in damage to the headstage.")] + "to the miniscope. Warning: this device requires 5.0V to 6.0V for proper operation." + + "Supplying higher voltages may result in damage to the miniscope.")] + [Category(ConfigurationCategory)] public double? PortVoltage { get => PortControl.PortVoltage; diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs index 77400a3e..230bf0c5 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs @@ -3,17 +3,41 @@ namespace OpenEphys.Onix1 { + /// + /// Configures the Bno055 inertial measurement unit (IMU) on a UCLA Miniscope V4. + /// public class ConfigureUclaMiniscopeV4Bno055 : SingleDeviceFactory { + /// + /// Initialize a new instance of a class. + /// public ConfigureUclaMiniscopeV4Bno055() : base(typeof(UclaMiniscopeV4Bno055)) { } + /// + /// Gets or sets the device enable state. + /// + /// + /// If set to true, will produce data. If set to false, + /// will not produce data. + /// [Category(ConfigurationCategory)] [Description("Specifies whether the BNO055 device is enabled.")] public bool Enable { get; set; } = true; + /// + /// Configures the Bno055 inertial measurement unit (IMU) on a UCLA Miniscope V4. + /// + /// + /// This will schedule configuration actions to be applied by a node + /// prior to data acquisition. + /// + /// A sequence of instances that holds all configuration actions. + /// + /// The original sequence but with each instance now containing configuration actions required to use the miniscope's Bno055 IMU. + /// public override IObservable Process(IObservable source) { var enable = Enable; @@ -25,7 +49,7 @@ public override IObservable Process(IObservable source var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); ConfigureDeserializer(device); ConfigureBno055(device); - var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); + var deviceInfo = new UclaMiniscopeV4Bno055DeviceInfo(context, DeviceType, deviceAddress, enable); return DeviceManager.RegisterDevice(deviceName, deviceInfo); }); } @@ -66,4 +90,15 @@ public NameConverter() } } } + + class UclaMiniscopeV4Bno055DeviceInfo : DeviceInfo + { + public UclaMiniscopeV4Bno055DeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, bool enable) + : base(context, deviceType, deviceAddress) + { + Enable = enable; + } + + public bool Enable { get; } + } } diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs index 03557318..5e99aff8 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs @@ -7,26 +7,46 @@ namespace OpenEphys.Onix1 { + /// + /// Configures the camera on a UCLA Miniscope V4. + /// public class ConfigureUclaMiniscopeV4Camera : SingleDeviceFactory { readonly BehaviorSubject ledBrightness = new(0); readonly BehaviorSubject sensorGain = new(UclaMiniscopeV4SensorGain.Low); readonly BehaviorSubject liquidLensVoltage = new(47); // NB: middle of range + /// + /// Initialize a new instance of a class. + /// public ConfigureUclaMiniscopeV4Camera() : base(typeof(UclaMiniscopeV4)) { } + /// + /// Gets or sets a value indicating whether the camera will produce image data. + /// + /// + /// If set to true, will produce image data. If set to false, will not produce image data. + /// [Category(ConfigurationCategory)] [Description("Specifies whether the camera is enabled.")] public bool Enable { get; set; } = true; + /// + /// Gets or sets the camera video rate in frames per second. + /// [Category(ConfigurationCategory)] [Description("Camera video rate in frames per second.")] public UclaMiniscopeV4FramesPerSecond FrameRate { get; set; } = UclaMiniscopeV4FramesPerSecond.Fps30Hz; + /// + /// Gets or sets the camera sensor's analog gain. + /// [Description("Camera sensor analog gain.")] + [Category(AcquisitionCategory)] [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] public UclaMiniscopeV4SensorGain SensorGain { @@ -34,11 +54,24 @@ public UclaMiniscopeV4SensorGain SensorGain set => sensorGain.OnNext(value); } + /// + /// Gets or sets a value indicating whether the excitation LED should turn on only when the camera + /// shutter is open. + /// + /// + /// If set to true, the excitation LED will turn on briefly before, and turn off briefly after, the + /// camera begins photon collection on its photodiode array. If set to false, the excitation LED will + /// remain on at all times. + /// [Category(ConfigurationCategory)] [Description("Only turn on excitation LED during camera exposures.")] public bool InterleaveLed { get; set; } = false; - [Description("Excitation LED brightness (0-100%).")] + /// + /// Gets or sets the excitation LED brightness level (0-100%). + /// + [Description("Excitation LED brightness level (0-100%).")] + [Category(AcquisitionCategory)] [Range(0, 100)] [Precision(1, 1)] [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] @@ -48,7 +81,18 @@ public double LedBrightness set => ledBrightness.OnNext(value); } - [Description("Liquid lens voltage (Volts RMS).")] + /// + /// Gets or sets the liquid lens driver voltage (Volts RMS). + /// + /// + /// The imaging focal plane is controlled by using a MAX14574 high-voltage liquid lens driver. This + /// chip produces pulse-width modulated, 1 kHz alternative electric field that deforms the miniscope's + /// liquid lens in order to change the focal plane. The strength of this field determines the degree + /// of deformation and therefore the focal depth. The default setting of 47 Volts RMS corresponds to + /// approximately mid-range. + /// + [Description("Liquid lens driver voltage (Volts RMS).")] + [Category(AcquisitionCategory)] [Range(24.4, 69.7)] [Precision(1, 1)] [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] @@ -58,6 +102,19 @@ public double LiquidLensVoltage set => liquidLensVoltage.OnNext(value); } + /// + /// Configures the camera on a UCLA Miniscope V4. + /// + /// + /// This will schedule configuration actions to be applied by a node + /// prior to data acquisition. + /// + /// A sequence of instances that holds all + /// configuration actions. + /// + /// The original sequence but with each instance now containing + /// configuration actions required to use the miniscope's camera. + /// public override IObservable Process(IObservable source) { var enable = Enable; @@ -78,6 +135,8 @@ public override IObservable Process(IObservable source { // configure device via the DS90UB9x deserializer device var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); + + // TODO: early exit if false? device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); // configure deserializer, chip states, and camera PLL @@ -89,7 +148,6 @@ public override IObservable Process(IObservable source WriteCameraRegister(atMega, 200, shutterWidth); var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); - var disposable = DeviceManager.RegisterDevice(deviceName, deviceInfo); var shutdown = Disposable.Create(() => { // turn off EWL @@ -104,8 +162,8 @@ public override IObservable Process(IObservable source ledBrightness.Subscribe(value => SetLedBrightness(device, value)), sensorGain.Subscribe(value => SetSensorGain(device, value)), liquidLensVoltage.Subscribe(value => SetLiquidLensVoltage(device, value)), - shutdown, - disposable); + DeviceManager.RegisterDevice(deviceName, deviceInfo), + shutdown); }); } @@ -118,8 +176,8 @@ internal static void ConfigureMiniscope(DeviceContext device) device.WriteRegister(DS90UB9x.SYNCBITS, 0); device.WriteRegister(DS90UB9x.DATAGATE, (uint)DS90UB9xDataGate.VsyncPositive); - // NB: This is required because Bonsai is not garuenteed to capure every frame at the start of acqusition. - // For this reason, the frame start needs to be marked. + // NB: This is required because Bonsai is not guaranteed to capture every frame at the start of + // acquisition. For this reason, the frame start needs to be marked. device.WriteRegister(DS90UB9x.MARK, (uint)DS90UB9xMarkMode.VsyncRising); // configure deserializer I2C aliases @@ -152,13 +210,13 @@ internal static void ConfigureMiniscope(DeviceContext device) // turn on LED and setup Python480 var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); WriteCameraRegister(atMega, 16, 3); // Turn on PLL - WriteCameraRegister(atMega, 32, 0x7007); // Turn on clock managment + WriteCameraRegister(atMega, 32, 0x7007); // Turn on clock management WriteCameraRegister(atMega, 199, 666); // Defines granularity (unit = 1/PLL clock) of exposure and reset_length WriteCameraRegister(atMega, 200, 3300); // Set frame rate to 30 Hz WriteCameraRegister(atMega, 201, 3000); // Set Exposure } - private static void WriteCameraRegister(I2CRegisterContext i2c, uint register, uint value) + static void WriteCameraRegister(I2CRegisterContext i2c, uint register, uint value) { // ATMega -> Python480 passthrough protocol var regLow = register & 0xFF; @@ -173,28 +231,22 @@ private static void WriteCameraRegister(I2CRegisterContext i2c, uint register, u internal static void SetLedBrightness(DeviceContext device, double percent) { - var des = device.Context.GetPassthroughDeviceContext(device.Address, typeof(DS90UB9x)); - - var atMega = new I2CRegisterContext(des, UclaMiniscopeV4.AtMegaAddress); + var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); atMega.WriteByte(0x01, (uint)((percent == 0) ? 0xFF : 0x08)); - var tpl0102 = new I2CRegisterContext(des, UclaMiniscopeV4.Tpl0102Address); + var tpl0102 = new I2CRegisterContext(device, UclaMiniscopeV4.Tpl0102Address); tpl0102.WriteByte(0x01, (uint)(255 * ((100 - percent) / 100.0))); } internal static void SetSensorGain(DeviceContext device, UclaMiniscopeV4SensorGain gain) { - var des = device.Context.GetPassthroughDeviceContext(device.Address, typeof(DS90UB9x)); - - var atMega = new I2CRegisterContext(des, UclaMiniscopeV4.AtMegaAddress); + var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); WriteCameraRegister(atMega, 204, (uint)gain); } internal static void SetLiquidLensVoltage(DeviceContext device, double voltage) { - var des = device.Context.GetPassthroughDeviceContext(device.Address, typeof(DS90UB9x)); - - var max14574 = new I2CRegisterContext(des, UclaMiniscopeV4.Max14574Address); + var max14574 = new I2CRegisterContext(device, UclaMiniscopeV4.Max14574Address); max14574.WriteByte(0x08, (uint)((voltage - 24.4) / 0.0445) >> 2); max14574.WriteByte(0x09, 0x02); } @@ -218,19 +270,55 @@ public NameConverter() } } + /// + /// Specifies analog gain of the Python-480 image sensor on a UCLA Miniscope V4. + /// public enum UclaMiniscopeV4SensorGain { + /// + /// Specifies low gain. + /// Low = 0x00E1, + + /// + /// Specifies medium gain. + /// Medium = 0x00E4, + + /// + /// Specifies high gain. + /// High = 0x0024, } + /// + /// Specifies the video frame rate of the Python-480 image sensor on a UCLA Miniscope V4. + /// public enum UclaMiniscopeV4FramesPerSecond { + /// + /// Specifies 10 frames per second. + /// Fps10Hz, + + /// + /// Specifies 15 frames per second. + /// Fps15Hz, + + /// + /// Specifies 20 frames per second. + /// Fps20Hz, + + /// + /// Specifies 25 frames per second. + /// Fps25Hz, + + /// + /// Specifies 30 frames per second. + /// Fps30Hz, } } diff --git a/OpenEphys.Onix1/ContextTask.cs b/OpenEphys.Onix1/ContextTask.cs index 1cdc27c6..9b66b002 100644 --- a/OpenEphys.Onix1/ContextTask.cs +++ b/OpenEphys.Onix1/ContextTask.cs @@ -92,7 +92,7 @@ private void Initialize() DeviceTable = ctx.DeviceTable; } - private void Reset() + internal void Reset() { lock (disposeLock) lock (regLock) diff --git a/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs b/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs index 28b7955c..97958af4 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eBno055Data.cs @@ -23,8 +23,8 @@ public class NeuropixelsV2eBno055Data : Source /// /// A sequence of objects. /// - /// This will generate a sequence of objects at approximately 100 Hz. This rate - /// may be limited by the I2C bus. + /// This will generate a sequence of s at approximately 100 Hz. This rate + /// may be limited by the hardware I2C bus. /// public override IObservable Generate() { @@ -34,9 +34,16 @@ public override IObservable Generate() } /// - /// Generates a sequence of objects. + /// Generates a sequence of s. /// - /// A sequence of objects. + /// An input sequence that drives the production of s + /// A sequence of s. + /// + /// A will be produced each time an element is received from the + /// sequence. This rate is limited by the hardware I2C bus and has a + /// maximum of 100 Hz. + /// public unsafe IObservable Generate(IObservable source) { return DeviceManager.GetDevice(DeviceName).SelectMany( diff --git a/OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs b/OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs index a45cfaee..877467bb 100644 --- a/OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs +++ b/OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs @@ -6,11 +6,26 @@ namespace OpenEphys.Onix1 { + /// + /// Produces a sequence of s from the Bno055 9-axis inertial measurement unit + /// on a UCLA Miniscope V4. + /// public class UclaMiniscopeV4Bno055Data : Source { + /// [TypeConverter(typeof(UclaMiniscopeV4Bno055.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + [Category(DeviceFactory.ConfigurationCategory)] public string DeviceName { get; set; } + /// + /// Generates a sequence of s at approximately 100 Hz. + /// + /// A sequence of s. + /// + /// This will generate a sequence of s at approximately 100 Hz. This rate + /// may be limited by the hardware I2C bus. + /// public override IObservable Generate() { // Max of 100 Hz, but limited by I2C bus @@ -18,24 +33,35 @@ public override IObservable Generate() return Generate(source); } + /// + /// Generates a sequence of s. + /// + /// An input sequence that drives the production of s + /// A sequence of s. + /// + /// A will be produced each time an element is received from the + /// sequence. This rate is limited by the hardware I2C bus and has a + /// maximum of 100 Hz. + /// public unsafe IObservable Generate(IObservable source) { return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => { - return !((NeuropixelsV2eBno055DeviceInfo)deviceInfo).Enable + return !((UclaMiniscopeV4Bno055DeviceInfo)deviceInfo).Enable ? Observable.Empty() : Observable.Create(observer => { - var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2eBno055)); + var device = deviceInfo.GetDeviceContext(typeof(UclaMiniscopeV4Bno055)); var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); - var i2c = new I2CRegisterContext(passthrough, NeuropixelsV2eBno055.BNO055Address); + var i2c = new I2CRegisterContext(passthrough, UclaMiniscopeV4Bno055.BNO055Address); return source.SubscribeSafe(observer, _ => { Bno055DataFrame frame = default; device.Context.EnsureContext(() => { - var data = i2c.ReadBytes(NeuropixelsV2eBno055.DataAddress, sizeof(Bno055DataPayload)); + var data = i2c.ReadBytes(UclaMiniscopeV4Bno055.DataAddress, sizeof(Bno055DataPayload)); ulong clock = passthrough.ReadRegister(DS90UB9x.LASTI2CL); clock += (ulong)passthrough.ReadRegister(DS90UB9x.LASTI2CH) << 32; fixed (byte* dataPtr = data) diff --git a/OpenEphys.Onix1/UclaMiniscopeV4Camera.cs b/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs similarity index 72% rename from OpenEphys.Onix1/UclaMiniscopeV4Camera.cs rename to OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs index 15acbf1a..0de80814 100644 --- a/OpenEphys.Onix1/UclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs @@ -9,21 +9,32 @@ namespace OpenEphys.Onix1 { - public class UclaMiniscopeV4Camera : Source + /// + /// Produces a sequence of s from the Python-480 image sensor on a + /// UCLA Miniscope V4. + /// + public class UclaMiniscopeV4CameraData : Source { + /// [TypeConverter(typeof(UclaMiniscopeV4.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + [Category(DeviceFactory.ConfigurationCategory)] public string DeviceName { get; set; } - public unsafe override IObservable Generate() + /// + /// Generates a sequence of s at a rate determined by . + /// + /// A sequence of s + public unsafe override IObservable Generate() { return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => { - var info = (NeuropixelsV2eDeviceInfo)deviceInfo; - var device = info.GetDeviceContext(typeof(NeuropixelsV2e)); + var device = deviceInfo.GetDeviceContext(typeof(UclaMiniscopeV4)); var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); var scopeData = device.Context.GetDeviceFrames(passthrough.Address); - return Observable.Create(observer => + return Observable.Create(observer => { var sampleIndex = 0; var imageBuffer = new short[UclaMiniscopeV4.SensorRows * UclaMiniscopeV4.SensorColumns]; @@ -48,7 +59,7 @@ public unsafe override IObservable Generate() { var imageData = Mat.FromArray(imageBuffer, UclaMiniscopeV4.SensorRows, UclaMiniscopeV4.SensorColumns, Depth.U16, 1); CV.ConvertScale(imageData.GetRow(0), imageData.GetRow(0), 1.0f, -32768.0f); // Get rid first row's mark bit - observer.OnNext(new UclaMiniscopeV4Image(clockBuffer, hubClockBuffer, imageData.GetImage())); + observer.OnNext(new UclaMiniscopeV4CameraFrame(clockBuffer, hubClockBuffer, imageData.GetImage())); hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; sampleIndex = 0; diff --git a/OpenEphys.Onix1/UclaMiniscopeV4CameraFrame.cs b/OpenEphys.Onix1/UclaMiniscopeV4CameraFrame.cs new file mode 100644 index 00000000..5b3f2ca2 --- /dev/null +++ b/OpenEphys.Onix1/UclaMiniscopeV4CameraFrame.cs @@ -0,0 +1,35 @@ +using System.Runtime.InteropServices; +using OpenCV.Net; + +namespace OpenEphys.Onix1 +{ + /// + /// Image data produced by the Python-480 CMOS image sensor on a UCLA Miniscope V4. + /// + public class UclaMiniscopeV4CameraFrame : BufferedDataFrame + { + /// + /// Initializes a new instance of the class. + /// + /// An array of values. + /// An array of hub clock counter values. + /// A image produced by the Python-480 on a UCLA Miniscope V4. + public UclaMiniscopeV4CameraFrame(ulong[] clock, ulong[] hubClock, IplImage image) + : base (clock, hubClock) + { + Image = image; + } + + /// + /// Gets the 608x608 pixel, 10-bit, monochrome image. + /// + public IplImage Image { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + unsafe struct UclaMiniscopeV4ImagerPayload + { + public ulong HubClock; + public fixed short ImageRow[UclaMiniscopeV4.SensorColumns]; + } +} diff --git a/OpenEphys.Onix1/UclaMiniscopeV4Image.cs b/OpenEphys.Onix1/UclaMiniscopeV4Image.cs deleted file mode 100644 index d53d8df9..00000000 --- a/OpenEphys.Onix1/UclaMiniscopeV4Image.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Runtime.InteropServices; -using OpenCV.Net; - -namespace OpenEphys.Onix1 -{ - public class UclaMiniscopeV4Image - { - public UclaMiniscopeV4Image(ulong[] clock, ulong[] hubClock, IplImage image) - { - Clock = clock; - HubClock = hubClock; - Image = image; - } - - public ulong[] Clock { get; } - - public ulong[] HubClock { get; } - - public IplImage Image { get; } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - unsafe struct UclaMiniscopeV4ImagerPayload - { - public ulong HubClock; - public fixed short ImageRow[UclaMiniscopeV4.SensorColumns]; - } -} From 4f7ab7667a8952429b69f0d45110c5530fd58158 Mon Sep 17 00:00:00 2001 From: jonnew Date: Tue, 3 Sep 2024 14:09:41 -0400 Subject: [PATCH 08/15] Enable EWL driver in ConfigureUclaMiniscopeV4Camera - Before, the driver was never enabled. --- OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs index 5e99aff8..5be19dd2 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs @@ -86,7 +86,7 @@ public double LedBrightness /// /// /// The imaging focal plane is controlled by using a MAX14574 high-voltage liquid lens driver. This - /// chip produces pulse-width modulated, 1 kHz alternative electric field that deforms the miniscope's + /// chip produces pulse-width modulated, 5 kHz alternative electric field that deforms the miniscope's /// liquid lens in order to change the focal plane. The strength of this field determines the degree /// of deformation and therefore the focal depth. The default setting of 47 Volts RMS corresponds to /// approximately mid-range. @@ -204,8 +204,7 @@ internal static void ConfigureMiniscope(DeviceContext device) // turn on EWL var max14574 = new I2CRegisterContext(device, UclaMiniscopeV4.Max14574Address); - max14574.WriteByte(0x08, 0x7F); - max14574.WriteByte(0x09, 0x02); + max14574.WriteByte(0x03, 0x03); // turn on LED and setup Python480 var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); From 3bbba4ec6b25e356405c8f05ced09e818049da0b Mon Sep 17 00:00:00 2001 From: jonnew Date: Wed, 4 Sep 2024 13:21:47 -0400 Subject: [PATCH 09/15] Simplify check for first row in UclaMiniscopeV4Data - Use sample index instead of defining a second boolean variable --- OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs | 2 +- OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs | 4 ++-- OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs | 7 ++----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs index 74497aa9..0415da4a 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs @@ -135,7 +135,7 @@ override protected bool CheckLinkState(DeviceContext device) try { var ds90ub9x = device.Context.GetPassthroughDeviceContext(DeviceAddress << 8, typeof(DS90UB9x)); - ConfigureUclaMiniscopeV4Camera.ConfigureMiniscope(ds90ub9x); + ConfigureUclaMiniscopeV4Camera.ConfigureCameraSystem(ds90ub9x); } catch (oni.ONIException ex) { diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs index 5be19dd2..d2df8b84 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs @@ -140,7 +140,7 @@ public override IObservable Process(IObservable source device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); // configure deserializer, chip states, and camera PLL - ConfigureMiniscope(device); + ConfigureCameraSystem(device); // configuration properties var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); @@ -167,7 +167,7 @@ public override IObservable Process(IObservable source }); } - internal static void ConfigureMiniscope(DeviceContext device) + internal static void ConfigureCameraSystem(DeviceContext device) { // configure deserializer device.WriteRegister(DS90UB9x.TRIGGEROFF, 0); diff --git a/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs b/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs index 0de80814..3cb98032 100644 --- a/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs +++ b/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs @@ -40,7 +40,6 @@ public unsafe override IObservable Generate() var imageBuffer = new short[UclaMiniscopeV4.SensorRows * UclaMiniscopeV4.SensorColumns]; var hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; var clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; - var awaitingFrameStart = true; var frameObserver = Observer.Create( frame => @@ -48,10 +47,9 @@ public unsafe override IObservable Generate() var payload = (UclaMiniscopeV4ImagerPayload*)frame.Data.ToPointer(); // Wait for first row - if (awaitingFrameStart && (payload->ImageRow[0] & 0x8000) == 0) - return; + if (sampleIndex == 0 && (payload->ImageRow[0] & 0x8000) == 0) + return; - awaitingFrameStart = false; Marshal.Copy(new IntPtr(payload->ImageRow), imageBuffer, sampleIndex * UclaMiniscopeV4.SensorColumns, UclaMiniscopeV4.SensorColumns); hubClockBuffer[sampleIndex] = payload->HubClock; clockBuffer[sampleIndex] = frame.Clock; @@ -63,7 +61,6 @@ public unsafe override IObservable Generate() hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; sampleIndex = 0; - awaitingFrameStart = true; } }, observer.OnError, From 8a9ee203dd8f6a9b03db2eccf5af23ad0489c2a2 Mon Sep 17 00:00:00 2001 From: jonnew Date: Tue, 10 Sep 2024 17:41:30 -0400 Subject: [PATCH 10/15] Improve miniscope port locking logic - Allow for retries during the final camera setup because the scope is finicky --- OpenEphys.Onix1/ConfigurePortController.cs | 2 +- OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs | 44 +++++++++++++-------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/OpenEphys.Onix1/ConfigurePortController.cs b/OpenEphys.Onix1/ConfigurePortController.cs index f9587c56..faf60bc6 100644 --- a/OpenEphys.Onix1/ConfigurePortController.cs +++ b/OpenEphys.Onix1/ConfigurePortController.cs @@ -31,7 +31,7 @@ protected virtual bool CheckLinkState(DeviceContext device) protected abstract bool ConfigurePortVoltage(DeviceContext device); - private bool ConfigurePortVoltageOverride(DeviceContext device, double voltage) + protected virtual bool ConfigurePortVoltageOverride(DeviceContext device, double voltage) { device.WriteRegister(PortController.PORTVOLTAGE, (uint)(voltage * 10)); Thread.Sleep(500); diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs index 0415da4a..18dd0a07 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs @@ -103,55 +103,65 @@ class ConfigureUclaMiniscopeV4PortController : ConfigurePortController { protected override bool ConfigurePortVoltage(DeviceContext device) { - const uint MinVoltage = 50; - const uint MaxVoltage = 70; - const uint VoltageOffset = 02; - const uint VoltageIncrement = 02; + const double MinVoltage = 5.3; + const double MaxVoltage = 6.5; + const double VoltageOffset = 0.2; + const double VoltageIncrement = 0.1; - for (uint voltage = MinVoltage; voltage <= MaxVoltage; voltage += VoltageIncrement) + for (var voltage = MinVoltage; voltage <= MaxVoltage; voltage += VoltageIncrement) { SetPortVoltage(device, voltage); if (CheckLinkState(device)) { - SetPortVoltage(device, voltage + VoltageOffset); - return CheckLinkState(device); + return ConfigurePortVoltageOverride(device, voltage + VoltageOffset); } } return false; } - private void SetPortVoltage(DeviceContext device, uint voltage) + private void SetPortVoltage(DeviceContext device, double voltage) { const int WaitUntilVoltageSettles = 200; device.WriteRegister(PortController.PORTVOLTAGE, 0); Thread.Sleep(WaitUntilVoltageSettles); - device.WriteRegister(PortController.PORTVOLTAGE, voltage); + device.WriteRegister(PortController.PORTVOLTAGE, (uint)(10 * voltage)); Thread.Sleep(WaitUntilVoltageSettles); } override protected bool CheckLinkState(DeviceContext device) { + const int WaitUntilPllSettles = 200; + const int FailureToWriteRegister = -6; try { var ds90ub9x = device.Context.GetPassthroughDeviceContext(DeviceAddress << 8, typeof(DS90UB9x)); ConfigureUclaMiniscopeV4Camera.ConfigureCameraSystem(ds90ub9x); } - catch (oni.ONIException ex) + catch (oni.ONIException ex) when (ex.Number == FailureToWriteRegister) { - // this can occur if power is too low, so we need to be able to try again - const int FailureToWriteRegister = -6; - if (ex.Number != FailureToWriteRegister) - { - throw; - } + return false; } - Thread.Sleep(200); + Thread.Sleep(WaitUntilPllSettles); var linkState = device.ReadRegister(PortController.LINKSTATE); return (linkState & PortController.LINKSTATE_SL) != 0; } + + override protected bool ConfigurePortVoltageOverride(DeviceContext device, double voltage) + { + SetPortVoltage(device, voltage); + + const int TotalTries = 10; + for (int i = 0; i < TotalTries; i++) + { + if (CheckLinkState(device)) + return true; + } + + return false; + } } } } From ad0ae1261b252f2ab4579bfcaebc6a0bc5ef7eb4 Mon Sep 17 00:00:00 2001 From: jonnew Date: Wed, 18 Sep 2024 09:58:22 -0400 Subject: [PATCH 11/15] Add hacks to miniscope configuration to increase reliability - More retry logic - Provide port controller with reference to Camera object so that it can signal it has already performed configmration in its retry loop. This prevents the camera from trying, and ponentially failing, to configurre itself redundantly. It is _not_ and elegant way to do this. The real solution relates much more to known issues about passthrough device configuration logic and where that should be located --- OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs | 71 +++++++--- .../ConfigureUclaMiniscopeV4Bno055.cs | 4 +- .../ConfigureUclaMiniscopeV4Camera.cs | 127 +++++++++++------- 3 files changed, 132 insertions(+), 70 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs index 18dd0a07..cb522c97 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel; using System.Threading; +using oni; namespace OpenEphys.Onix1 { @@ -20,6 +22,9 @@ namespace OpenEphys.Onix1 /// public class ConfigureUclaMiniscopeV4 : MultiDeviceFactory { + + const double MaxVoltage = 5.6; + PortName port; readonly ConfigureUclaMiniscopeV4PortController PortControl = new(); @@ -64,6 +69,12 @@ public PortName Port PortControl.DeviceAddress = (uint)port; Camera.DeviceAddress = offset + 0; Bno055.DeviceAddress = offset + 1; + + // Hack: we configure the camera using the port controller below. configuration is super + // unreliable, so we do a bunch of retries in the logic PortController logic. We don't want to + // reperform configuration in the Camera object after this. So we capture a reference to the + // camera here in order to inform it we have already performed configuration. + PortControl.Camera = Camera; } } @@ -79,12 +90,12 @@ public PortName Port /// port voltage. /// /// - /// Warning: this device requires 5.0V to 6.0V, measured at the miniscope, for proper operation. Supplying higher + /// Warning: this device requires 4.0 to 5.0V, measured at the miniscope, for proper operation. Supplying higher /// voltages may result in damage. /// /// [Description("If defined, it will override automated voltage discovery and apply the specified voltage" + - "to the miniscope. Warning: this device requires 5.0V to 6.0V for proper operation." + + "to the miniscope. Warning: this device requires 4.0 to 5.0V, measured at the scope, for proper operation." + "Supplying higher voltages may result in damage to the miniscope.")] [Category(ConfigurationCategory)] public double? PortVoltage @@ -99,30 +110,37 @@ internal override IEnumerable GetDevices() yield return Camera; yield return Bno055; } + class ConfigureUclaMiniscopeV4PortController : ConfigurePortController { + internal ConfigureUclaMiniscopeV4Camera Camera; + protected override bool ConfigurePortVoltage(DeviceContext device) { - const double MinVoltage = 5.3; - const double MaxVoltage = 6.5; - const double VoltageOffset = 0.2; - const double VoltageIncrement = 0.1; + const double MinVoltage = 5.2; + const double VoltageIncrement = 0.05; for (var voltage = MinVoltage; voltage <= MaxVoltage; voltage += VoltageIncrement) { SetPortVoltage(device, voltage); - if (CheckLinkState(device)) + if (CheckLinkStateWithRetry(device)) { - return ConfigurePortVoltageOverride(device, voltage + VoltageOffset); + return true; } } return false; } - private void SetPortVoltage(DeviceContext device, double voltage) + void SetPortVoltage(DeviceContext device, double voltage) { - const int WaitUntilVoltageSettles = 200; + if (voltage > MaxVoltage) + { + throw new ArgumentException($"The port voltage must be set to a value less than {MaxVoltage} " + + $"volts to prevent damage to the miniscope."); + } + + const int WaitUntilVoltageSettles = 400; device.WriteRegister(PortController.PORTVOLTAGE, 0); Thread.Sleep(WaitUntilVoltageSettles); device.WriteRegister(PortController.PORTVOLTAGE, (uint)(10 * voltage)); @@ -131,32 +149,45 @@ private void SetPortVoltage(DeviceContext device, double voltage) override protected bool CheckLinkState(DeviceContext device) { - const int WaitUntilPllSettles = 200; + var ds90ub9x = device.Context.GetPassthroughDeviceContext(DeviceAddress << 8, typeof(DS90UB9x)); + ConfigureUclaMiniscopeV4Camera.ConfigureDeserializer(ds90ub9x); + const int FailureToWriteRegister = -6; try { - var ds90ub9x = device.Context.GetPassthroughDeviceContext(DeviceAddress << 8, typeof(DS90UB9x)); - ConfigureUclaMiniscopeV4Camera.ConfigureCameraSystem(ds90ub9x); + ConfigureUclaMiniscopeV4Camera.ConfigureCameraSystem(ds90ub9x, Camera.FrameRate, Camera.InterleaveLed); } - catch (oni.ONIException ex) when (ex.Number == FailureToWriteRegister) + catch (ONIException ex) when (ex.Number == FailureToWriteRegister) { return false; } - Thread.Sleep(WaitUntilPllSettles); - var linkState = device.ReadRegister(PortController.LINKSTATE); return (linkState & PortController.LINKSTATE_SL) != 0; } - override protected bool ConfigurePortVoltageOverride(DeviceContext device, double voltage) + bool CheckLinkStateWithRetry(DeviceContext device) { - SetPortVoltage(device, voltage); - const int TotalTries = 10; for (int i = 0; i < TotalTries; i++) { if (CheckLinkState(device)) + { + Camera.Configured = true; + return true; + } + } + + return false; + } + + override protected bool ConfigurePortVoltageOverride(DeviceContext device, double voltage) + { + const int TotalTries = 3; + for (int i = 0; i < TotalTries; i++) + { + SetPortVoltage(device, voltage); + if (CheckLinkStateWithRetry(device)) return true; } diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs index 230bf0c5..574761e1 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs @@ -63,10 +63,10 @@ static void ConfigureDeserializer(DeviceContext device) deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias4, alias); } + // TODO: This can fail, pretty randomly, during configuration causing the failure to write + // register and workflow to terminate. Not sure why, likely due to hardware design. static void ConfigureBno055(DeviceContext device) { - // setup BNO055 device - // TODO: Correct orientation var i2c = new I2CRegisterContext(device, UclaMiniscopeV4Bno055.BNO055Address); i2c.WriteByte(0x3E, 0x00); // Power mode normal i2c.WriteByte(0x07, 0x00); // Page ID address 0 diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs index d2df8b84..2ca1071f 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs @@ -3,6 +3,8 @@ using System.Drawing.Design; using System.Reactive.Disposables; using System.Reactive.Subjects; +using System.Threading; +using System.Xml.Serialization; using Bonsai; namespace OpenEphys.Onix1 @@ -102,6 +104,13 @@ public double LiquidLensVoltage set => liquidLensVoltage.OnNext(value); } + // This is a hack. The hardware is quite unreliable and requires special assistance in order to + // operate. We do a lot of retries to make this happen. Once we have successful configuration, redoing + // it can cause failure. Therefore, we allow ConfigureMiniscopeV4 to set this variable in order to + // skip redundant configuration in Process(). This is not a great solution. + [XmlIgnore] + internal bool Configured { get; set; } = false; + /// /// Configures the camera on a UCLA Miniscope V4. /// @@ -120,54 +129,55 @@ public override IObservable Process(IObservable source var enable = Enable; var deviceName = DeviceName; var deviceAddress = DeviceAddress; - uint shutterWidth = FrameRate switch - { - UclaMiniscopeV4FramesPerSecond.Fps10Hz => 10000, - UclaMiniscopeV4FramesPerSecond.Fps15Hz => 6667, - UclaMiniscopeV4FramesPerSecond.Fps20Hz => 5000, - UclaMiniscopeV4FramesPerSecond.Fps25Hz => 4000, - UclaMiniscopeV4FramesPerSecond.Fps30Hz => 3300, - _ => 3300 - }; - var interleaveLED = InterleaveLed; + var frameRate = FrameRate; + var interleaveLed = InterleaveLed; return source.ConfigureDevice(context => { - // configure device via the DS90UB9x deserializer device - var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); - - // TODO: early exit if false? - device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); - - // configure deserializer, chip states, and camera PLL - ConfigureCameraSystem(device); - - // configuration properties - var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); - atMega.WriteByte(0x04, (uint)(interleaveLED ? 0x00 : 0x03)); - WriteCameraRegister(atMega, 200, shutterWidth); - - var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); - var shutdown = Disposable.Create(() => + try { - // turn off EWL - var max14574 = new I2CRegisterContext(device, UclaMiniscopeV4.Max14574Address); - max14574.WriteByte(0x03, 0x00); - - // turn off LED - var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); - atMega.WriteByte(1, 0xFF); - }); - return new CompositeDisposable( - ledBrightness.Subscribe(value => SetLedBrightness(device, value)), - sensorGain.Subscribe(value => SetSensorGain(device, value)), - liquidLensVoltage.Subscribe(value => SetLiquidLensVoltage(device, value)), - DeviceManager.RegisterDevice(deviceName, deviceInfo), - shutdown); + // configure device via the DS90UB9x deserializer device + var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); + + // TODO: early exit if false? + device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); + + // check if configuration was already performed on this object + if (!Configured) + { + ConfigureDeserializer(device); + ConfigureCameraSystem(device, frameRate, interleaveLed); + } + + var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress); + var shutdown = Disposable.Create(() => + { + // turn off EWL + var max14574 = new I2CRegisterContext(device, UclaMiniscopeV4.Max14574Address); + max14574.WriteByte(0x03, 0x00); + + // turn off LED + var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); + atMega.WriteByte(1, 0xFF); + }); + return new CompositeDisposable( + ledBrightness.Subscribe(value => SetLedBrightness(device, value)), + sensorGain.Subscribe(value => SetSensorGain(device, value)), + liquidLensVoltage.Subscribe(value => SetLiquidLensVoltage(device, value)), + DeviceManager.RegisterDevice(deviceName, deviceInfo), + shutdown); + } + finally + { + // needs to be reconfigured past this point, cannot rely on putting this action in + // shutdown disposable since any exception before this function returns will not enforce + // this setting + Configured = false; + } }); } - internal static void ConfigureCameraSystem(DeviceContext device) + internal static void ConfigureDeserializer(DeviceContext device) { // configure deserializer device.WriteRegister(DS90UB9x.TRIGGEROFF, 0); @@ -196,6 +206,21 @@ internal static void ConfigureCameraSystem(DeviceContext device) i2cAlias = UclaMiniscopeV4.Max14574Address << 1; deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID3, i2cAlias); deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias3, i2cAlias); + } + + internal static void ConfigureCameraSystem(DeviceContext device, UclaMiniscopeV4FramesPerSecond frameRate, bool interleaveLed) + { + const int WaitUntilPllSettles = 200; + + // set up Python480 + var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); + WriteCameraRegister(atMega, 16, 3); // Turn on PLL + Thread.Sleep(WaitUntilPllSettles); + WriteCameraRegister(atMega, 32, 0x7007); // Turn on clock management + Thread.Sleep(WaitUntilPllSettles); + WriteCameraRegister(atMega, 199, 666); // Defines granularity (unit = 1/PLL clock) of exposure and reset_length + WriteCameraRegister(atMega, 200, 3300); // Set frame rate to 30 Hz + WriteCameraRegister(atMega, 201, 3000); // Set Exposure // set up potentiometer var tpl0102 = new I2CRegisterContext(device, UclaMiniscopeV4.Tpl0102Address); @@ -206,13 +231,19 @@ internal static void ConfigureCameraSystem(DeviceContext device) var max14574 = new I2CRegisterContext(device, UclaMiniscopeV4.Max14574Address); max14574.WriteByte(0x03, 0x03); - // turn on LED and setup Python480 - var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress); - WriteCameraRegister(atMega, 16, 3); // Turn on PLL - WriteCameraRegister(atMega, 32, 0x7007); // Turn on clock management - WriteCameraRegister(atMega, 199, 666); // Defines granularity (unit = 1/PLL clock) of exposure and reset_length - WriteCameraRegister(atMega, 200, 3300); // Set frame rate to 30 Hz - WriteCameraRegister(atMega, 201, 3000); // Set Exposure + // configuration properties + uint shutterWidth = frameRate switch + { + UclaMiniscopeV4FramesPerSecond.Fps10Hz => 10000, + UclaMiniscopeV4FramesPerSecond.Fps15Hz => 6667, + UclaMiniscopeV4FramesPerSecond.Fps20Hz => 5000, + UclaMiniscopeV4FramesPerSecond.Fps25Hz => 4000, + UclaMiniscopeV4FramesPerSecond.Fps30Hz => 3300, + _ => 3300 + }; + + atMega.WriteByte(0x04, (uint)(interleaveLed ? 0x00 : 0x03)); + WriteCameraRegister(atMega, 200, shutterWidth); } static void WriteCameraRegister(I2CRegisterContext i2c, uint register, uint value) From f13180de6010093c21c22a6d3be7f0eff3dfbcc0 Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 19 Sep 2024 15:39:03 -0400 Subject: [PATCH 12/15] Set I2C clock to 100 kHz for minscope configuration - It was previously unset and relying on whatever the state of the deserializer was previously --- OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs index 2ca1071f..bd783856 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Camera.cs @@ -190,8 +190,12 @@ internal static void ConfigureDeserializer(DeviceContext device) // acquisition. For this reason, the frame start needs to be marked. device.WriteRegister(DS90UB9x.MARK, (uint)DS90UB9xMarkMode.VsyncRising); - // configure deserializer I2C aliases + // set I2C clock rate to ~100 kHz var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR); + deserializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.SCLHIGH, 0x7A); + deserializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.SCLLOW, 0x7A); + + // configure deserializer I2C aliases uint coaxMode = 0x4 + (uint)DS90UB9xMode.Raw12BitLowFrequency; // 0x4 maintains coax mode deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.PortMode, coaxMode); From ace54517128b02f902ba0fba772d1b95a5db7bc4 Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 19 Sep 2024 15:55:28 -0400 Subject: [PATCH 13/15] Use PolledBno055 in UclaMiniscopev4 - Remove UclaMiniscopeV4-specific Bno055 - Add correct axis remapping --- OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs | 7 +- .../ConfigureUclaMiniscopeV4Bno055.cs | 104 ------------------ OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs | 82 -------------- 3 files changed, 5 insertions(+), 188 deletions(-) delete mode 100644 OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs delete mode 100644 OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs index cb522c97..b2cc0a38 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs @@ -48,8 +48,11 @@ public ConfigureUclaMiniscopeV4() /// Gets or sets the Bno055 9-axis inertial measurement unit configuration. /// [Category(DevicesCategory)] - [TypeConverter(typeof(SingleDeviceFactoryConverter))] - public ConfigureUclaMiniscopeV4Bno055 Bno055 { get; set; } = new(); + [TypeConverter(typeof(PolledBno055SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the Bno055 device.")] + public ConfigurePolledBno055 Bno055 { get; set; } = + new ConfigurePolledBno055 { AxisMap = Bno055AxisMap.ZYX, AxisSign = Bno055AxisSign.MirrorX | Bno055AxisSign.MirrorY | Bno055AxisSign.MirrorZ }; + /// /// Gets or sets the port. diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs deleted file mode 100644 index 574761e1..00000000 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4Bno055.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.ComponentModel; - -namespace OpenEphys.Onix1 -{ - /// - /// Configures the Bno055 inertial measurement unit (IMU) on a UCLA Miniscope V4. - /// - public class ConfigureUclaMiniscopeV4Bno055 : SingleDeviceFactory - { - /// - /// Initialize a new instance of a class. - /// - public ConfigureUclaMiniscopeV4Bno055() - : base(typeof(UclaMiniscopeV4Bno055)) - { - } - - /// - /// Gets or sets the device enable state. - /// - /// - /// If set to true, will produce data. If set to false, - /// will not produce data. - /// - [Category(ConfigurationCategory)] - [Description("Specifies whether the BNO055 device is enabled.")] - public bool Enable { get; set; } = true; - - /// - /// Configures the Bno055 inertial measurement unit (IMU) on a UCLA Miniscope V4. - /// - /// - /// This will schedule configuration actions to be applied by a node - /// prior to data acquisition. - /// - /// A sequence of instances that holds all configuration actions. - /// - /// The original sequence but with each instance now containing configuration actions required to use the miniscope's Bno055 IMU. - /// - public override IObservable Process(IObservable source) - { - var enable = Enable; - var deviceName = DeviceName; - var deviceAddress = DeviceAddress; - return source.ConfigureDevice(context => - { - // configure device via the DS90UB9x deserializer device - var device = context.GetPassthroughDeviceContext(deviceAddress, typeof(DS90UB9x)); - ConfigureDeserializer(device); - ConfigureBno055(device); - var deviceInfo = new UclaMiniscopeV4Bno055DeviceInfo(context, DeviceType, deviceAddress, enable); - return DeviceManager.RegisterDevice(deviceName, deviceInfo); - }); - } - - static void ConfigureDeserializer(DeviceContext device) - { - // configure deserializer I2C aliases - var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR); - uint alias = UclaMiniscopeV4Bno055.BNO055Address << 1; - deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID4, alias); - deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias4, alias); - } - - // TODO: This can fail, pretty randomly, during configuration causing the failure to write - // register and workflow to terminate. Not sure why, likely due to hardware design. - static void ConfigureBno055(DeviceContext device) - { - var i2c = new I2CRegisterContext(device, UclaMiniscopeV4Bno055.BNO055Address); - i2c.WriteByte(0x3E, 0x00); // Power mode normal - i2c.WriteByte(0x07, 0x00); // Page ID address 0 - i2c.WriteByte(0x3F, 0x00); // Internal oscillator - i2c.WriteByte(0x41, 0b00000110); // Axis map config (configured to match hs64; X => Z, Y => -Y, Z => X) - i2c.WriteByte(0x42, 0b000000010); // Axis sign (negate Y) - i2c.WriteByte(0x3D, 8); // Operation mode is NOF - } - } - - static class UclaMiniscopeV4Bno055 - { - public const int BNO055Address = 0x28; - public const int DataAddress = 0x1A; - - internal class NameConverter : DeviceNameConverter - { - public NameConverter() - : base(typeof(UclaMiniscopeV4Bno055)) - { - } - } - } - - class UclaMiniscopeV4Bno055DeviceInfo : DeviceInfo - { - public UclaMiniscopeV4Bno055DeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, bool enable) - : base(context, deviceType, deviceAddress) - { - Enable = enable; - } - - public bool Enable { get; } - } -} diff --git a/OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs b/OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs deleted file mode 100644 index 877467bb..00000000 --- a/OpenEphys.Onix1/UclaMiniscopeV4Bno055Data.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai; - -namespace OpenEphys.Onix1 -{ - /// - /// Produces a sequence of s from the Bno055 9-axis inertial measurement unit - /// on a UCLA Miniscope V4. - /// - public class UclaMiniscopeV4Bno055Data : Source - { - /// - [TypeConverter(typeof(UclaMiniscopeV4Bno055.NameConverter))] - [Description(SingleDeviceFactory.DeviceNameDescription)] - [Category(DeviceFactory.ConfigurationCategory)] - public string DeviceName { get; set; } - - /// - /// Generates a sequence of s at approximately 100 Hz. - /// - /// A sequence of s. - /// - /// This will generate a sequence of s at approximately 100 Hz. This rate - /// may be limited by the hardware I2C bus. - /// - public override IObservable Generate() - { - // Max of 100 Hz, but limited by I2C bus - var source = Observable.Interval(TimeSpan.FromSeconds(0.01)); - return Generate(source); - } - - /// - /// Generates a sequence of s. - /// - /// An input sequence that drives the production of s - /// A sequence of s. - /// - /// A will be produced each time an element is received from the - /// sequence. This rate is limited by the hardware I2C bus and has a - /// maximum of 100 Hz. - /// - public unsafe IObservable Generate(IObservable source) - { - return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => - { - return !((UclaMiniscopeV4Bno055DeviceInfo)deviceInfo).Enable - ? Observable.Empty() - : Observable.Create(observer => - { - var device = deviceInfo.GetDeviceContext(typeof(UclaMiniscopeV4Bno055)); - var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); - var i2c = new I2CRegisterContext(passthrough, UclaMiniscopeV4Bno055.BNO055Address); - - return source.SubscribeSafe(observer, _ => - { - Bno055DataFrame frame = default; - device.Context.EnsureContext(() => - { - var data = i2c.ReadBytes(UclaMiniscopeV4Bno055.DataAddress, sizeof(Bno055DataPayload)); - ulong clock = passthrough.ReadRegister(DS90UB9x.LASTI2CL); - clock += (ulong)passthrough.ReadRegister(DS90UB9x.LASTI2CH) << 32; - fixed (byte* dataPtr = data) - { - frame = new Bno055DataFrame(clock, (Bno055DataPayload*)dataPtr); - } - }); - - if (frame != null) - { - observer.OnNext(frame); - } - }); - }); - }); - } - } -} From d9915108cb43dcbfda9b9e7b90222d093ba5f231 Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 19 Sep 2024 18:57:25 -0400 Subject: [PATCH 14/15] Add 8-bit or 10-bit image format options to UclaMiniscopeV4Data - 8-bit data is much easier to save, especially in bonsai - 10-bit is available if people want it. --- OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs | 59 ++++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs b/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs index 3cb98032..7baaabbc 100644 --- a/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs +++ b/OpenEphys.Onix1/UclaMiniscopeV4CameraData.cs @@ -21,6 +21,21 @@ public class UclaMiniscopeV4CameraData : Source [Category(DeviceFactory.ConfigurationCategory)] public string DeviceName { get; set; } + /// + /// Gets or sets the data type used to represent pixel intensity values. + /// + /// + /// The UCLA Miniscope V4 uses a 10-bit image sensor. To capture images that use the full + /// ADC resolution, this value can be set to . + /// This comes at the cost of limited codec support and larger file sizes. If is selected, the two least significant bits of + /// each pixel sample will be discarded, which greatly increases codec options and reduces + /// file sizes. + /// + [Description("The bit-depth used to represent pixel intensity values.")] + [Category(DeviceFactory.ConfigurationCategory)] + public UclaMiniscopeV4ImageDepth DataType { get; set; } = UclaMiniscopeV4ImageDepth.U8; + /// /// Generates a sequence of s at a rate determined by . @@ -33,13 +48,15 @@ public unsafe override IObservable Generate() var device = deviceInfo.GetDeviceContext(typeof(UclaMiniscopeV4)); var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); var scopeData = device.Context.GetDeviceFrames(passthrough.Address); - + var dataType = DataType; + return Observable.Create(observer => { var sampleIndex = 0; var imageBuffer = new short[UclaMiniscopeV4.SensorRows * UclaMiniscopeV4.SensorColumns]; var hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; var clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; + var sampleRect = new Rect(0, 1, UclaMiniscopeV4.SensorColumns, UclaMiniscopeV4.SensorRows - 1); var frameObserver = Observer.Create( frame => @@ -48,16 +65,33 @@ public unsafe override IObservable Generate() // Wait for first row if (sampleIndex == 0 && (payload->ImageRow[0] & 0x8000) == 0) - return; + return; Marshal.Copy(new IntPtr(payload->ImageRow), imageBuffer, sampleIndex * UclaMiniscopeV4.SensorColumns, UclaMiniscopeV4.SensorColumns); hubClockBuffer[sampleIndex] = payload->HubClock; clockBuffer[sampleIndex] = frame.Clock; if (++sampleIndex >= UclaMiniscopeV4.SensorRows) { - var imageData = Mat.FromArray(imageBuffer, UclaMiniscopeV4.SensorRows, UclaMiniscopeV4.SensorColumns, Depth.U16, 1); - CV.ConvertScale(imageData.GetRow(0), imageData.GetRow(0), 1.0f, -32768.0f); // Get rid first row's mark bit - observer.OnNext(new UclaMiniscopeV4CameraFrame(clockBuffer, hubClockBuffer, imageData.GetImage())); + + var imageData = Mat.FromArray(imageBuffer, UclaMiniscopeV4.SensorRows, UclaMiniscopeV4.SensorColumns, Depth.U16, 1); + CV.ConvertScale(imageData.GetRow(0), imageData.GetRow(0), 1.0f, -32768f); // Get rid first row's mark bit + + switch (dataType) + { + case UclaMiniscopeV4ImageDepth.U8: + { + var eightBitImageData = new Mat(imageData.Size, Depth.U8, 1); + CV.ConvertScale(imageData, eightBitImageData, 0.25); + observer.OnNext(new UclaMiniscopeV4CameraFrame(clockBuffer, hubClockBuffer, eightBitImageData.GetImage())); + break; + } + case UclaMiniscopeV4ImageDepth.U10: + { + observer.OnNext(new UclaMiniscopeV4CameraFrame(clockBuffer, hubClockBuffer, imageData.GetImage())); + break; + } + } + hubClockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; clockBuffer = new ulong[UclaMiniscopeV4.SensorRows]; sampleIndex = 0; @@ -70,5 +104,20 @@ public unsafe override IObservable Generate() }); }); } + + /// + /// Specifies the bit-depth used to represent pixel intensity values. + /// + public enum UclaMiniscopeV4ImageDepth + { + /// + /// 8-bit pixel values encoded as bytes. + /// + U8, + /// + /// 10-bit pixel values encoded as unsigned 16-bit integers + /// + U10 + } } } From 45e01d93b14d29abf79f484c35af0678b64fb833 Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 19 Sep 2024 19:04:38 -0400 Subject: [PATCH 15/15] Address review comments --- OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs index b2cc0a38..6ff9ba76 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs @@ -10,7 +10,7 @@ namespace OpenEphys.Onix1 /// Configures a UCLA Miniscope V4 on the specified port. /// /// - /// The UCLA Miniscope V4 is a e miniaturized fluorescent microscope for performing single-photon calcium + /// The UCLA Miniscope V4 is a miniaturized fluorescent microscope for performing single-photon calcium /// imaging in freely moving animals. It has the following features: /// /// A Python-480 0.48 Megapixel CMOS image sensor. @@ -97,8 +97,8 @@ public PortName Port /// voltages may result in damage. /// /// - [Description("If defined, it will override automated voltage discovery and apply the specified voltage" + - "to the miniscope. Warning: this device requires 4.0 to 5.0V, measured at the scope, for proper operation." + + [Description("If defined, it will override automated voltage discovery and apply the specified voltage " + + "to the miniscope. Warning: this device requires 4.0 to 5.0V, measured at the scope, for proper operation. " + "Supplying higher voltages may result in damage to the miniscope.")] [Category(ConfigurationCategory)] public double? PortVoltage