-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFrameWriter.cs
272 lines (239 loc) · 11.2 KB
/
FrameWriter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
using Bonsai;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using OpenCV.Net;
using System.Runtime.InteropServices;
using System.Numerics;
using Bonsai.Reactive;
using System.Net;
using System.Security.Cryptography;
using System.Linq.Expressions;
using OpenEphys.Onix1;
using System.CodeDom;
using System.Drawing.Text;
namespace FrameWriter
{
[Combinator]
[Description("")]
[WorkflowElementCategory(ElementCategory.Sink)]
public class FrameWriter
{
public IObservable<Tsource> Process<Tsource>(IObservable<Tsource> source) // where Tsource : OpenEphys.Onix1.DataFrame //We need to create a common IOnixFrame or something that is a common ancestor of DataFrame and BufferedDataFrame
{
PropertyInfo[] properties = typeof(Tsource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
//prefetch this so it's faster later
Expression[] writeDelegates = new Expression[properties.Length];
var inputParam = Expression.Parameter(typeof(Tsource), "input");
for (int i = 0; i < properties.Length; i++)
{
PropertyInfo property = properties[i];
Expression writeDelegate = null;
MethodInfo method = null;
if (property.PropertyType.IsEnum)
{
method = ((Action<IConvertible>)WriteEnum).Method;
}
else
{
method = this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault(m => m.Name == "Write" &&
m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType.IsAssignableFrom(property.PropertyType));
}
if (method != null)
{
writeDelegate = GetMethodDelegate<Tsource>(method, property, inputParam);
}
if (method == null && (property.PropertyType.IsValueType || (property.PropertyType.IsArray && property.PropertyType.GetElementType().IsValueType)))
{
writeDelegate = BuildWriterExpression(property, inputParam);
}
if (writeDelegate == null)
{
throw new InvalidOperationException("Write method not supported for type " + property.PropertyType.ToString());
}
writeDelegates[i] = writeDelegate;
}
var allDelegates = Expression.Block(writeDelegates);
Action<Tsource> processParameters = Expression.Lambda<Action<Tsource>>(allDelegates, inputParam).Compile();
return source.Do(input =>
{
//For performance we might want to put the input into a queue for another thread to pick it up,
//or we might want the
processParameters(input);
});
}
//This is the method that needs to connect to whatever thread and send them data. Every other type is already converted into byte[]
// Note that ther might be a bunch of small arrays (for example a Quaternion is 4 arrays of size 4 (4 elements of sizeof(int) = 4 bytes)
//So some buffering to write in chunks might be desirable, but that is to see.
private void Write(byte[] data)
{
Console.WriteLine(data.ToString() + ": " + data.Length);
}
//We need a void Write(OpenCv.pImage) that either converts it to a byte[] and forwards it to the prior method
//or sends it to write in its own format.
private Expression BuildWriterExpression(PropertyInfo property, Expression input)
{
Type type = property.PropertyType;
Expression instance = Expression.Property(input, property);
try
{
if (type.IsArray)
{
return BuildArrayWriter(type, instance);
}
else
{
return BuildStructWriter(type, instance);
}
}
catch
{
return null;
}
}
private Expression BuildStructWriter(Type type, Expression instance)
{
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && p.GetIndexParameters().Length > 0);
var elements = fields.Cast<MemberInfo>().Concat(properties.Cast<MemberInfo>()).OrderBy(x => x.Name).ToArray();
var bufferParam = Expression.Parameter(typeof(byte[]), "buffer");
var thisInstance = Expression.Constant(this);
Expression[] expressions = new Expression[elements.Length];
for (int i = 0; i < elements.Length; i++)
{
Type elementType = GetMemberType(elements[i]);
if (!(typeof(IConvertible).IsAssignableFrom(elementType) || (elementType.IsArray && elementType.GetElementType().IsValueType)))
{
throw new InvalidOperationException();
}
Expression member = elements[i].MemberType switch
{
MemberTypes.Property => Expression.Property(instance, (PropertyInfo)elements[i]),
MemberTypes.Field => Expression.Field(instance, (FieldInfo)elements[i]),
_ => throw new InvalidOperationException()
};
if (typeof(IConvertible).IsAssignableFrom(elementType))
{
if (type.IsEnum)
{
expressions[i]= Expression.Call(thisInstance, ((Action<IConvertible>)WriteEnum).Method, Expression.Convert(member,typeof(IConvertible)));
}
else
{
expressions[i] = Expression.Call(thisInstance, ((Action<IConvertible>)Write).Method, Expression.Convert(member, typeof(IConvertible)));
}
}
else
{
expressions[i] = BuildStructWriter(elementType,member);
}
}
return Expression.Block(expressions);
}
private Expression BuildArrayWriter(Type type, Expression instance)
{
MethodInfo genericWriteMethod = this.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Where(m => m.Name == "Write" && m.IsGenericMethod && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsArray)
.Single();
MethodInfo byteWriteMethod = this.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Where(m => m.Name == "Write" && !m.IsGenericMethod && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsArray
&& m.GetParameters()[0].ParameterType.GetElementType() == typeof(byte)).Single();
var elementType = type.GetElementType();
var thisInstance = Expression.Constant(this);
if (elementType.IsPrimitive)
{
if (elementType == typeof(byte))
{
return Expression.Call(thisInstance, byteWriteMethod, instance);
}
else
{
var writeMethod = genericWriteMethod.MakeGenericMethod(elementType);
return Expression.Call(thisInstance, writeMethod, instance);
}
}
else
{
var method = ((Func<Type, Expression, Expression>)BuildStructWriter).Method;
var i = Expression.Parameter(typeof(int), "i");
var stopLabel = Expression.Label("stopLoop");
var body = Expression.Block(
Expression.Call(thisInstance, method, Expression.ArrayIndex(instance, i)),
Expression.PostIncrementAssign(i)
);
var condition = Expression.LessThan(i, Expression.ArrayLength(instance));
var ifExpression = Expression.IfThenElse(condition, body, Expression.Break(stopLabel));
var loop = Expression.Loop(ifExpression, stopLabel);
return Expression.Block(new[] { i }, Expression.Assign(i, Expression.Constant(0)), loop);
}
}
private Expression GetMethodDelegate<Tsource> (MethodInfo method, PropertyInfo property, ParameterExpression inputParam)
{
var accessProperty = Expression.Property(inputParam, property);
Type writeParamType = method.GetParameters()[0].ParameterType;
Expression convertedParam;
if (property.PropertyType == writeParamType)
{
convertedParam = accessProperty;
}
else
{
convertedParam = Expression.Convert(accessProperty, writeParamType);
}
var instance = Expression.Constant(this);
return Expression.Call(instance, method, convertedParam);
}
private void Write<T>(T[] data) where T : unmanaged
{
int size = SizeCache<T>.Size;
byte[] bytes = new byte[data.Length * size];
System.Buffer.BlockCopy(data, 0, bytes, 0, bytes.Length);
Write(bytes);
}
private static class SizeCache<T>
{
public static readonly int Size = Marshal.SizeOf(typeof(T));
}
private static Type GetMemberType(MemberInfo member)
{
return member.MemberType switch
{
MemberTypes.Property => ((PropertyInfo)member).PropertyType,
MemberTypes.Field => ((FieldInfo)member).FieldType,
_ => throw new ArgumentException("Unexpected member type ", nameof(member))
};
}
private void WriteEnum(IConvertible data)
{
Write(Convert.ToInt64(data));
}
private void Write(Mat mat)
{
Write(MatToArray(mat));
}
//This calls classes that implement IConvertible such as Uint64 Int16 etc...
private void Write(IConvertible data)
{
dynamic dyndata = data;
Write(BitConverter.GetBytes(dyndata));
}
//Quick and dirty copy from Bonsai.Dsp.ArrHelper, since that class is private
private static byte[] MatToArray(Mat input)
{
var step = input.ElementSize * input.Cols;
var data = new byte[step * input.Rows];
var dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
try
{
Mat dataHeader;
dataHeader = new Mat(input.Rows, input.Cols, input.Depth, input.Channels, dataHandle.AddrOfPinnedObject(), step);
}
finally { dataHandle.Free(); }
return data;
}
}
}