From 4110ce0ab2498d8d68b4b12052bee7930de57554 Mon Sep 17 00:00:00 2001 From: jonnew Date: Wed, 14 Aug 2024 12:57:25 -0400 Subject: [PATCH 1/3] Modularize API and code review - Break the classes contained with the large ProbeInterface class into standalone files - Remove autogenated backing fields and cruft - Some sytanx changes and simplifications --- OpenEphys.ProbeInterface.NET.sln | 2 +- OpenEphys.ProbeInterface.NET/Contact.cs | 72 ++ .../ContactAnnotations.cs | 25 + OpenEphys.ProbeInterface.NET/ContactShape.cs | 31 + .../ContactShapeParam.cs | 63 + OpenEphys.ProbeInterface.NET/Probe.cs | 334 ++++++ .../ProbeAnnotations.cs | 44 + OpenEphys.ProbeInterface.NET/ProbeGroup.cs | 331 ++++++ .../ProbeInterface.cs | 1056 ----------------- OpenEphys.ProbeInterface.NET/ProbeNdim.cs | 22 + OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs | 25 + 11 files changed, 948 insertions(+), 1057 deletions(-) create mode 100644 OpenEphys.ProbeInterface.NET/Contact.cs create mode 100644 OpenEphys.ProbeInterface.NET/ContactAnnotations.cs create mode 100644 OpenEphys.ProbeInterface.NET/ContactShape.cs create mode 100644 OpenEphys.ProbeInterface.NET/ContactShapeParam.cs create mode 100644 OpenEphys.ProbeInterface.NET/Probe.cs create mode 100644 OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs create mode 100644 OpenEphys.ProbeInterface.NET/ProbeGroup.cs delete mode 100644 OpenEphys.ProbeInterface.NET/ProbeInterface.cs create mode 100644 OpenEphys.ProbeInterface.NET/ProbeNdim.cs create mode 100644 OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs diff --git a/OpenEphys.ProbeInterface.NET.sln b/OpenEphys.ProbeInterface.NET.sln index db2d59d..6a525bb 100644 --- a/OpenEphys.ProbeInterface.NET.sln +++ b/OpenEphys.ProbeInterface.NET.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34511.84 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenEphys.ProbeInterface.NET", "OpenEphys.ProbeInterface.NET\OpenEphys.ProbeInterface.NET.csproj", "{822F3536-A4B7-4FE4-8332-A75A8458EE56}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenEphys.ProbeInterface.NET", "OpenEphys.ProbeInterface.NET\OpenEphys.ProbeInterface.NET.csproj", "{822F3536-A4B7-4FE4-8332-A75A8458EE56}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F8644FAC-94E5-4E73-B809-925ABABE35B1}" ProjectSection(SolutionItems) = preProject diff --git a/OpenEphys.ProbeInterface.NET/Contact.cs b/OpenEphys.ProbeInterface.NET/Contact.cs new file mode 100644 index 0000000..348d37c --- /dev/null +++ b/OpenEphys.ProbeInterface.NET/Contact.cs @@ -0,0 +1,72 @@ +namespace OpenEphys.ProbeInterface +{ + /// + /// Struct that extends the Probeinterface specification by encapsulating all values for a single contact. + /// + public readonly struct Contact + { + /// + /// Gets the x-position of the contact. + /// + public float PosX { get; } + + /// + /// Gets the y-position of the contact. + /// + public float PosY { get; } + + /// + /// Gets the of the contact. + /// + public ContactShape Shape { get; } + + /// + /// Gets the 's of the contact. + /// + public ContactShapeParam ShapeParams { get; } + + /// + /// Gets the device ID of the contact. + /// + public int DeviceId { get; } + + /// + /// Gets the contact ID of the contact. + /// + public string ContactId { get; } + + /// + /// Gets the shank ID of the contact. + /// + public string ShankId { get; } + + /// + /// Gets the index of the contact within the object. + /// + public int Index { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Contact(float posX, float posY, ContactShape shape, ContactShapeParam shapeParam, + int device_id, string contact_id, string shank_id, int index) + { + PosX = posX; + PosY = posY; + Shape = shape; + ShapeParams = shapeParam; + DeviceId = device_id; + ContactId = contact_id; + ShankId = shank_id; + Index = index; + } + } +} diff --git a/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs b/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs new file mode 100644 index 0000000..4f9fbca --- /dev/null +++ b/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace OpenEphys.ProbeInterface +{ + /// + /// Class holding all of the annotations for each contact. + /// + public class ContactAnnotations + { + /// + /// Gets the array of strings holding annotations for each contact. Not all indices must have annotations. + /// + public string[] Annotations { get; protected set; } + + /// + /// Initializes a new instance of the class. + /// + /// + [JsonConstructor] + public ContactAnnotations(string[] contactAnnotations) + { + Annotations = contactAnnotations; + } + } +} diff --git a/OpenEphys.ProbeInterface.NET/ContactShape.cs b/OpenEphys.ProbeInterface.NET/ContactShape.cs new file mode 100644 index 0000000..85ad399 --- /dev/null +++ b/OpenEphys.ProbeInterface.NET/ContactShape.cs @@ -0,0 +1,31 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace OpenEphys.ProbeInterface +{ + /// + /// Shape of the contact. + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum ContactShape + { + /// + /// Circle. + /// + [EnumMember(Value = "circle")] + Circle = 0, + + /// + /// Rectangle. + /// + [EnumMember(Value = "rect")] + Rect = 1, + + /// + /// Square. + /// + [EnumMember(Value = "square")] + Square = 2, + } +} diff --git a/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs b/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs new file mode 100644 index 0000000..6f29e6a --- /dev/null +++ b/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json; + +namespace OpenEphys.ProbeInterface +{ + /// + /// Class holding parameters used to draw the contact. + /// + /// + /// Fields are nullable, since not all fields are required depending on the shape selected. + /// + public class ContactShapeParam + { + /// + /// Gets the radius of the contact. + /// + /// + /// This is only used to draw contacts. Field can be null. + /// + public float? Radius { get; protected set; } + + /// + /// Gets the width of the contact. + /// + /// + /// This is used to draw or contacts. + /// Field can be null. + /// + public float? Width { get; protected set; } + + /// + /// Gets the height of the contact. + /// + /// + /// This is only used to draw contacts. Field can be null. + /// + public float? Height { get; protected set; } + + /// + /// Initializes a new instance of the class. + /// + /// Radius. Can be null. + /// Width. Can be null. + /// Height. Can be null. + [JsonConstructor] + public ContactShapeParam(float? radius = null, float? width = null, float? height = null) + { + Radius = radius; + Width = width; + Height = height; + } + + /// + /// Copy constructor given an existing object. + /// + /// + protected ContactShapeParam(ContactShapeParam shape) + { + Radius = shape.Radius; + Width = shape.Width; + Height = shape.Height; + } + } +} diff --git a/OpenEphys.ProbeInterface.NET/Probe.cs b/OpenEphys.ProbeInterface.NET/Probe.cs new file mode 100644 index 0000000..78b4f56 --- /dev/null +++ b/OpenEphys.ProbeInterface.NET/Probe.cs @@ -0,0 +1,334 @@ +using System.Xml.Serialization; +using System.CodeDom.Compiler; +using Newtonsoft.Json; + +namespace OpenEphys.ProbeInterface +{ + /// + /// Class that implements the Probe Interface specification for a Probe. + /// + public class Probe + { + + /// + /// Gets the to use while plotting the . + /// + [XmlIgnore] + [JsonProperty("ndim", Required = Required.Always)] + public ProbeNdim NumDimensions { get; protected set; } + + /// + /// Gets the to use while plotting the . + /// + [XmlIgnore] + [JsonProperty("si_units", Required = Required.Always)] + public ProbeSiUnits SiUnits { get; protected set; } + + /// + /// Gets the for the . + /// + /// + /// Used to specify the name of the probe, and the manufacturer. + /// + [XmlIgnore] + [JsonProperty("annotations", Required = Required.Always)] + public ProbeAnnotations Annotations { get; protected set; } + + /// + /// Gets the for the . + /// + /// + /// This field can be used for noting things like where it physically is within a specimen, or if it + /// is no longer functioning correctly. + /// + [XmlIgnore] + [JsonProperty("contact_annotations")] + public ContactAnnotations ContactAnnotations { get; protected set; } + + /// + /// Gets the positions, specifically the center point of every contact. + /// + /// + /// This is a two-dimensional array of floats; the first index is the index of the contact, and + /// the second index is the X and Y value, respectively. + /// + [XmlIgnore] + [JsonProperty("contact_positions", Required = Required.Always)] + public float[][] ContactPositions { get; protected set; } + + /// + /// Gets the plane axes for the contacts. + /// + [XmlIgnore] + [JsonProperty("contact_plane_axes")] + public float[][][] ContactPlaneAxes { get; protected set; } + + + /// + /// Gets the for each contact. + /// + [XmlIgnore] + [JsonProperty("contact_shapes", Required = Required.Always)] + public ContactShape[] ContactShapes { get; protected set; } + + /// + /// Gets the parameters of the shape for each contact. + /// + /// + /// Depending on which + /// is selected, not all parameters are needed; for instance, only uses + /// , while just uses + /// . + /// + [XmlIgnore] + [JsonProperty("contact_shape_params", Required = Required.Always)] + public ContactShapeParam[] ContactShapeParams { get; protected set; } + + + /// + /// Gets the outline of the probe that represents the physical shape. + /// + [XmlIgnore] + [JsonProperty("probe_planar_contour")] + public float[][] ProbePlanarContour { get; protected set; } + + /// + /// Gets the indices of each channel defining their recording channel number. Must be unique, except for contacts + /// that are set to -1 if they disabled. + /// + [XmlIgnore] + [JsonProperty("device_channel_indices")] + public int[] DeviceChannelIndices { get; internal set; } + + /// + /// Gets the contact IDs for each channel. These do not have to be unique. + /// + [XmlIgnore] + [JsonProperty("contact_ids")] + public string[] ContactIds { get; internal set; } + + /// + /// Gets the shank that each contact belongs to. + /// + [XmlIgnore] + [JsonProperty("shank_ids")] + public string[] ShankIds { get; internal set; } + + + /// + /// Public constructor, defined as the default Json constructor. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + [JsonConstructor] + public Probe(ProbeNdim ndim, ProbeSiUnits si_units, ProbeAnnotations annotations, ContactAnnotations contact_annotations, + float[][] contact_positions, float[][][] contact_plane_axes, ContactShape[] contact_shapes, + ContactShapeParam[] contact_shape_params, float[][] probe_planar_contour, int[] device_channel_indices, + string[] contact_ids, string[] shank_ids) + { + NumDimensions = ndim; + SiUnits = si_units; + Annotations = annotations; + ContactAnnotations = contact_annotations; + ContactPositions = contact_positions; + ContactPlaneAxes = contact_plane_axes; + ContactShapes = contact_shapes; + ContactShapeParams = contact_shape_params; + ProbePlanarContour = probe_planar_contour; + DeviceChannelIndices = device_channel_indices; + ContactIds = contact_ids; + ShankIds = shank_ids; + } + + /// + /// Copy constructor given an existing object. + /// + /// + protected Probe(Probe probe) + { + NumDimensions = probe.NumDimensions; + SiUnits = probe.SiUnits; + Annotations = probe.Annotations; + ContactAnnotations = probe.ContactAnnotations; + ContactPositions = probe.ContactPositions; + ContactPlaneAxes = probe.ContactPlaneAxes; + ContactShapes = probe.ContactShapes; + ContactShapeParams = probe.ContactShapeParams; + ProbePlanarContour = probe.ProbePlanarContour; + DeviceChannelIndices = probe.DeviceChannelIndices; + ContactIds = probe.ContactIds; + ShankIds = probe.ShankIds; + } + + /// + /// Returns default array that contains the given number of channels and the corresponding shape. + /// + /// Number of contacts in a single . + /// The to apply to each contact. + /// array. + public static ContactShape[] DefaultContactShapes(int numberOfContacts, ContactShape contactShape) + { + ContactShape[] contactShapes = new ContactShape[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + contactShapes[i] = contactShape; + } + + return contactShapes; + } + + /// + /// Returns a default contactPlaneAxes array, with each contact given the same axis; { { 1, 0 }, { 0, 1 } } + /// + /// + /// See Probeinterface documentation for more info. + /// + /// Number of contacts in a single . + /// Three-dimensional array of s. + public static float[][][] DefaultContactPlaneAxes(int numberOfContacts) + { + float[][][] contactPlaneAxes = new float[numberOfContacts][][]; + + for (int i = 0; i < numberOfContacts; i++) + { + contactPlaneAxes[i] = new float[2][] { new float[2] { 1.0f, 0.0f }, new float[2] { 0.0f, 1.0f } }; + } + + return contactPlaneAxes; + } + + /// + /// Returns an array of s for a . + /// + /// Number of contacts in a single . + /// Radius of the contact, in units of . + /// array. + public static ContactShapeParam[] DefaultCircleParams(int numberOfContacts, float radius) + { + ContactShapeParam[] contactShapeParams = new ContactShapeParam[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + contactShapeParams[i] = new ContactShapeParam(radius: radius); + } + + return contactShapeParams; + } + + /// + /// Returns an array of s for a . + /// + /// Number of contacts in a single . + /// Width of the contact, in units of . + /// array. + public static ContactShapeParam[] DefaultSquareParams(int numberOfContacts, float width) + { + ContactShapeParam[] contactShapeParams = new ContactShapeParam[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + contactShapeParams[i] = new ContactShapeParam(width: width); + } + + return contactShapeParams; + } + + /// + /// Returns an array of s for a . + /// + /// Number of contacts in a single . + /// Width of the contact, in units of . + /// Height of the contact, in units of . + /// array. + public static ContactShapeParam[] DefaultRectParams(int numberOfContacts, float width, float height) + { + ContactShapeParam[] contactShapeParams = new ContactShapeParam[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + contactShapeParams[i] = new ContactShapeParam(height: height); + } + + return contactShapeParams; + } + + /// + /// Returns a default array of sequential . + /// + /// Number of contacts in a single . + /// The first value of the . + /// A serially increasing array of . + public static int[] DefaultDeviceChannelIndices(int numberOfContacts, int offset) + { + int[] deviceChannelIndices = new int[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + deviceChannelIndices[i] = i + offset; + } + + return deviceChannelIndices; + } + + /// + /// Returns a sequential array of . + /// + /// Number of contacts in a single . + /// Array of strings defining the . + public static string[] DefaultContactIds(int numberOfContacts) + { + string[] contactIds = new string[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + contactIds[i] = i.ToString(); + } + + return contactIds; + } + + /// + /// Returns an array of empty strings as the default shank ID. + /// + /// Number of contacts in a single . + /// Array of empty strings. + public static string[] DefaultShankIds(int numberOfContacts) + { + string[] contactIds = new string[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + contactIds[i] = ""; + } + + return contactIds; + } + + /// + /// Returns a object. + /// + /// Relative index of the contact in this . + /// . + public Contact GetContact(int index) + { + return new Contact(ContactPositions[index][0], ContactPositions[index][1], ContactShapes[index], ContactShapeParams[index], + DeviceChannelIndices[index], ContactIds[index], ShankIds[index], index); + } + + /// + /// Gets the number of contacts within this . + /// + public int NumberOfContacts => ContactPositions.Length; + } +} diff --git a/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs b/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs new file mode 100644 index 0000000..24746c6 --- /dev/null +++ b/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; + +namespace OpenEphys.ProbeInterface +{ + /// + /// Class holding the annotations. + /// + public class ProbeAnnotations + { + /// + /// Gets the name of the probe as defined by the manufacturer, or a descriptive name such as the neurological target. + /// + [JsonProperty("name")] + public string Name { get; protected set; } + + /// + /// Gets the name of the manufacturer who created the probe. + /// + [JsonProperty("manufacturer")] + public string Manufacturer { get; protected set; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + [JsonConstructor] + public ProbeAnnotations(string name, string manufacturer) + { + Name = name; + Manufacturer = manufacturer; + } + + /// + /// Copy constructor that copies data from an existing object. + /// + /// + protected ProbeAnnotations(ProbeAnnotations probeAnnotations) + { + Name = probeAnnotations.Name; + Manufacturer = probeAnnotations.Manufacturer; + } + } +} diff --git a/OpenEphys.ProbeInterface.NET/ProbeGroup.cs b/OpenEphys.ProbeInterface.NET/ProbeGroup.cs new file mode 100644 index 0000000..b478e83 --- /dev/null +++ b/OpenEphys.ProbeInterface.NET/ProbeGroup.cs @@ -0,0 +1,331 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Serialization; +using Newtonsoft.Json; +using System; + +namespace OpenEphys.ProbeInterface +{ + /// + /// Abstract class that implements the Probeinterface specification in C# for .NET. + /// + public abstract class ProbeGroup + { + //private IEnumerable Probes; + + /// + /// Gets the string defining the specification of the file. + /// + /// + /// For Probeinterface files, this value is expected to be "probeinterface". + /// + [JsonProperty("specification", Required = Required.Always)] + public string Specification { get; protected set; } + + /// + /// Gets the string defining which version of Probeinterface was used. + /// + [JsonProperty("version", Required = Required.Always)] + public string Version { get; protected set; } + + /// + /// Gets an IEnumerable of probes that are present. + /// + /// + /// Each probe can contain multiple shanks, and each probe has a unique + /// contour that defines the physical representation of the probe. Contacts have several representations + /// for their channel number, specifically (a string that is not guaranteed to be unique) and + /// (guaranteed to be unique across all probes). 's can also be set to -1 + /// to indicate that the channel was not connected or recorded from. + /// + [XmlIgnore] + [JsonProperty("probes", Required = Required.Always)] + public IEnumerable Probes { get; protected set; } + + + /// + /// Initializes a new instance of the class. + /// + /// String defining the parameter. + /// String defining the parameter. + /// IEnumerable of objects. + public ProbeGroup(string specification, string version, IEnumerable probes) + { + Specification = specification; + Version = version; + Probes = probes; + + Validate(); + } + + /// + /// Copy constructor that takes in an existing object and copies the individual fields. + /// + /// + /// After copying the relevant fields, the is validated to ensure that it is compliant + /// with the Probeinterface specification. See for more details on what is checked. + /// + /// Existing object. + protected ProbeGroup(ProbeGroup probeGroup) + { + Specification = probeGroup.Specification; + Version = probeGroup.Version; + Probes = probeGroup.Probes; + + Validate(); + } + + /// + /// Gets the number of contacts across all objects. + /// + public int NumberOfContacts => Probes.Aggregate(0, (total, next) => total + next.NumberOfContacts); + + + /// + /// Returns the 's of all contacts in all probes. + /// + /// + /// Note that these are not guaranteed to be unique values across probes. + /// + /// List of strings containing all contact IDs. + public IEnumerable GetContactIds() + { + List contactIds = new(); + + foreach (var probe in Probes) + { + contactIds.AddRange(probe.ContactIds.ToList()); + } + + return contactIds; + } + + /// + /// Returns all objects in the . + /// + /// + public List GetContacts() + { + List contacts = new(); + + foreach (var p in Probes) + { + for (int i = 0; i < p.NumberOfContacts; i++) + { + contacts.Add(p.GetContact(i)); + } + } + + return contacts; + } + + /// + /// Returns all 's in the . + /// + /// + /// Device channel indices are guaranteed to be unique, unless they are -1. Multiple contacts can be + /// set to -1 to indicate they are not recorded from. + /// + /// + public IEnumerable GetDeviceChannelIndices() + { + List deviceChannelIndices = new(); + + foreach (var probe in Probes) + { + deviceChannelIndices.AddRange(probe.DeviceChannelIndices.ToList()); + } + + return deviceChannelIndices; + } + + /// + /// Validate that the correctly implements the Probeinterface specification. + /// + /// + /// Check that all necessary fields are populated (, + /// , ). + /// Check that there is at least one defined. + /// Check that all variables in each have the same length. + /// Check if are present, and generate default values + /// based on the index if there are no values defined. + /// Check if are zero-indexed, and convert to + /// zero-indexed if possible. + /// Check if are defined, and initialize empty strings + /// if they are not defined. + /// Check if are defined, and initialize default + /// values (using the value as the new ). + /// Check that all are unique across all 's, + /// unless the value is -1; multiple contacts can be set to -1. + /// + public void Validate() + { + if (string.IsNullOrEmpty(Specification)) + { + throw new InvalidOperationException("Specification string must be defined."); + } + + if (string.IsNullOrEmpty(Version)) + { + throw new InvalidOperationException("Version string must be defined."); + } + + if (Probes == null || Probes.Count() == 0) + { + throw new InvalidOperationException("No probes are listed, probes must be added during construction"); + } + + ValidateVariableLength(); + + SetDefaultContactIdsIfMissing(); + ForceContactIdsToZeroIndexed(); + SetEmptyShankIdsIfMissing(); + SetDefaultDeviceChannelIndicesIfMissing(); + + if (!ValidateDeviceChannelIndices()) + { + throw new Exception("Device channel indices are not unique across all probes."); + } + } + + private void ValidateVariableLength() + { + for (int i = 0; i < Probes.Count(); i++) + { + if (Probes.ElementAt(i).NumberOfContacts != Probes.ElementAt(i).ContactPositions.Count() || + Probes.ElementAt(i).NumberOfContacts != Probes.ElementAt(i).ContactPlaneAxes.Count() || + Probes.ElementAt(i).NumberOfContacts != Probes.ElementAt(i).ContactShapeParams.Count() || + Probes.ElementAt(i).NumberOfContacts != Probes.ElementAt(i).ContactShapes.Count()) + { + throw new InvalidOperationException($"Required contact parameters are not the same length in probe {i}. " + + "Check positions / plane axes / shapes / shape parameters for lengths."); + } + + if (Probes.ElementAt(i).ContactIds != null && + Probes.ElementAt(i).ContactIds.Count() != Probes.ElementAt(i).NumberOfContacts) + { + throw new InvalidOperationException($"Contact IDs does not have the correct number of channels for probe {i}"); + } + + if (Probes.ElementAt(i).ShankIds != null && + Probes.ElementAt(i).ShankIds.Count() != Probes.ElementAt(i).NumberOfContacts) + { + throw new InvalidOperationException($"Shank IDs does not have the correct number of channels for probe {i}"); + } + + if (Probes.ElementAt(i).DeviceChannelIndices != null && + Probes.ElementAt(i).DeviceChannelIndices.Count() != Probes.ElementAt(i).NumberOfContacts) + { + throw new InvalidOperationException($"Device Channel Indices does not have the correct number of channels for probe {i}"); + } + } + } + + private void SetDefaultContactIdsIfMissing() + { + for (int i = 0; i < Probes.Count(); i++) + { + if (Probes.ElementAt(i).ContactIds == null) + { + Probes.ElementAt(i).ContactIds = Probe.DefaultContactIds(Probes.ElementAt(i).NumberOfContacts); + } + } + } + + private void ForceContactIdsToZeroIndexed() + { + var contactIds = GetContactIds(); + var numericIds = contactIds.Select(c => { return int.Parse(c); }) + .ToList(); + + var min = numericIds.Min(); + var max = numericIds.Max(); + + if (min == 1 && max == NumberOfContacts && numericIds.Count == numericIds.Distinct().Count()) + { + for (int i = 0; i < Probes.Count(); i++) + { + var probe = Probes.ElementAt(i); + var newContactIds = probe.ContactIds.Select(c => { return (int.Parse(c) - 1).ToString(); }); + + for (int j = 0; j < probe.NumberOfContacts; j++) + { + probe.ContactIds.SetValue(newContactIds.ElementAt(j), j); + } + } + } + } + + private void SetEmptyShankIdsIfMissing() + { + for (int i = 0; i < Probes.Count(); i++) + { + if (Probes.ElementAt(i).ShankIds == null) + { + Probes.ElementAt(i).ShankIds = Probe.DefaultShankIds(Probes.ElementAt(i).NumberOfContacts); + } + } + } + + private void SetDefaultDeviceChannelIndicesIfMissing() + { + for (int i = 0; i < Probes.Count(); i++) + { + if (Probes.ElementAt(i).DeviceChannelIndices == null) + { + Probes.ElementAt(i).DeviceChannelIndices = new int[Probes.ElementAt(i).NumberOfContacts]; + + for (int j = 0; j < Probes.ElementAt(i).NumberOfContacts; j++) + { + if (int.TryParse(Probes.ElementAt(i).ContactIds[j], out int result)) + { + Probes.ElementAt(i).DeviceChannelIndices[j] = result; + } + } + } + } + } + + /// + /// Validate the uniqueness of all 's across all 's. + /// + /// + /// All indices that are greater than or equal to 0 must be unique, + /// but there can be as many values equal to -1 as there are contacts. A value of -1 indicates that this contact is + /// not being recorded. + /// + /// True if all values not equal to -1 are unique, False if there are duplicates. + public bool ValidateDeviceChannelIndices() + { + var activeChannels = GetDeviceChannelIndices().Where(index => index != -1); + return activeChannels.Count() == activeChannels.Distinct().Count(); + } + + /// + /// Update the at the given probe index. + /// + /// + /// Device channel indices can be updated as contacts are being enabled or disabled. This is done on a + /// per-probe basis, where the incoming array of indices must be the same size as the original probe, + /// and must follow the standard for uniqueness found in . + /// + /// Zero-based index of the probe to update. + /// Array of . + /// + public void UpdateDeviceChannelIndices(int probeIndex, int[] deviceChannelIndices) + { + if (Probes.ElementAt(probeIndex).DeviceChannelIndices.Length != deviceChannelIndices.Length) + { + throw new ArgumentException($"Incoming device channel indices have {deviceChannelIndices.Length} contacts, " + + $"but the existing probe {probeIndex} has {Probes.ElementAt(probeIndex).DeviceChannelIndices.Length} contacts"); + } + + Probes.ElementAt(probeIndex).DeviceChannelIndices = deviceChannelIndices; + + if (!ValidateDeviceChannelIndices()) + { + throw new ArgumentException("Device channel indices are not valid. Ensure that all values are either -1 or are unique."); + } + } + } +} diff --git a/OpenEphys.ProbeInterface.NET/ProbeInterface.cs b/OpenEphys.ProbeInterface.NET/ProbeInterface.cs deleted file mode 100644 index 140c994..0000000 --- a/OpenEphys.ProbeInterface.NET/ProbeInterface.cs +++ /dev/null @@ -1,1056 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Xml.Serialization; -using System.Runtime.Serialization; -using System.CodeDom.Compiler; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using System; - -namespace OpenEphys.ProbeInterface; - -/// -/// Abstract class that implements the Probeinterface specification in C# for .NET. -/// -[GeneratedCodeAttribute("Bonsai.Sgen", "0.3.0.0 (Newtonsoft.Json v13.0.0.0)")] -public abstract class ProbeGroup -{ - private string _specification; - private string _version; - private IEnumerable _probes; - - /// - /// Gets the string defining the specification of the file. - /// - /// - /// For Probeinterface files, this value is expected to be "probeinterface". - /// - [JsonProperty("specification", Required = Required.Always)] - public string Specification - { - get { return _specification; } - protected set { _specification = value; } - } - - /// - /// Gets the string defining which version of Probeinterface was used. - /// - [JsonProperty("version", Required = Required.Always)] - public string Version - { - get { return _version; } - protected set { _version = value; } - } - - /// - /// Gets an IEnumerable of probes that are present. - /// - /// - /// Each probe can contain multiple shanks, and each probe has a unique - /// contour that defines the physical representation of the probe. Contacts have several representations - /// for their channel number, specifically (a string that is not guaranteed to be unique) and - /// (guaranteed to be unique across all probes). 's can also be set to -1 - /// to indicate that the channel was not connected or recorded from. - /// - [XmlIgnore] - [JsonProperty("probes", Required = Required.Always)] - public IEnumerable Probes - { - get { return _probes; } - protected set { _probes = value; } - } - - /// - /// Initializes a new instance of the class. - /// - /// String defining the parameter. - /// String defining the parameter. - /// IEnumerable of objects. - public ProbeGroup(string specification, string version, IEnumerable probes) - { - _specification = specification; - _version = version; - _probes = probes; - - Validate(); - } - - /// - /// Copy constructor that takes in an existing object and copies the individual fields. - /// - /// - /// After copying the relevant fields, the is validated to ensure that it is compliant - /// with the Probeinterface specification. See for more details on what is checked. - /// - /// Existing object. - protected ProbeGroup(ProbeGroup probeGroup) - { - _specification = probeGroup._specification; - _version = probeGroup._version; - _probes = probeGroup._probes; - - Validate(); - } - - /// - /// Gets the number of contacts across all objects. - /// - public int NumberOfContacts - { - get - { - int numContacts = 0; - - foreach (var probe in _probes) - { - numContacts += probe.NumberOfContacts; - } - - return numContacts; - } - } - - /// - /// Returns the 's of all contacts in all probes. - /// - /// - /// Note that these are not guaranteed to be unique values across probes. - /// - /// List of strings containing all contact IDs. - public IEnumerable GetContactIds() - { - List contactIds = new(); - - foreach (var probe in _probes) - { - contactIds.AddRange(probe.ContactIds.ToList()); - } - - return contactIds; - } - - /// - /// Returns all objects in the . - /// - /// - public List GetContacts() - { - List contacts = new(); - - foreach (var p in Probes) - { - for (int i = 0; i < p.NumberOfContacts; i++) - { - contacts.Add(p.GetContact(i)); - } - } - - return contacts; - } - - /// - /// Returns all 's in the . - /// - /// - /// Device channel indices are guaranteed to be unique, unless they are -1. Multiple contacts can be - /// set to -1 to indicate they are not recorded from. - /// - /// - public IEnumerable GetDeviceChannelIndices() - { - List deviceChannelIndices = new(); - - foreach (var probe in _probes) - { - deviceChannelIndices.AddRange(probe.DeviceChannelIndices.ToList()); - } - - return deviceChannelIndices; - } - - /// - /// Validate that the correctly implements the Probeinterface specification. - /// - /// - /// Check that all necessary fields are populated (, - /// , ). - /// Check that there is at least one defined. - /// Check that all variables in each have the same length. - /// Check if are present, and generate default values - /// based on the index if there are no values defined. - /// Check if are zero-indexed, and convert to - /// zero-indexed if possible. - /// Check if are defined, and initialize empty strings - /// if they are not defined. - /// Check if are defined, and initialize default - /// values (using the value as the new ). - /// Check that all are unique across all 's, - /// unless the value is -1; multiple contacts can be set to -1. - /// - public void Validate() - { - if (_specification == null || _version == null || _probes == null) - { - throw new Exception("Necessary fields are null, unable to validate properly"); - } - - if (_probes.Count() == 0) - { - throw new Exception("No probes are listed, probes must be added during construction"); - } - - if (!ValidateVariableLength(out string result)) - { - throw new Exception(result); - } - - SetDefaultContactIdsIfMissing(); - ValidateContactIds(); - SetEmptyShankIdsIfMissing(); - SetDefaultDeviceChannelIndicesIfMissing(); - - if (!ValidateDeviceChannelIndices()) - { - throw new Exception("Device channel indices are not unique across all probes."); - } - } - - private bool ValidateVariableLength(out string result) - { - for (int i = 0; i < _probes.Count(); i++) - { - if (_probes.ElementAt(i).NumberOfContacts != _probes.ElementAt(i).ContactPositions.Count() || - _probes.ElementAt(i).NumberOfContacts != _probes.ElementAt(i).ContactPlaneAxes.Count() || - _probes.ElementAt(i).NumberOfContacts != _probes.ElementAt(i).ContactShapeParams.Count() || - _probes.ElementAt(i).NumberOfContacts != _probes.ElementAt(i).ContactShapes.Count()) - { - result = $"Required contact parameters are not the same length in probe {i}. " + - "Check positions / plane axes / shapes / shape parameters for lengths."; - return false; - } - - if (_probes.ElementAt(i).ContactIds != null && - _probes.ElementAt(i).ContactIds.Count() != _probes.ElementAt(i).NumberOfContacts) - { - result = $"Contact IDs does not have the correct number of channels for probe {i}"; - return false; - } - - if (_probes.ElementAt(i).ShankIds != null && - _probes.ElementAt(i).ShankIds.Count() != _probes.ElementAt(i).NumberOfContacts) - { - result = $"Shank IDs does not have the correct number of channels for probe {i}"; - return false; - } - - if (_probes.ElementAt(i).DeviceChannelIndices != null && - _probes.ElementAt(i).DeviceChannelIndices.Count() != _probes.ElementAt(i).NumberOfContacts) - { - result = $"Device Channel Indices does not have the correct number of channels for probe {i}"; - return false; - } - } - - result = ""; - return true; - } - - private void SetDefaultContactIdsIfMissing() - { - int contactNum = 0; - - for (int i = 0; i < _probes.Count(); i++) - { - if (_probes.ElementAt(i).ContactIds == null) - { - _probes.ElementAt(i).ContactIds = Probe.DefaultContactIds(_probes.ElementAt(i).NumberOfContacts); - } - else - contactNum += _probes.ElementAt(i).NumberOfContacts; - } - } - - private void ValidateContactIds() - { - CheckIfContactIdsAreZeroIndexed(); - } - - private void CheckIfContactIdsAreZeroIndexed() - { - var contactIds = GetContactIds(); - var numericIds = contactIds.Select(c => { return int.Parse(c); }) - .ToList(); - - var min = numericIds.Min(); - var max = numericIds.Max(); - - if (min == 1 && max == NumberOfContacts && numericIds.Count == numericIds.Distinct().Count()) - { - for (int i = 0; i < _probes.Count(); i++) - { - var probe = _probes.ElementAt(i); - var newContactIds = probe.ContactIds.Select(c => { return (int.Parse(c) - 1).ToString(); }); - - for (int j = 0; j < probe.NumberOfContacts; j++) - { - probe.ContactIds.SetValue(newContactIds.ElementAt(j), j); - } - } - } - } - - private void SetEmptyShankIdsIfMissing() - { - for (int i = 0; i < _probes.Count(); i++) - { - if (_probes.ElementAt(i).ShankIds == null) - { - _probes.ElementAt(i).ShankIds = Probe.DefaultShankIds(_probes.ElementAt(i).NumberOfContacts); - } - } - } - - private void SetDefaultDeviceChannelIndicesIfMissing() - { - for (int i = 0; i < _probes.Count(); i++) - { - if (_probes.ElementAt(i).DeviceChannelIndices == null) - { - _probes.ElementAt(i).DeviceChannelIndices = new int[_probes.ElementAt(i).NumberOfContacts]; - - for (int j = 0; j < _probes.ElementAt(i).NumberOfContacts; j++) - { - if (int.TryParse(_probes.ElementAt(i).ContactIds[j], out int result)) - { - _probes.ElementAt(i).DeviceChannelIndices[j] = result; - } - } - } - } - } - - /// - /// Validate the uniqueness of all 's across all 's. - /// - /// - /// All indices that are greater than or equal to 0 must be unique, - /// but there can be as many values equal to -1 as there are contacts. A value of -1 indicates that this contact is - /// not being recorded. - /// - /// True if all values not equal to -1 are unique, False if there are duplicates. - public bool ValidateDeviceChannelIndices() - { - var activeChannels = GetDeviceChannelIndices() - .Where(index => index != -1); - - if (activeChannels.Count() != activeChannels.Distinct().Count()) - { - return false; - } - - return true; - } - - /// - /// Update the at the given probe index. - /// - /// - /// Device channel indices can be updated as contacts are being enabled or disabled. This is done on a - /// per-probe basis, where the incoming array of indices must be the same size as the original probe, - /// and must follow the standard for uniqueness found in . - /// - /// Zero-based index of the probe to update. - /// Array of . - /// - public void UpdateDeviceChannelIndices(int probeIndex, int[] deviceChannelIndices) - { - if (_probes.ElementAt(probeIndex).DeviceChannelIndices.Length != deviceChannelIndices.Length) - { - throw new ArgumentException($"Incoming device channel indices have {deviceChannelIndices.Length} contacts, " + - $"but the existing probe {probeIndex} has {_probes.ElementAt(probeIndex).DeviceChannelIndices.Length} contacts"); - } - - _probes.ElementAt(probeIndex).DeviceChannelIndices = deviceChannelIndices; - - if (!ValidateDeviceChannelIndices()) - { - throw new ArgumentException("Device channel indices are not valid. Ensure that all values are either -1 or are unique."); - } - } -} - -/// -/// Class that implements the Probe Interface specification for a Probe. -/// -[GeneratedCodeAttribute("Bonsai.Sgen", "0.3.0.0 (Newtonsoft.Json v13.0.0.0)")] -public class Probe -{ - private ProbeNdim _numDimensions; - private ProbeSiUnits _siUnits; - private ProbeAnnotations _annotations; - private ContactAnnotations _contactAnnotations; - private float[][] _contactPositions; - private float[][][] _contactPlaneAxes; - private ContactShape[] _contactShapes; - private ContactShapeParam[] _contactShapeParams; - private float[][] _probePlanarContour; - private int[] _deviceChannelIndices; - private string[] _contactIds; - private string[] _shankIds; - - /// - /// Gets the to use while plotting the . - /// - [XmlIgnore] - [JsonProperty("ndim", Required = Required.Always)] - public ProbeNdim NumDimensions - { - get { return _numDimensions; } - protected set { _numDimensions = value; } - } - - /// - /// Gets the to use while plotting the . - /// - [XmlIgnore] - [JsonProperty("si_units", Required = Required.Always)] - public ProbeSiUnits SiUnits - { - get { return _siUnits; } - protected set { _siUnits = value; } - } - - /// - /// Gets the for the . - /// - /// - /// Used to specify the name of the probe, and the manufacturer. - /// - [XmlIgnore] - [JsonProperty("annotations", Required = Required.Always)] - public ProbeAnnotations Annotations - { - get { return _annotations; } - protected set { _annotations = value; } - } - - /// - /// Gets the for the . - /// - /// - /// This field can be used for noting things like where it physically is within a specimen, or if it - /// is no longer functioning correctly. - /// - [XmlIgnore] - [JsonProperty("contact_annotations")] - public ContactAnnotations ContactAnnotations - { - get { return _contactAnnotations; } - protected set { _contactAnnotations = value; } - } - - /// - /// Gets the positions, specifically the center point of every contact. - /// - /// - /// This is a two-dimensional array of floats; the first index is the index of the contact, and - /// the second index is the X and Y value, respectively. - /// - [XmlIgnore] - [JsonProperty("contact_positions", Required = Required.Always)] - public float[][] ContactPositions - { - get { return _contactPositions; } - protected set { _contactPositions = value; } - } - - /// - /// Gets the plane axes for the contacts. - /// - [XmlIgnore] - [JsonProperty("contact_plane_axes")] - public float[][][] ContactPlaneAxes - { - get { return _contactPlaneAxes; } - protected set { _contactPlaneAxes = value; } - } - - /// - /// Gets the for each contact. - /// - [XmlIgnore] - [JsonProperty("contact_shapes", Required = Required.Always)] - public ContactShape[] ContactShapes - { - get { return _contactShapes; } - protected set { _contactShapes = value; } - } - - /// - /// Gets the parameters of the shape for each contact. - /// - /// - /// Depending on which - /// is selected, not all parameters are needed; for instance, only uses - /// , while just uses - /// . - /// - [XmlIgnore] - [JsonProperty("contact_shape_params", Required = Required.Always)] - public ContactShapeParam[] ContactShapeParams - { - get { return _contactShapeParams; } - protected set { _contactShapeParams = value; } - } - - /// - /// Gets the outline of the probe that represents the physical shape. - /// - [XmlIgnore] - [JsonProperty("probe_planar_contour")] - public float[][] ProbePlanarContour - { - get { return _probePlanarContour; } - protected set { _probePlanarContour = value; } - } - - /// - /// Gets the indices of each channel defining their recording channel number. Must be unique, except for contacts - /// that are set to -1 if they disabled. - /// - [XmlIgnore] - [JsonProperty("device_channel_indices")] - public int[] DeviceChannelIndices - { - get { return _deviceChannelIndices; } - internal set { _deviceChannelIndices = value; } - } - - /// - /// Gets the contact IDs for each channel. These do not have to be unique. - /// - [XmlIgnore] - [JsonProperty("contact_ids")] - public string[] ContactIds - { - get { return _contactIds; } - internal set { _contactIds = value; } - } - - /// - /// Gets the shank that each contact belongs to. - /// - [XmlIgnore] - [JsonProperty("shank_ids")] - public string[] ShankIds - { - get { return _shankIds; } - internal set { _shankIds = value; } - } - - /// - /// Public constructor, defined as the default Json constructor. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - [JsonConstructor] - public Probe(ProbeNdim ndim, ProbeSiUnits si_units, ProbeAnnotations annotations, ContactAnnotations contact_annotations, - float[][] contact_positions, float[][][] contact_plane_axes, ContactShape[] contact_shapes, - ContactShapeParam[] contact_shape_params, float[][] probe_planar_contour, int[] device_channel_indices, - string[] contact_ids, string[] shank_ids) - { - _numDimensions = ndim; - _siUnits = si_units; - _annotations = annotations; - _contactAnnotations = contact_annotations; - _contactPositions = contact_positions; - _contactPlaneAxes = contact_plane_axes; - _contactShapes = contact_shapes; - _contactShapeParams = contact_shape_params; - _probePlanarContour = probe_planar_contour; - _deviceChannelIndices = device_channel_indices; - _contactIds = contact_ids; - _shankIds = shank_ids; - } - - /// - /// Copy constructor given an existing object. - /// - /// - protected Probe(Probe probe) - { - _numDimensions = probe._numDimensions; - _siUnits = probe._siUnits; - _annotations = probe._annotations; - _contactAnnotations = probe._contactAnnotations; - _contactPositions = probe._contactPositions; - _contactPlaneAxes = probe._contactPlaneAxes; - _contactShapes = probe._contactShapes; - _contactShapeParams = probe._contactShapeParams; - _probePlanarContour = probe._probePlanarContour; - _deviceChannelIndices = probe._deviceChannelIndices; - _contactIds = probe._contactIds; - _shankIds = probe._shankIds; - } - - /// - /// Returns default array that contains the given number of channels and the corresponding shape. - /// - /// Number of contacts in a single . - /// The to apply to each contact. - /// array. - public static ContactShape[] DefaultContactShapes(int numberOfContacts, ContactShape contactShape) - { - ContactShape[] contactShapes = new ContactShape[numberOfContacts]; - - for (int i = 0; i < numberOfContacts; i++) - { - contactShapes[i] = contactShape; - } - - return contactShapes; - } - - /// - /// Returns a default contactPlaneAxes array, with each contact given the same axis; { { 1, 0 }, { 0, 1 } } - /// - /// - /// See Probeinterface documentation for more info. - /// - /// Number of contacts in a single . - /// Three-dimensional array of s. - public static float[][][] DefaultContactPlaneAxes(int numberOfContacts) - { - float[][][] contactPlaneAxes = new float[numberOfContacts][][]; - - for (int i = 0; i < numberOfContacts; i++) - { - contactPlaneAxes[i] = new float[2][] { new float[2] { 1.0f, 0.0f }, new float[2] { 0.0f, 1.0f } }; - } - - return contactPlaneAxes; - } - - /// - /// Returns an array of s for a . - /// - /// Number of contacts in a single . - /// Radius of the contact, in units of . - /// array. - public static ContactShapeParam[] DefaultCircleParams(int numberOfContacts, float radius) - { - ContactShapeParam[] contactShapeParams = new ContactShapeParam[numberOfContacts]; - - for (int i = 0; i < numberOfContacts; i++) - { - contactShapeParams[i] = new ContactShapeParam(radius: radius); - } - - return contactShapeParams; - } - - /// - /// Returns an array of s for a . - /// - /// Number of contacts in a single . - /// Width of the contact, in units of . - /// array. - public static ContactShapeParam[] DefaultSquareParams(int numberOfContacts, float width) - { - ContactShapeParam[] contactShapeParams = new ContactShapeParam[numberOfContacts]; - - for (int i = 0; i < numberOfContacts; i++) - { - contactShapeParams[i] = new ContactShapeParam(width: width); - } - - return contactShapeParams; - } - - /// - /// Returns an array of s for a . - /// - /// Number of contacts in a single . - /// Width of the contact, in units of . - /// Height of the contact, in units of . - /// array. - public static ContactShapeParam[] DefaultRectParams(int numberOfContacts, float width, float height) - { - ContactShapeParam[] contactShapeParams = new ContactShapeParam[numberOfContacts]; - - for (int i = 0; i < numberOfContacts; i++) - { - contactShapeParams[i] = new ContactShapeParam(height: height); - } - - return contactShapeParams; - } - - /// - /// Returns a default array of sequential . - /// - /// Number of contacts in a single . - /// The first value of the . - /// A serially increasing array of . - public static int[] DefaultDeviceChannelIndices(int numberOfContacts, int offset) - { - int[] deviceChannelIndices = new int[numberOfContacts]; - - for (int i = 0; i < numberOfContacts; i++) - { - deviceChannelIndices[i] = i + offset; - } - - return deviceChannelIndices; - } - - /// - /// Returns a sequential array of . - /// - /// Number of contacts in a single . - /// Array of strings defining the . - public static string[] DefaultContactIds(int numberOfContacts) - { - string[] contactIds = new string[numberOfContacts]; - - for (int i = 0; i < numberOfContacts; i++) - { - contactIds[i] = i.ToString(); - } - - return contactIds; - } - - /// - /// Returns an array of empty strings as the default shank ID. - /// - /// Number of contacts in a single . - /// Array of empty strings. - public static string[] DefaultShankIds(int numberOfContacts) - { - string[] contactIds = new string[numberOfContacts]; - - for (int i = 0; i < numberOfContacts; i++) - { - contactIds[i] = ""; - } - - return contactIds; - } - - /// - /// Returns a object. - /// - /// Relative index of the contact in this . - /// . - public Contact GetContact(int index) - { - return new Contact(ContactPositions[index][0], ContactPositions[index][1], ContactShapes[index], ContactShapeParams[index], - DeviceChannelIndices[index], ContactIds[index], ShankIds[index], index); - } - - /// - /// Gets the number of contacts within this . - /// - public int NumberOfContacts => ContactPositions.Length; -} - -/// -/// Number of dimensions to use while plotting a . -/// -[GeneratedCodeAttribute("Bonsai.Sgen", "0.3.0.0 (Newtonsoft.Json v13.0.0.0)")] -public enum ProbeNdim -{ - /// - /// Two-dimensions. - /// - [EnumMemberAttribute(Value = "2")] - Two = 2, - - /// - /// Three-dimensions. - /// - [EnumMemberAttribute(Value = "3")] - Three = 3, -} - -/// -/// SI units for all values relating to location and position. -/// -[GeneratedCodeAttribute("Bonsai.Sgen", "0.3.0.0 (Newtonsoft.Json v13.0.0.0)")] -[JsonConverter(typeof(StringEnumConverter))] -public enum ProbeSiUnits -{ - /// - /// Millimeters [mm]. - /// - [EnumMemberAttribute(Value = "mm")] - mm = 0, - - /// - /// Micrometers [um]. - /// - [EnumMemberAttribute(Value = "um")] - um = 1, -} - -/// -/// Struct that extends the Probeinterface specification by encapsulating all values for a single contact. -/// -public readonly struct Contact -{ - /// - /// Gets the x-position of the contact. - /// - public float PosX { get; } - - /// - /// Gets the y-position of the contact. - /// - public float PosY { get; } - - /// - /// Gets the of the contact. - /// - public ContactShape Shape { get; } - - /// - /// Gets the 's of the contact. - /// - public ContactShapeParam ShapeParams { get; } - - /// - /// Gets the device ID of the contact. - /// - public int DeviceId { get; } - - /// - /// Gets the contact ID of the contact. - /// - public string ContactId { get; } - - /// - /// Gets the shank ID of the contact. - /// - public string ShankId { get; } - - /// - /// Gets the index of the contact within the object. - /// - public int Index { get; } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// - /// - /// - /// - /// - /// - /// - public Contact(float posX, float posY, ContactShape shape, ContactShapeParam shapeParam, - int device_id, string contact_id, string shank_id, int index) - { - PosX = posX; - PosY = posY; - Shape = shape; - ShapeParams = shapeParam; - DeviceId = device_id; - ContactId = contact_id; - ShankId = shank_id; - Index = index; - } -} - -/// -/// Class holding parameters used to draw the contact. -/// -/// -/// Fields are nullable, since not all fields are required depending on the shape selected. -/// -[GeneratedCodeAttribute("Bonsai.Sgen", "0.3.0.0 (Newtonsoft.Json v13.0.0.0)")] -public class ContactShapeParam -{ - private float? _radius; - private float? _width; - private float? _height; - - /// - /// Gets the radius of the contact. - /// - /// - /// This is only used to draw contacts. Field can be null. - /// - public float? Radius - { - get { return _radius; } - protected set { _radius = value; } - } - - /// - /// Gets the width of the contact. - /// - /// - /// This is used to draw or contacts. - /// Field can be null. - /// - public float? Width - { - get { return _width; } - protected set { _width = value; } - } - - /// - /// Gets the height of the contact. - /// - /// - /// This is only used to draw contacts. Field can be null. - /// - public float? Height - { - get { return _height; } - protected set { _height = value; } - } - - /// - /// Initializes a new instance of the class. - /// - /// Radius. Can be null. - /// Width. Can be null. - /// Height. Can be null. - [JsonConstructor] - public ContactShapeParam(float? radius = null, float? width = null, float? height = null) - { - _radius = radius; - _width = width; - _height = height; - } - - /// - /// Copy constructor given an existing object. - /// - /// - protected ContactShapeParam(ContactShapeParam shape) - { - _radius = shape._radius; - _width = shape._width; - _height = shape._height; - } -} - -/// -/// Shape of the contact. -/// -[GeneratedCodeAttribute("Bonsai.Sgen", "0.3.0.0 (Newtonsoft.Json v13.0.0.0)")] -[JsonConverter(typeof(StringEnumConverter))] -public enum ContactShape -{ - /// - /// Circle. - /// - [EnumMemberAttribute(Value = "circle")] - Circle = 0, - - /// - /// Rectangle. - /// - [EnumMemberAttribute(Value = "rect")] - Rect = 1, - - /// - /// Square. - /// - [EnumMemberAttribute(Value = "square")] - Square = 2, -} - -/// -/// Class holding the annotations. -/// -[GeneratedCodeAttribute("Bonsai.Sgen", "0.3.0.0 (Newtonsoft.Json v13.0.0.0)")] -public class ProbeAnnotations -{ - private string _name; - private string _manufacturer; - - /// - /// Gets the name of the probe as defined by the manufacturer, or a descriptive name such as the neurological target. - /// - [JsonProperty("name")] - public string Name - { - get { return _name; } - protected set { _name = value; } - } - - /// - /// Gets the name of the manufacturer who created the probe. - /// - [JsonProperty("manufacturer")] - public string Manufacturer - { - get { return _manufacturer; } - protected set { _manufacturer = value; } - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// - [JsonConstructor] - public ProbeAnnotations(string name, string manufacturer) - { - _name = name; - _manufacturer = manufacturer; - } - - /// - /// Copy constructor that copies data from an existing object. - /// - /// - protected ProbeAnnotations(ProbeAnnotations probeAnnotations) - { - _name = probeAnnotations._name; - _manufacturer = probeAnnotations._manufacturer; - } -} - -/// -/// Class holding all of the annotations for each contact. -/// -public class ContactAnnotations -{ - private string[] _contactAnnotations; - - /// - /// Gets the array of strings holding annotations for each contact. Not all indices must have annotations. - /// - public string[] Annotations - { - get { return _contactAnnotations; } - protected set { _contactAnnotations = value; } - } - - /// - /// Initializes a new instance of the class. - /// - /// - [JsonConstructor] - public ContactAnnotations(string[] contactAnnotations) - { - _contactAnnotations = contactAnnotations; - } -} diff --git a/OpenEphys.ProbeInterface.NET/ProbeNdim.cs b/OpenEphys.ProbeInterface.NET/ProbeNdim.cs new file mode 100644 index 0000000..d2fe4a8 --- /dev/null +++ b/OpenEphys.ProbeInterface.NET/ProbeNdim.cs @@ -0,0 +1,22 @@ +using System.Runtime.Serialization; + +namespace OpenEphys.ProbeInterface +{ + /// + /// Number of dimensions to use while plotting a . + /// + public enum ProbeNdim + { + /// + /// Two-dimensions. + /// + [EnumMember(Value = "2")] + Two = 2, + + /// + /// Three-dimensions. + /// + [EnumMember(Value = "3")] + Three = 3, + } +} diff --git a/OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs b/OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs new file mode 100644 index 0000000..907dd56 --- /dev/null +++ b/OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs @@ -0,0 +1,25 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace OpenEphys.ProbeInterface +{ + /// + /// SI units for all values relating to location and position. + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum ProbeSiUnits + { + /// + /// Millimeters [mm]. + /// + [EnumMember(Value = "mm")] + mm = 0, + + /// + /// Micrometers [um]. + /// + [EnumMember(Value = "um")] + um = 1, + } +} From 2c9a3eb37488d6ab16050cd52866ef1c7bbec478 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Wed, 14 Aug 2024 13:45:46 -0400 Subject: [PATCH 2/3] Fixed spacing, and updated namespace to match solution --- OpenEphys.ProbeInterface.NET/Contact.cs | 2 +- .../ContactAnnotations.cs | 2 +- OpenEphys.ProbeInterface.NET/ContactShape.cs | 2 +- .../ContactShapeParam.cs | 2 +- OpenEphys.ProbeInterface.NET/Probe.cs | 19 +++++++------------ .../ProbeAnnotations.cs | 2 +- OpenEphys.ProbeInterface.NET/ProbeGroup.cs | 6 +----- OpenEphys.ProbeInterface.NET/ProbeNdim.cs | 2 +- OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs | 2 +- 9 files changed, 15 insertions(+), 24 deletions(-) diff --git a/OpenEphys.ProbeInterface.NET/Contact.cs b/OpenEphys.ProbeInterface.NET/Contact.cs index 348d37c..6e3af5c 100644 --- a/OpenEphys.ProbeInterface.NET/Contact.cs +++ b/OpenEphys.ProbeInterface.NET/Contact.cs @@ -1,4 +1,4 @@ -namespace OpenEphys.ProbeInterface +namespace OpenEphys.ProbeInterface.NET { /// /// Struct that extends the Probeinterface specification by encapsulating all values for a single contact. diff --git a/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs b/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs index 4f9fbca..8e46223 100644 --- a/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs +++ b/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace OpenEphys.ProbeInterface +namespace OpenEphys.ProbeInterface.NET { /// /// Class holding all of the annotations for each contact. diff --git a/OpenEphys.ProbeInterface.NET/ContactShape.cs b/OpenEphys.ProbeInterface.NET/ContactShape.cs index 85ad399..cf90d3d 100644 --- a/OpenEphys.ProbeInterface.NET/ContactShape.cs +++ b/OpenEphys.ProbeInterface.NET/ContactShape.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace OpenEphys.ProbeInterface +namespace OpenEphys.ProbeInterface.NET { /// /// Shape of the contact. diff --git a/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs b/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs index 6f29e6a..51cab51 100644 --- a/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs +++ b/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace OpenEphys.ProbeInterface +namespace OpenEphys.ProbeInterface.NET { /// /// Class holding parameters used to draw the contact. diff --git a/OpenEphys.ProbeInterface.NET/Probe.cs b/OpenEphys.ProbeInterface.NET/Probe.cs index 78b4f56..7944663 100644 --- a/OpenEphys.ProbeInterface.NET/Probe.cs +++ b/OpenEphys.ProbeInterface.NET/Probe.cs @@ -1,15 +1,13 @@ using System.Xml.Serialization; -using System.CodeDom.Compiler; using Newtonsoft.Json; -namespace OpenEphys.ProbeInterface +namespace OpenEphys.ProbeInterface.NET { /// /// Class that implements the Probe Interface specification for a Probe. /// public class Probe { - /// /// Gets the to use while plotting the . /// @@ -35,7 +33,7 @@ public class Probe public ProbeAnnotations Annotations { get; protected set; } /// - /// Gets the for the . + /// Gets the for the . /// /// /// This field can be used for noting things like where it physically is within a specimen, or if it @@ -63,7 +61,6 @@ public class Probe [JsonProperty("contact_plane_axes")] public float[][][] ContactPlaneAxes { get; protected set; } - /// /// Gets the for each contact. /// @@ -84,7 +81,6 @@ public class Probe [JsonProperty("contact_shape_params", Required = Required.Always)] public ContactShapeParam[] ContactShapeParams { get; protected set; } - /// /// Gets the outline of the probe that represents the physical shape. /// @@ -114,7 +110,6 @@ public class Probe [JsonProperty("shank_ids")] public string[] ShankIds { get; internal set; } - /// /// Public constructor, defined as the default Json constructor. /// @@ -264,11 +259,11 @@ public static ContactShapeParam[] DefaultRectParams(int numberOfContacts, float } /// - /// Returns a default array of sequential . + /// Returns a default array of sequential . /// /// Number of contacts in a single . - /// The first value of the . - /// A serially increasing array of . + /// The first value of the . + /// A serially increasing array of . public static int[] DefaultDeviceChannelIndices(int numberOfContacts, int offset) { int[] deviceChannelIndices = new int[numberOfContacts]; @@ -282,10 +277,10 @@ public static int[] DefaultDeviceChannelIndices(int numberOfContacts, int offset } /// - /// Returns a sequential array of . + /// Returns a sequential array of . /// /// Number of contacts in a single . - /// Array of strings defining the . + /// Array of strings defining the . public static string[] DefaultContactIds(int numberOfContacts) { string[] contactIds = new string[numberOfContacts]; diff --git a/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs b/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs index 24746c6..e7e59ed 100644 --- a/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs +++ b/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace OpenEphys.ProbeInterface +namespace OpenEphys.ProbeInterface.NET { /// /// Class holding the annotations. diff --git a/OpenEphys.ProbeInterface.NET/ProbeGroup.cs b/OpenEphys.ProbeInterface.NET/ProbeGroup.cs index b478e83..5c6408f 100644 --- a/OpenEphys.ProbeInterface.NET/ProbeGroup.cs +++ b/OpenEphys.ProbeInterface.NET/ProbeGroup.cs @@ -4,15 +4,13 @@ using Newtonsoft.Json; using System; -namespace OpenEphys.ProbeInterface +namespace OpenEphys.ProbeInterface.NET { /// /// Abstract class that implements the Probeinterface specification in C# for .NET. /// public abstract class ProbeGroup { - //private IEnumerable Probes; - /// /// Gets the string defining the specification of the file. /// @@ -42,7 +40,6 @@ public abstract class ProbeGroup [JsonProperty("probes", Required = Required.Always)] public IEnumerable Probes { get; protected set; } - /// /// Initializes a new instance of the class. /// @@ -80,7 +77,6 @@ protected ProbeGroup(ProbeGroup probeGroup) /// public int NumberOfContacts => Probes.Aggregate(0, (total, next) => total + next.NumberOfContacts); - /// /// Returns the 's of all contacts in all probes. /// diff --git a/OpenEphys.ProbeInterface.NET/ProbeNdim.cs b/OpenEphys.ProbeInterface.NET/ProbeNdim.cs index d2fe4a8..b1da56e 100644 --- a/OpenEphys.ProbeInterface.NET/ProbeNdim.cs +++ b/OpenEphys.ProbeInterface.NET/ProbeNdim.cs @@ -1,6 +1,6 @@ using System.Runtime.Serialization; -namespace OpenEphys.ProbeInterface +namespace OpenEphys.ProbeInterface.NET { /// /// Number of dimensions to use while plotting a . diff --git a/OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs b/OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs index 907dd56..c623147 100644 --- a/OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs +++ b/OpenEphys.ProbeInterface.NET/ProbeSiUnits.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace OpenEphys.ProbeInterface +namespace OpenEphys.ProbeInterface.NET { /// /// SI units for all values relating to location and position. From a83c7708a6fc1fd1ec3959fec11758dddd816efe Mon Sep 17 00:00:00 2001 From: bparks13 Date: Wed, 14 Aug 2024 15:52:55 -0400 Subject: [PATCH 3/3] Added missing parameter comments --- OpenEphys.ProbeInterface.NET/Contact.cs | 24 ++++++++-------- .../ContactAnnotations.cs | 2 +- .../ContactShapeParam.cs | 2 +- OpenEphys.ProbeInterface.NET/Probe.cs | 28 +++++++++---------- .../ProbeAnnotations.cs | 6 ++-- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/OpenEphys.ProbeInterface.NET/Contact.cs b/OpenEphys.ProbeInterface.NET/Contact.cs index 6e3af5c..b25f9bc 100644 --- a/OpenEphys.ProbeInterface.NET/Contact.cs +++ b/OpenEphys.ProbeInterface.NET/Contact.cs @@ -48,24 +48,24 @@ public readonly struct Contact /// /// Initializes a new instance of the struct. /// - /// - /// - /// - /// - /// - /// - /// - /// + /// Center value of the contact on the X-axis. + /// Center value of the contact on the Y-axis. + /// The of the contact. + /// 's relevant to the of the contact. + /// The device channel index () of this contact. + /// The contact ID () of this contact. + /// The shank ID () of this contact. + /// The index of the contact within the context of the . public Contact(float posX, float posY, ContactShape shape, ContactShapeParam shapeParam, - int device_id, string contact_id, string shank_id, int index) + int deviceId, string contactId, string shankId, int index) { PosX = posX; PosY = posY; Shape = shape; ShapeParams = shapeParam; - DeviceId = device_id; - ContactId = contact_id; - ShankId = shank_id; + DeviceId = deviceId; + ContactId = contactId; + ShankId = shankId; Index = index; } } diff --git a/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs b/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs index 8e46223..73b241e 100644 --- a/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs +++ b/OpenEphys.ProbeInterface.NET/ContactAnnotations.cs @@ -15,7 +15,7 @@ public class ContactAnnotations /// /// Initializes a new instance of the class. /// - /// + /// Array of strings containing annotations for each contact. Size of the array should match the number of contacts, but they can be empty strings. [JsonConstructor] public ContactAnnotations(string[] contactAnnotations) { diff --git a/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs b/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs index 51cab51..943046b 100644 --- a/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs +++ b/OpenEphys.ProbeInterface.NET/ContactShapeParam.cs @@ -52,7 +52,7 @@ public ContactShapeParam(float? radius = null, float? width = null, float? heigh /// /// Copy constructor given an existing object. /// - /// + /// Existing object to be copied. protected ContactShapeParam(ContactShapeParam shape) { Radius = shape.Radius; diff --git a/OpenEphys.ProbeInterface.NET/Probe.cs b/OpenEphys.ProbeInterface.NET/Probe.cs index 7944663..b7a84ad 100644 --- a/OpenEphys.ProbeInterface.NET/Probe.cs +++ b/OpenEphys.ProbeInterface.NET/Probe.cs @@ -113,18 +113,18 @@ public class Probe /// /// Public constructor, defined as the default Json constructor. /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// + /// Number of dimensions to use while plotting the contacts [ or ]. + /// Real-world units to use while plotting the contacts [ or ]. + /// Annotations for the probe. + /// Annotations for each contact as an array of strings. + /// Center position of each contact in a two-dimensional array of floats. For more info, see . + /// Plane axes of each contact in a three-dimensional array of floats. For more info, see . + /// Array of shapes for each contact. + /// Array of shape parameters for the each contact. + /// Two-dimensional array of floats (X and Y positions) defining a closed shape for a probe contour. + /// Array of integers containing the device channel indices for each contact. For more info, see . + /// Array of strings containing the contact ID for each contact. For more info, see . + /// Array of strings containing the shank ID for each contact. For more info, see . [JsonConstructor] public Probe(ProbeNdim ndim, ProbeSiUnits si_units, ProbeAnnotations annotations, ContactAnnotations contact_annotations, float[][] contact_positions, float[][][] contact_plane_axes, ContactShape[] contact_shapes, @@ -148,7 +148,7 @@ public Probe(ProbeNdim ndim, ProbeSiUnits si_units, ProbeAnnotations annotations /// /// Copy constructor given an existing object. /// - /// + /// Existing object to be copied. protected Probe(Probe probe) { NumDimensions = probe.NumDimensions; @@ -252,7 +252,7 @@ public static ContactShapeParam[] DefaultRectParams(int numberOfContacts, float for (int i = 0; i < numberOfContacts; i++) { - contactShapeParams[i] = new ContactShapeParam(height: height); + contactShapeParams[i] = new ContactShapeParam(width: width, height: height); } return contactShapeParams; diff --git a/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs b/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs index e7e59ed..72f9011 100644 --- a/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs +++ b/OpenEphys.ProbeInterface.NET/ProbeAnnotations.cs @@ -22,8 +22,8 @@ public class ProbeAnnotations /// /// Initializes a new instance of the class. /// - /// - /// + /// String defining the name of the probe. + /// String defining the manufacturer of the probe. [JsonConstructor] public ProbeAnnotations(string name, string manufacturer) { @@ -34,7 +34,7 @@ public ProbeAnnotations(string name, string manufacturer) /// /// Copy constructor that copies data from an existing object. /// - /// + /// Existing object, containing a and a . protected ProbeAnnotations(ProbeAnnotations probeAnnotations) { Name = probeAnnotations.Name;