Skip to content

Commit 3396c62

Browse files
authored
Merge pull request #2028 from shimat/fix/filenode-filestorage-quality
Close FileNode/FileStorage gaps vs. the native CV_WRAP surface
2 parents 1d8b597 + 677413f commit 3396c62

10 files changed

Lines changed: 974 additions & 41 deletions

File tree

src/OpenCvSharp/Internal/PInvoke/NativeMethods/NativeMethods_stdvector.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,9 @@ public static partial void vector_string_getElements(
450450

451451
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
452452
public static partial void vector_string_delete(IntPtr vector);
453+
454+
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
455+
public static partial void vector_string_pushBack(OpenCvSafeHandle vector, [MarshalAs(UnmanagedType.LPStr)] string value);
453456
#endregion
454457
#region vector<cv::line_descriptor::KeyLine>
455458
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]

src/OpenCvSharp/Internal/PInvoke/NativeMethods/core/NativeMethods_core_FileNode.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,17 @@ public static partial ExceptionStatus core_FileNode_operatorThis_byString(
4848
public static partial ExceptionStatus core_FileNode_name(OpenCvSafeHandle obj, IntPtr buf);
4949
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
5050
public static partial ExceptionStatus core_FileNode_size(OpenCvSafeHandle obj, out IntPtr returnValue);
51+
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
52+
public static partial ExceptionStatus core_FileNode_rawSize(OpenCvSafeHandle obj, out IntPtr returnValue);
53+
54+
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
55+
public static partial ExceptionStatus core_FileNode_keys(OpenCvSafeHandle obj, IntPtr returnValue);
5156

5257
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
5358
public static partial ExceptionStatus core_FileNode_toInt(OpenCvSafeHandle obj, out int returnValue);
5459
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
60+
public static partial ExceptionStatus core_FileNode_toInt64(OpenCvSafeHandle obj, out long returnValue);
61+
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
5562
public static partial ExceptionStatus core_FileNode_toFloat(OpenCvSafeHandle obj, out float returnValue);
5663
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
5764
public static partial ExceptionStatus core_FileNode_toDouble(OpenCvSafeHandle obj, out double returnValue);
@@ -74,6 +81,8 @@ public static partial ExceptionStatus core_FileNode_readRaw(
7481
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
7582
public static partial ExceptionStatus core_FileNode_read_int(OpenCvSafeHandle node, out int value, int defaultValue);
7683
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
84+
public static partial ExceptionStatus core_FileNode_read_int64(OpenCvSafeHandle node, out long value, long defaultValue);
85+
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
7786
public static partial ExceptionStatus core_FileNode_read_float(OpenCvSafeHandle node, out float value, float defaultValue);
7887
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
7988
public static partial ExceptionStatus core_FileNode_read_double(OpenCvSafeHandle node, out double value, double defaultValue);

src/OpenCvSharp/Internal/PInvoke/NativeMethods/core/NativeMethods_core_FileStorage.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,22 @@ public static partial ExceptionStatus core_FileStorage_startWriteStruct(
7878
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
7979
public static partial ExceptionStatus core_FileStorage_state(OpenCvSafeHandle obj, out int returnValue);
8080

81+
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
82+
public static partial ExceptionStatus core_FileStorage_getFormat(OpenCvSafeHandle obj, out int returnValue);
83+
8184
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
8285
public static partial ExceptionStatus core_FileStorage_write_int(
8386
OpenCvSafeHandle fs, [MarshalAs(UnmanagedType.LPStr)] string name, int value);
8487
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
88+
public static partial ExceptionStatus core_FileStorage_write_bool(
89+
OpenCvSafeHandle fs, [MarshalAs(UnmanagedType.LPStr)] string name, int value);
90+
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
91+
public static partial ExceptionStatus core_FileStorage_write_int64(
92+
OpenCvSafeHandle fs, [MarshalAs(UnmanagedType.LPStr)] string name, long value);
93+
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
94+
public static partial ExceptionStatus core_FileStorage_write_vectorOfString(
95+
OpenCvSafeHandle fs, [MarshalAs(UnmanagedType.LPStr)] string name, IntPtr value);
96+
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
8597
public static partial ExceptionStatus core_FileStorage_write_float(
8698
OpenCvSafeHandle fs, [MarshalAs(UnmanagedType.LPStr)] string name, float value);
8799
[LibraryImport(DllExtern), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]

src/OpenCvSharp/Internal/Vectors/VectorOfString.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ public VectorOfString(int size)
2626
SetSafeHandle(new OpenCvPtrSafeHandle(p, ownsHandle: false, releaseAction: null));
2727
}
2828

29+
/// <summary>
30+
/// Creates a vector populated from <paramref name="data"/>.
31+
/// </summary>
32+
/// <param name="data"></param>
33+
public VectorOfString(IEnumerable<string> data)
34+
{
35+
ArgumentNullException.ThrowIfNull(data);
36+
var p = NativeMethods.vector_string_new1();
37+
SetSafeHandle(new OpenCvPtrSafeHandle(p, ownsHandle: false, releaseAction: null));
38+
foreach (var s in data)
39+
{
40+
if (s is null)
41+
throw new ArgumentException("Collection must not contain null elements.", nameof(data));
42+
NativeMethods.vector_string_pushBack(Handle, s);
43+
}
44+
}
45+
2946
/// <summary>
3047
/// Releases unmanaged resources
3148
/// </summary>

src/OpenCvSharp/Modules/core/FileNode.cs

Lines changed: 199 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections;
22
using System.Diagnostics.CodeAnalysis;
3+
using System.Text.Json.Nodes;
34
using OpenCvSharp.Internal;
45
using OpenCvSharp.Internal.Vectors;
56

@@ -45,6 +46,28 @@ private void InitSafeHandle(IntPtr p, bool ownsHandle = true)
4546
static h => NativeMethods.HandleException(NativeMethods.core_FileNode_delete(h))));
4647
}
4748

