Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ generated
rules.ninja
*.a
**/_build
clang-tidy-output.{txt,json}
clang-tidy-output.{txt,json}

# CSharp build artifacts
c_sharp/bin/
c_sharp/obj/
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ option(BUILD_EXAMPLES "Build C++ examples" OFF)
option(OUSTER_USE_EIGEN_MAX_ALIGN_BYTES_32 "Eigen max aligned bytes." OFF)
option(BUILD_SHARED_LIBRARY "Build shared Library." OFF)
option(BUILD_DEBIAN_FOR_GITHUB "Build debian for github ci" OFF)
option(BUILD_C_WRAPPER "Build C wrapper library." ON)

# when building as a top-level project
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
Expand Down Expand Up @@ -89,6 +90,11 @@ if(BUILD_MAPPING)
add_subdirectory(ouster_mapping)
endif()

if(BUILD_C_WRAPPER)
# Place before examples so examples can link to ouster_c
add_subdirectory(ouster_c)
endif()

if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
Expand Down
103 changes: 103 additions & 0 deletions c_sharp/NativeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.Runtime.InteropServices;

namespace OusterSdkCSharp;

internal static class NativeConstants
{
public const int OU_CLIENT_TIMEOUT = 0;
public const int OU_CLIENT_ERROR = 1;
public const int OU_CLIENT_LIDAR_DATA = 2;
public const int OU_CLIENT_IMU_DATA = 4;
public const int OU_CLIENT_EXIT = 8;
}

