Skip to content

Commit eaca3ba

Browse files
committed
Merge branch 'dev'
* dev: #BUILD read both classification values, get min max classification into metadata json, add converter version to metadata json, fix classification collection fix randomization issue use currently selected input format in file browser require importformat before input folder for Batch, add missing ply reader, default importformat is unknown now add experimental PLY importer support (ascii and tested with simple files only)
2 parents ca4aaca + b88d408 commit eaca3ba

File tree

10 files changed

+370
-116
lines changed

10 files changed

+370
-116
lines changed

MainWindow.xaml.cs

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace PointCloudConverter
2929
{
3030
public partial class MainWindow : Window
3131
{
32-
static readonly string version = "29.03.2025";
32+
static readonly string version = "03.04.2025";
3333
static readonly string appname = "PointCloud Converter - " + version;
3434
static readonly string rootFolder = AppDomain.CurrentDomain.BaseDirectory;
3535

@@ -70,6 +70,10 @@ public partial class MainWindow : Window
7070
private readonly float cellSize = 0.5f;
7171
private static ConcurrentDictionary<(int, int, int), byte> occupiedCells = new();
7272

73+
// classification stats
74+
static byte minClass = 255;
75+
static byte maxClass = 0;
76+
7377
// plugins
7478
string externalFileFormats = "";
7579

@@ -198,6 +202,10 @@ private async void Main()
198202
// get elapsed time using time
199203
var startTime = DateTime.Now;
200204

205+
// classification minmax
206+
minClass = 255;
207+
maxClass = 0;
208+
201209
// if have files, process them
202210
if (importSettings.errors.Count == 0)
203211
{
@@ -448,6 +456,7 @@ private static async Task ProcessAllFiles(object workerParamsObject)
448456
}
449457
else
450458
{
459+
Log.Write("files" + importSettings.inputFiles.Count + " i:" + i);
451460
Log.Write("Error> Failed to parse file: " + importSettings.inputFiles[i], LogEvent.Error);
452461
}
453462
}
@@ -752,12 +761,6 @@ static bool ParseFile(ImportSettings importSettings, int fileIndex, int? taskId,
752761
return false;
753762
}
754763

755-
if (importSettings.importMetadata == true)
756-
{
757-
var metaData = taskReader.GetMetaData(importSettings, fileIndex);
758-
lasHeaders.Add(metaData);
759-
}
760-
761764
if (importSettings.importMetadataOnly == false)
762765
{
763766
int fullPointCount = taskReader.GetPointCount();
@@ -945,7 +948,7 @@ static bool ParseFile(ImportSettings importSettings, int fileIndex, int? taskId,
945948
// if no rgb, then replace RGB with intensity
946949
if (importSettings.importRGB == false)
947950
{
948-
rgb.r = intensity/255f;
951+
rgb.r = intensity / 255f;
949952
rgb.g = rgb.r;
950953
rgb.b = rgb.r;
951954
}
@@ -956,10 +959,16 @@ static bool ParseFile(ImportSettings importSettings, int fileIndex, int? taskId,
956959
if (importSettings.importClassification == true)
957960
{
958961
classification = taskReader.GetClassification();
959-
//classification = taskReader.GetIntensity();
962+
963+
// get min and max
964+
if (classification < minClass) minClass = classification;
965+
if (classification > maxClass) maxClass = classification;
966+
967+
//classification = (byte)255;
968+
960969
//if (classification<0 || classification>1) Log.Write("****: " + classification.ToString());
961970

962-
//if (i < 20000) Log.Write("class: " + classification.ToString());
971+
//if (i < 10000) Log.Write("class: " + classification.ToString() + " minClass: " + minClass + " maxClass: " + maxClass);
963972
//classification = 0;
964973
//if (intensity.r < minInt)
965974
//{
@@ -975,7 +984,7 @@ static bool ParseFile(ImportSettings importSettings, int fileIndex, int? taskId,
975984
// if no rgb, then replace RGB with intensity
976985
if (importSettings.importRGB == false)
977986
{
978-
rgb.r = classification/255f;
987+
rgb.r = classification / 255f;
979988
rgb.g = rgb.r;
980989
rgb.b = rgb.r;
981990
}
@@ -1022,7 +1031,27 @@ static bool ParseFile(ImportSettings importSettings, int fileIndex, int? taskId,
10221031

10231032
//Log.Write(jsonString, LogEvent.File);
10241033

1025-
} // if importMetadataOnly == false
1034+
if (importSettings.importMetadata == true)
1035+
{
1036+
var metaData = taskReader.GetMetaData(importSettings, fileIndex);
1037+
// NOTE now its added to every file..
1038+
metaData.ConverterVersion = version;
1039+
metaData.MinClassification = minClass;
1040+
metaData.MaxClassification = maxClass;
1041+
lasHeaders.Add(metaData);
1042+
}
1043+
1044+
} // if importMetadataOnly == false ^
1045+
else // only metadata:
1046+
{
1047+
if (importSettings.importMetadata == true)
1048+
{
1049+
var metaData = taskReader.GetMetaData(importSettings, fileIndex);
1050+
// NOTE now its added to every file..
1051+
metaData.ConverterVersion = version;
1052+
lasHeaders.Add(metaData);
1053+
}
1054+
}
10261055

10271056
//Log.Write("taskid: " + taskId + " done");
10281057
return true;
@@ -1099,12 +1128,13 @@ void StartProcess(bool doProcess = true)
10991128
if (inputFile.Contains(" ")) inputFile = "\"" + inputFile + "\"";
11001129
if (outputFile.Contains(" ")) outputFile = "\"" + outputFile + "\"";
11011130

1102-
args.Add("-input=" + inputFile);
1103-
11041131
if (cmbImportFormat.SelectedItem != null)
11051132
{
11061133
args.Add("-importformat=" + cmbImportFormat.SelectedItem.ToString());
11071134
}
1135+
1136+
args.Add("-input=" + inputFile);
1137+
11081138
if (cmbExportFormat.SelectedItem != null)
11091139
{
11101140
args.Add("-exportformat=" + cmbExportFormat.SelectedItem.ToString());
@@ -1386,7 +1416,27 @@ private void btnBrowseInput_Click(object sender, RoutedEventArgs e)
13861416
// select single file
13871417
var dialog = new OpenFileDialog();
13881418
dialog.Title = "Select file to import";
1389-
dialog.Filter = "LAS|*.las;*.laz";
1419+
1420+
if (cmbImportFormat.SelectedItem != null)
1421+
{
1422+
var format = cmbImportFormat.SelectedItem.ToString();
1423+
if (format == "LAS" || format == "LAZ")
1424+
{
1425+
dialog.Filter = "LAS Files|*.las;*.laz|All Files|*.*";
1426+
}
1427+
else if (format == "PLY")
1428+
{
1429+
dialog.Filter = "PLY Files|*.ply|All Files|*.*";
1430+
}
1431+
else
1432+
{
1433+
dialog.Filter = "All Files|*.*";
1434+
}
1435+
}
1436+
else
1437+
{
1438+
dialog.Filter = "Point Cloud Files|*.las;*.laz;*.ply|LAS Files|*.las;*.laz|PLY Files|*.ply|All Files|*.*";
1439+
}
13901440

13911441
// take folder from field
13921442
if (string.IsNullOrEmpty(txtInputFile.Text) == false)

PointCloudConverter.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<ItemGroup>
3737
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
3838
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
39+
<PackageReference Include="Ply.Net" Version="5.5.5" />
3940
<PackageReference Include="System.Buffers" Version="4.5.1" />
4041
<PackageReference Include="System.Memory" Version="4.5.5" />
4142
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />

Readers/LAZ.cs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -383,19 +383,13 @@ byte IReader.GetIntensity()
383383

384384
byte IReader.GetClassification()
385385
{
386-
//float c = new Color();
387-
// get point reference
388386
var p = lazReader.point;
389-
//c.r = (Remap(p.extended_classification, 2, 112, 0, 1)); // works, but we dont know range ahead of time, unless read first all values in all files?
390-
//c.r = (Remap(p.extended_classification, 0, 255, 0, 1));
391-
byte c = p.extended_classification;// / 255f;
392-
//c.r = p.classification;
393-
//c.r = p.extended_classification;
394-
//float c = Tools.LUT255[(byte)(p.classification)];
395-
//Console.WriteLine(c.r);
396-
//c.g = c.r;
397-
//c.b = c.r;
398-
return c;
387+
// now reads both, we dont know which one is enabled?
388+
byte classification = p.classification;
389+
byte extended = p.extended_classification;
390+
// Choose extended if it's valid and not equal to default "unclassified"
391+
byte finalClassification = (extended > 0 && extended != classification) ? extended : classification;
392+
return finalClassification;
399393
}
400394

401395
Float3 IReader.GetXYZ()

Readers/PLY.cs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
using PointCloudConverter.Structs;
2+
using Ply.Net;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using Color = PointCloudConverter.Structs.Color;
8+
using System.Diagnostics;
9+
10+
namespace PointCloudConverter.Readers
11+
{
12+
public class PLY : IReader, IDisposable
13+
{
14+
private PlyParser.Dataset dataset;
15+
private int pointIndex;
16+
private int pointCount;
17+
18+
private PlyParser.PropertyData px, py, pz;
19+
private PlyParser.PropertyData pr, pg, pb;
20+
//private PlyParser.PropertyData pintensity, pclass, ptime;
21+
22+
private Float3 currentPoint;
23+
private Color currentColor;
24+
// private double currentTime;
25+
// private byte currentIntensity;
26+
// private byte currentClassification;
27+
private Bounds bounds;
28+
29+
public bool InitReader(ImportSettings importSettings, int fileIndex)
30+
{
31+
var file = importSettings.inputFiles[fileIndex];
32+
using var stream = File.OpenRead(file);
33+
dataset = PlyParser.Parse(stream, 1024);
34+
35+
//var info = PlyParser.ParseHeader(file);
36+
//var infoVertices = info.Elements.FirstOrDefault(x => x.Type == PlyParser.ElementType.Vertex);
37+
//Trace.WriteLine($"PLY: {file} has {infoVertices?.Count} vertices");
38+
39+
var vertexElement = dataset.Data.FirstOrDefault(d => d.Element.Type == PlyParser.ElementType.Vertex);
40+
if (vertexElement == null) return false;
41+
42+
pointCount = vertexElement.Data[0].Data.Length;
43+
44+
px = vertexElement["x"] ?? throw new Exception("Missing 'x' property in PLY file");
45+
py = vertexElement["y"] ?? throw new Exception("Missing 'y' property in PLY file");
46+
pz = vertexElement["z"] ?? throw new Exception("Missing 'z' property in PLY file");
47+
48+
pr = vertexElement["red"];
49+
pg = vertexElement["green"];
50+
pb = vertexElement["blue"];
51+
52+
Debug.WriteLine($"PLY: {file} has {pointCount} points");
53+
Debug.WriteLine($"PLY: {file} has {pr.Data.Length} pr values");
54+
55+
56+
//pa = vertexElement["alpha"];
57+
// pintensity = vertexElement["intensity"] ?? vertexElement["scalar_intensity"];
58+
// pclass = vertexElement["classification"] ?? vertexElement["scalar_classification"];
59+
// ptime = vertexElement["time"];
60+
61+
CalculateBounds();
62+
pointIndex = 0;
63+
64+
return true;
65+
}
66+
67+
public int GetPointCount() => pointCount;
68+
69+
public Bounds GetBounds() => bounds;
70+
71+
public Float3 GetXYZ()
72+
{
73+
if (pointIndex >= pointCount)
74+
return new Float3 { hasError = true };
75+
76+
currentPoint = new Float3
77+
{
78+
x = Convert.ToSingle(px.Data.GetValue(pointIndex)),
79+
y = Convert.ToSingle(py.Data.GetValue(pointIndex)),
80+
z = Convert.ToSingle(pz.Data.GetValue(pointIndex)),
81+
hasError = false
82+
};
83+
84+
//Trace.WriteLine($"PLY: {pointIndex} {pr.Data.GetValue(pointIndex)} {pg.Data.GetValue(pointIndex)} {pb.Data.GetValue(pointIndex)}");
85+
currentColor = new Color
86+
{
87+
r = pr != null ? Convert.ToSingle(Convert.ToByte(pr.Data.GetValue(pointIndex))) / 255f : 1f,
88+
g = pg != null ? Convert.ToSingle(Convert.ToByte(pg.Data.GetValue(pointIndex))) / 255f : 1f,
89+
b = pb != null ? Convert.ToSingle(Convert.ToByte(pb.Data.GetValue(pointIndex))) / 255f : 1f
90+
};
91+
92+
93+
//Trace.WriteLine($"PLY: {pointIndex} {currentColor.r} {currentColor.g} {currentColor.b}");
94+
95+
// currentIntensity = pintensity != null ? Convert.ToByte(pintensity.Data.GetValue(pointIndex)) : (byte)0;
96+
// currentClassification = pclass != null ? Convert.ToByte(pclass.Data.GetValue(pointIndex)) : (byte)0;
97+
// currentTime = ptime != null ? Convert.ToDouble(ptime.Data.GetValue(pointIndex)) : 0.0;
98+
99+
pointIndex++;
100+
return currentPoint;
101+
}
102+
103+
public Color GetRGB()
104+
{
105+
//currentColor = new Color();
106+
//currentColor.r = 255;
107+
//currentColor.g = 0;
108+
//currentColor.b = 0;
109+
return currentColor;
110+
}
111+
112+
public double GetTime()
113+
{
114+
return 0.0;
115+
}
116+
117+
public byte GetIntensity()
118+
{
119+
return 0;
120+
}
121+
122+
public byte GetClassification()
123+
{
124+
return 0;
125+
}
126+
127+
// TODO return ply data
128+
public LasHeader GetMetaData(ImportSettings importSettings, int fileIndex)
129+
{
130+
return new LasHeader
131+
{
132+
FileName = importSettings.inputFiles[fileIndex],
133+
NumberOfPointRecords = (uint)pointCount,
134+
MinX = bounds.minX,
135+
MaxX = bounds.maxX,
136+
MinY = bounds.minY,
137+
MaxY = bounds.maxY,
138+
MinZ = bounds.minZ,
139+
MaxZ = bounds.maxZ
140+
};
141+
}
142+
143+
public void Close()
144+
{
145+
dataset = null;
146+
}
147+
148+
public void Dispose() => Close();
149+
150+
private void CalculateBounds()
151+
{
152+
// NOTE doesnt support BINARY ply
153+
154+
// need to calculate manually
155+
bounds = new Bounds
156+
{
157+
minX = float.MaxValue,
158+
maxX = float.MinValue,
159+
minY = float.MaxValue,
160+
maxY = float.MinValue,
161+
minZ = float.MaxValue,
162+
maxZ = float.MinValue
163+
};
164+
165+
for (int i = 0; i < pointCount; i++)
166+
{
167+
float x = Convert.ToSingle(px.Data.GetValue(i));
168+
float y = Convert.ToSingle(py.Data.GetValue(i));
169+
float z = Convert.ToSingle(pz.Data.GetValue(i));
170+
171+
bounds.minX = Math.Min(bounds.minX, x);
172+
bounds.maxX = Math.Max(bounds.maxX, x);
173+
bounds.minY = Math.Min(bounds.minY, y);
174+
bounds.maxY = Math.Max(bounds.maxY, y);
175+
bounds.minZ = Math.Min(bounds.minZ, z);
176+
bounds.maxZ = Math.Max(bounds.maxZ, z);
177+
}
178+
}
179+
}
180+
}

Structs/ImportFormat.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
public enum ImportFormat
44
{
55
Unknown,
6-
LAS // and LAZ
6+
LAS, // and LAZ
7+
PLY
78
}
89
}

0 commit comments

Comments
 (0)