49+
/// <summary>
50+
/// Wraps a native cv::FileNode* as a nullable FileNode, normalizing the "not found" case.
51+
/// Native FileNode lookups (FileStorage/FileNode indexers) never return a null pointer -
52+
/// a missing key comes back as a non-null, heap-allocated node whose Type is None. That
53+
/// sentinel is indistinguishable from a key that is present but explicitly stores a "none"
54+
/// value (e.g. YAML's <c>~</c>), so this treats both as "not found" and returns null,
55+
/// which is what a C# caller chaining with <c>?.</c> actually expects.
56+
/// </summary>
57+
internal static FileNode? FromRawPtrOrNull(IntPtr ptr)
58+
{
59+
if (ptr == IntPtr.Zero)
60+
return null;
61+
62+
var node = new FileNode(ptr);
63+
if (node.IsNone)
64+
{
65+
node.Dispose();
66+
return null;
67+
}
68+
return node;
69+
}
70+
4871
#endregion
4972

5073
#region Cast
@@ -74,6 +97,31 @@ public int ToInt32()
7497
return ret;
7598
}
7699

100+
/// <summary>
101+
/// Returns the node content as a signed 64-bit integer. If the node stores a floating-point number, it is rounded.
102+
/// </summary>
103+
/// <param name="node"></param>
104+
/// <returns></returns>
105+
public static explicit operator long(FileNode node)
106+
{
107+
ArgumentNullException.ThrowIfNull(node);
108+
return node.ToInt64();
109+
}
110+
111+
/// <summary>
112+
/// Returns the node content as a signed 64-bit integer. If the node stores a floating-point number, it is rounded.
113+
/// </summary>
114+
/// <returns></returns>
115+
public long ToInt64()
116+
{
117+
ThrowIfDisposed();
118+
119+
NativeMethods.HandleException(
120+
NativeMethods.core_FileNode_toInt64(Handle, out var ret));
121+
122+
return ret;
123+
}
124+
77125
/// <summary>
78126
/// Returns the node content as float
79127
/// </summary>
@@ -176,12 +224,65 @@ public Mat ToMat()
176224
return matrix;
177225
}
178226