internal static class NativeMethods
{
private const string LinuxLib = "ouster_c"; // resolves to libouster_c.so
private const string MacLib = "ouster_c"; // resolves to libouster_c.dylib
private const string WindowsLib = "ouster_c"; // ouster_c.dll

#if WINDOWS
private const string Lib = WindowsLib;
#elif OSX
private const string Lib = MacLib;
#else
private const string Lib = LinuxLib;
#endif

// Client API
[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr ouster_client_create(string hostname, int lidar_port, int imu_port);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern void ouster_client_destroy(IntPtr client);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_client_poll(IntPtr client, int timeout_sec);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_client_get_metadata(IntPtr client, IntPtr buffer, UIntPtr capacity);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_client_fetch_and_parse_metadata(IntPtr client, int timeout_sec);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_client_get_frame_dimensions(IntPtr client, out int width, out int height);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_client_get_packet_sizes(IntPtr client, out UIntPtr lidar_packet_size, out UIntPtr imu_packet_size);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_client_read_lidar_packet(IntPtr client, IntPtr buf, UIntPtr buf_size);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_client_read_imu_packet(IntPtr client, IntPtr buf, UIntPtr buf_size);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_client_get_lidar_port(IntPtr client);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_client_get_imu_port(IntPtr client);

// Scan Source API
[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_scan_source_create(string hostname, out IntPtr out_source);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern void ouster_scan_source_destroy(IntPtr source);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_scan_source_frame_dimensions(IntPtr source, out int width, out int height);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_scan_source_get_metadata(IntPtr source, IntPtr buffer, UIntPtr capacity);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr ouster_scan_source_next_scan(IntPtr source, int timeout_sec);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern void ouster_lidar_scan_destroy(IntPtr scan);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern void ouster_lidar_scan_get_dimensions(IntPtr scan, out int width, out int height);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_lidar_scan_get_field_u32(IntPtr scan, string field_name, int destagger, IntPtr out_buf, UIntPtr capacity, out UIntPtr out_count);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_lidar_scan_get_field_u16(IntPtr scan, string field_name, int destagger, IntPtr out_buf, UIntPtr capacity, out UIntPtr out_count);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_lidar_scan_get_field_u8(IntPtr scan, string field_name, int destagger, IntPtr out_buf, UIntPtr capacity, out UIntPtr out_count);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern int ouster_lidar_scan_get_xyz(IntPtr scan, IntPtr lut, IntPtr xyz_out, UIntPtr capacity_points, out UIntPtr out_points, int filter_invalid);

// XYZ LUT API
[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr ouster_scan_source_create_xyz_lut(IntPtr source, int use_extrinsics);

[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
internal static extern void ouster_xyz_lut_destroy(IntPtr lut);
}
72 changes: 72 additions & 0 deletions c_sharp/OusterClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using System.Runtime.InteropServices;

namespace OusterSdkCSharp;

public sealed class OusterClient : IDisposable
{
public IntPtr Handle { get; private set; }
public bool IsValid => Handle != IntPtr.Zero;

public static OusterClient? Create(string hostname, int lidarPort = 0, int imuPort = 0)
{
var h = NativeMethods.ouster_client_create(hostname, lidarPort, imuPort);
return h == IntPtr.Zero ? null : new OusterClient(h);
}

private OusterClient(IntPtr handle) => Handle = handle;

public int FetchAndParseMetadata(int timeoutSec = 30) => NativeMethods.ouster_client_fetch_and_parse_metadata(Handle, timeoutSec);

public string GetMetadata()
{
int len = NativeMethods.ouster_client_get_metadata(Handle, IntPtr.Zero, UIntPtr.Zero);
if (len <= 0) return string.Empty;
var buf = Marshal.AllocHGlobal(len + 1);
try
{
NativeMethods.ouster_client_get_metadata(Handle, buf, (UIntPtr)(ulong)(len + 1));
return Marshal.PtrToStringAnsi(buf) ?? string.Empty;
}
finally { Marshal.FreeHGlobal(buf); }
}

public (int Width, int Height) GetFrameDimensions()
{
NativeMethods.ouster_client_get_frame_dimensions(Handle, out int w, out int h);
return (w, h);
}

public (ulong LidarPacketSize, ulong ImuPacketSize) GetPacketSizes()
{
NativeMethods.ouster_client_get_packet_sizes(Handle, out UIntPtr lidar, out UIntPtr imu);
return ((ulong)lidar, (ulong)imu);
}

public int Poll(int timeoutSec = 1) => NativeMethods.ouster_client_poll(Handle, timeoutSec);

public byte[]? ReadLidarPacket()
{
var sizes = GetPacketSizes();
if (sizes.LidarPacketSize == 0) return null;
var arr = new byte[sizes.LidarPacketSize];
var ptr = Marshal.AllocHGlobal(arr.Length);
try
{
int ok = NativeMethods.ouster_client_read_lidar_packet(Handle, ptr, (UIntPtr)(ulong)arr.Length);
if (ok == 1) Marshal.Copy(ptr, arr, 0, arr.Length);
return ok == 1 ? arr : null;
}
finally { Marshal.FreeHGlobal(ptr); }
}

public void Dispose()
{
if (Handle != IntPtr.Zero)
{
NativeMethods.ouster_client_destroy(Handle);
Handle = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
}
190 changes: 190 additions & 0 deletions c_sharp/OusterScanSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using System;
using System.Runtime.InteropServices;

namespace OusterSdkCSharp;

public sealed class OusterLidarScan : IDisposable
{
internal IntPtr Handle { get; private set; }
internal XYZLut Lut { get; private set; }

internal int Width { get; private set; }
internal int Height { get; private set; }

internal OusterLidarScan(IntPtr handle, XYZLut lut)
{
Handle = handle;
Lut = lut;
NativeMethods.ouster_lidar_scan_get_dimensions(Handle, out int w, out int h);
Width = w;
Height = h;
}

public void Dispose()
{
if (Handle != IntPtr.Zero)
{
NativeMethods.ouster_lidar_scan_destroy(Handle);
Handle = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}

public float[] GetXYZ(bool filterInvalid)
{
int maxPoints = Width * Height;
var xyz = new float[maxPoints * 3];
var ptr = Marshal.AllocHGlobal(sizeof(float) * maxPoints * 3);
try
{
int rc = NativeMethods.ouster_lidar_scan_get_xyz(
Handle, Lut.Handle, ptr, (UIntPtr)maxPoints, out var outPoints, filterInvalid ? 1 : 0);
if (rc != 0) return Array.Empty<float>();
int n = (int)outPoints;
Marshal.Copy(ptr, xyz, 0, (int)(n * 3));
if (n * 3 < xyz.Length)
{
Array.Resize(ref xyz, (int)(n * 3));
}
return xyz;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}

public T[,] GetField<T>(string fieldName, bool destagger = false) where T : unmanaged
{
int count = Width * Height;
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf<T>() * count);
var image = new T[Height, Width];
int rc = 0;
nuint outCount;

try
{
switch (Type.GetTypeCode(typeof(T)))
{
case TypeCode.UInt32:
rc = NativeMethods.ouster_lidar_scan_get_field_u32(
Handle, fieldName, destagger ? 1 : 0, ptr, (UIntPtr)count, out outCount);
break;
case TypeCode.UInt16:
rc = NativeMethods.ouster_lidar_scan_get_field_u16(
Handle, fieldName, destagger ? 1 : 0, ptr, (UIntPtr)count, out outCount);
break;
case TypeCode.Byte:
rc = NativeMethods.ouster_lidar_scan_get_field_u8(
Handle, fieldName, destagger ? 1 : 0, ptr, (UIntPtr)count, out outCount);
break;
default:
throw new NotSupportedException($"Type {typeof(T)} is not supported.");
}

if (rc != 0) return image;


for (int i = 0; i < Height; i++)
{
for (int j = 0; j < Width; j++)
{
unsafe { image[i, j] = ((T*)ptr)[i * Width + j]; }
}
}

return image;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
}

public sealed class XYZLut : IDisposable
{
internal IntPtr Handle { get; private set; }

internal XYZLut(IntPtr handle)
{
Handle = handle;
}

public void Dispose()
{
if (Handle != IntPtr.Zero)
{
NativeMethods.ouster_xyz_lut_destroy(Handle);
Handle = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
}

public sealed class OusterScanSource : IDisposable
{
public IntPtr Handle { get; private set; }
private int _width;
private int _height;
public int Width => _width;
public int Height => _height;

private XYZLut _lut = null!;

public static OusterScanSource? Create(string hostname)
{
int rc = NativeMethods.ouster_scan_source_create(hostname, out var handle);
if (rc != 0 || handle == IntPtr.Zero) return null;
var src = new OusterScanSource(handle);
src.RefreshDimensions();
src._lut = src.CreateXYZLut();
return src;
}

public XYZLut CreateXYZLut(bool useExtrinsics = true)
{
int flag = useExtrinsics ? 1 : 0;
var lutPtr = NativeMethods.ouster_scan_source_create_xyz_lut(Handle, flag);
return new XYZLut(lutPtr);
}

private OusterScanSource(IntPtr h) => Handle = h;

private void RefreshDimensions()
{
if (Handle != IntPtr.Zero)
{
NativeMethods.ouster_scan_source_frame_dimensions(Handle, out _width, out _height);
}
}

public string GetMetadata()
{
int len = NativeMethods.ouster_scan_source_get_metadata(Handle, IntPtr.Zero, UIntPtr.Zero);
if (len <= 0) return string.Empty;
var buf = Marshal.AllocHGlobal(len + 1);
try
{
NativeMethods.ouster_scan_source_get_metadata(Handle, buf, (UIntPtr)(ulong)(len + 1));
return Marshal.PtrToStringAnsi(buf) ?? string.Empty;
}
finally { Marshal.FreeHGlobal(buf); }
}

public OusterLidarScan? NextScan(int timeoutSec = 2)
{
var scanPtr = NativeMethods.ouster_scan_source_next_scan(Handle, timeoutSec);
return scanPtr == IntPtr.Zero ? null : new OusterLidarScan(scanPtr, _lut);
}

public void Dispose()
{
if (Handle != IntPtr.Zero)
{
NativeMethods.ouster_scan_source_destroy(Handle);
Handle = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
}
Loading
Loading