227+
/// <summary>
228+
/// Converts this node (and, recursively, its children) into a <see cref="JsonNode"/> tree,
229+
/// regardless of whether the underlying <see cref="FileStorage"/> was opened as XML, YAML or
230+
/// JSON. This is a generic structural conversion (scalars, sequences, mappings) - it does not
231+
/// give special treatment to OpenCV-specific encodings such as <c>Mat</c>/<c>KeyPoint</c>/
232+
/// <c>DMatch</c>, which come through as their raw mapping/sequence shape (e.g. a Mat becomes a
233+
/// JSON object with "rows"/"cols"/"dt"/"data" members, not an <see cref="OpenCvSharp.Mat"/>).
234+
/// The result can be fed to <see cref="System.Text.Json.JsonSerializer"/> or queried directly.
235+
/// </summary>
236+
/// <returns>
237+
/// The converted node, or null for a <see cref="Types.None"/> node (mirroring JSON <c>null</c>).
238+
/// </returns>
239+
public JsonNode? ToJsonNode()
240+
{
241+
ThrowIfDisposed();
242+
243+
return Type switch
244+
{
245+
Types.None => null,
246+
Types.Int => JsonValue.Create(ToInt64()),
247+
Types.Real => JsonValue.Create(ToDouble()),
248+
Types.Str => JsonValue.Create(ToString()),
249+
Types.Seq => SeqToJsonArray(),
250+
Types.Map => MapToJsonObject(),
251+
_ => throw new NotSupportedException($"Cannot convert a FileNode of type {Type} to a JsonNode."),
252+
};
253+
}
254+
255+
private JsonArray SeqToJsonArray()
256+
{
257+
var array = new JsonArray();
258+
foreach (var child in this)
259+
{
260+
using (child)
261+
{
262+
array.Add(child.ToJsonNode());
263+
}
264+
}
265+
return array;
266+
}
267+
268+
private JsonObject MapToJsonObject()
269+
{
270+
var obj = new JsonObject();
271+
foreach (var key in Keys)
272+
{
273+
using var child = this[key];
274+
obj[key] = child?.ToJsonNode();
275+
}
276+
return obj;
277+
}
278+
179279
#endregion
180280

181281
#region Properties
182282

183283
/// <summary>
184-
/// returns element of a mapping node
284+
/// Returns the element of a mapping node with the given key, or null if the key is not
285+
/// present (or is explicitly stored as a "none" value).
185286
/// </summary>
186287
public FileNode? this[string nodeName]
187288
{
@@ -193,28 +294,69 @@ public FileNode? this[string nodeName]
193294
NativeMethods.HandleException(
194295
NativeMethods.core_FileNode_operatorThis_byString(Handle, nodeName, out var node));
195296

196-
if (node == IntPtr.Zero)
197-
return null;
198-
return new FileNode(node);
297+
return FromRawPtrOrNull(node);
199298
}
200299
}
201300

202301
/// <summary>
203-
/// returns element of a sequence node
302+
/// Returns the element of a sequence node at the given index, or null if the index is out
303+
/// of range (or the element is explicitly stored as a "none" value).
204304
/// </summary>
205305
public FileNode? this[int i]
206306
{
207307
get
208308
{
209309
ThrowIfDisposed();
210310

211-
NativeMethods.HandleException(
311+
NativeMethods.HandleException(
212312
NativeMethods.core_FileNode_operatorThis_byInt(Handle, i, out var node));
213313

214-
if (node == IntPtr.Zero)
314+
return FromRawPtrOrNull(node);
315+
}
316+
}
317+
318+
/// <summary>
319+
/// Navigates a chain of mapping keys (<see cref="string"/>) and/or sequence indices
320+
/// (<see cref="int"/>), disposing every intermediate <see cref="FileNode"/> along the way.
321+
/// Equivalent to repeated indexer chaining (e.g. <c>node["a"][2]["b"]</c>), except that the
322+
/// indexer chain leaves every intermediate node unreferenced - each one is still a real
323+
/// native allocation that would otherwise sit around until the GC finalizes it.
324+
/// </summary>
325+
/// <param name="path">One or more mapping keys / sequence indices to follow, in order.</param>
326+
/// <returns>The node at the end of the path, or null if any segment along the way is missing.</returns>
327+
public FileNode? GetPath(params object[] path)
328+
{
329+
ArgumentNullException.ThrowIfNull(path);
330+
if (path.Length == 0)
331+
throw new ArgumentException("Path must contain at least one key or index.", nameof(path));
332+
333+
ThrowIfDisposed();
334+
335+
var current = this;
336+
var ownsCurrent = false;
337+
338+
foreach (var segment in path)
339+
{
340+
var next = segment switch
341+
{
342+
string key => current[key],
343+
int index => current[index],
344+
_ => throw new ArgumentException(
345+
$"Path segments must be string (mapping key) or int (sequence index), got '{segment?.GetType()}'.",
346+
nameof(path)),
347+
};
348+
349+
if (ownsCurrent)
350+
current.Dispose();
351+
352+
if (next is null)
215353
return null;
216-
return new FileNode(node);
354+
355+
current = next;
356+
ownsCurrent = true;
217357
}
358+
359+
return current;
218360
}
219361

220362
/// <summary>
@@ -368,6 +510,35 @@ public long Size
368510
}
369511
}
370512

513+
/// <summary>
514+
/// Returns the keys of a mapping node.
515+
/// </summary>
516+
public string[] Keys
517+
{
518+
get
519+
{
520+
ThrowIfDisposed();
521+
using var buf = new VectorOfString();
522+
NativeMethods.HandleException(
523+
NativeMethods.core_FileNode_keys(Handle, buf.CvPtr));
524+
return buf.ToArray();
525+
}
526+
}
527+
528+
/// <summary>
529+
/// Returns the raw size of the node in bytes.
530+
/// </summary>
531+
public long RawSize
532+
{
533+
get
534+
{
535+
ThrowIfDisposed();
536+
NativeMethods.HandleException(
537+
NativeMethods.core_FileNode_rawSize(Handle, out var ret));
538+
return ret.ToInt64();
539+
}
540+
}
541+
371542
/// <summary>
372543
/// Returns type of the node.
373544
/// </summary>
@@ -433,14 +604,17 @@ IEnumerator IEnumerable.GetEnumerator()
433604
/// Reads node elements to the buffer with the specified format
434605
/// </summary>
435606
/// <param name="fmt"></param>
436-
/// <param name="vec"></param>
437-
/// <param name="len"></param>
438-
public void ReadRaw(string fmt, IntPtr vec, long len)
607+
/// <param name="vec">The buffer to read into.</param>
608+
public unsafe void ReadRaw(string fmt, Span<byte> vec)
439609
{
440610
ThrowIfDisposed();
441611
ArgumentNullException.ThrowIfNull(fmt);
442-
NativeMethods.HandleException(
443-
NativeMethods.core_FileNode_readRaw(Handle, fmt, vec, new IntPtr(len)));
612+
613+
fixed (byte* p = vec)
614+
{
615+
NativeMethods.HandleException(
616+
NativeMethods.core_FileNode_readRaw(Handle, fmt, (IntPtr)p, new IntPtr(vec.Length)));
617+
}
444618
}
445619

446620
#region Read
@@ -457,6 +631,18 @@ public int ReadInt(int defaultValue = default)
457631
return value;
458632
}
459633

634+
/// <summary>
635+
/// Reads the node element as Int64 (long)
636+
/// </summary>
637+
/// <param name="defaultValue"></param>
638+
/// <returns></returns>
639+
public long ReadInt64(long defaultValue = default)
640+
{
641+
NativeMethods.HandleException(
642+
NativeMethods.core_FileNode_read_int64(Handle, out var value, defaultValue));
643+
return value;
644+
}
645+
460646
/// <summary>
461647
/// Reads the node element as Single (float)
462648
/// </summary>

0 commit comments

Comments
 (0)