///|
/// Export and validation helpers for point-cloud service boundaries.
pub fn point_cloud_to_flat(points : ArrayView[Point3]) -> Array[Double] {
let result : Array[Double] = []
for point in points {
result.push(point.x)
result.push(point.y)
result.push(point.z)
}
result
}
///|
pub fn point_cloud_from_flat(values : ArrayView[Double]) -> Array[Point3] {
let result : Array[Point3] = []
let count = values.length() / 3
for i in 0.. Int {
values.length() / 3
}
///|
pub fn point_cloud_flat_remainder(values : ArrayView[Double]) -> Int {
values.length() % 3
}
///|
pub fn point_cloud_flat_is_valid(values : ArrayView[Double]) -> Bool {
values.length() % 3 == 0
}
///|
pub fn point_cloud_count_finite(points : ArrayView[Point3]) -> Int {
let mut result = 0
for point in points {
if !point.x.is_nan() && !point.y.is_nan() && !point.z.is_nan() {
result += 1
}
}
result
}
///|
pub fn point_cloud_finite_ratio(points : ArrayView[Point3]) -> Double {
if points.length() == 0 {
0.0
} else {
point_cloud_count_finite(points).to_double() / points.length().to_double()
}
}
///|
pub fn point_cloud_validate(
points : ArrayView[Point3],
minimum_count : Int,
minimum_finite_ratio : Double,
) -> Bool {
points.length() >= minimum_count.max(0) &&
point_cloud_finite_ratio(points) >= minimum_finite_ratio
}
///|
pub fn point_cloud_json_number(value : Double) -> String {
if value.is_nan() {
"null"
} else {
value.to_string()
}
}
///|
pub fn point_cloud_csv_row(point : Point3) -> String {
point_cloud_json_number(point.x) +
"," +
point_cloud_json_number(point.y) +
"," +
point_cloud_json_number(point.z)
}
///|
pub fn point_cloud_csv(points : ArrayView[Point3]) -> String {
let mut result = "x,y,z"
for point in points {
result += "\n" + point_cloud_csv_row(point)
}
result
}
///|
pub fn point_cloud_checksum(points : ArrayView[Point3]) -> Double {
let mut result = 0.0
for i in 0.. Bool {
(point_cloud_checksum(points) - checksum).abs() <= tolerance.max(0.0)
}
///|
pub fn point_cloud_batch_checksums(
batches : ArrayView[Array[Point3]],
) -> Array[Double] {
let result : Array[Double] = []
for batch in batches {
result.push(point_cloud_checksum(batch))
}
result
}
///|
pub fn point_cloud_batch_total(batches : ArrayView[Array[Point3]]) -> Int {
let mut result = 0
for batch in batches {
result += batch.length()
}
result
}
///|
pub fn point_cloud_batch_flatten(
batches : ArrayView[Array[Point3]],
) -> Array[Point3] {
let result : Array[Point3] = []
for batch in batches {
for point in batch {
result.push(point)
}
}
result
}
///|
pub fn point_cloud_batch_nonempty(
batches : ArrayView[Array[Point3]],
) -> Array[Array[Point3]] {
let result : Array[Array[Point3]] = []
for batch in batches {
if batch.length() > 0 {
result.push(batch)
}
}
result
}
///|
pub fn point_cloud_batch_largest(
batches : ArrayView[Array[Point3]],
) -> Array[Point3] {
if batches.length() == 0 {
[]
} else {
let mut result = batches[0]
for batch in batches[1:] {
if batch.length() > result.length() {
result = batch
}
}
result
}
}
///|
pub fn point_cloud_batch_smallest(
batches : ArrayView[Array[Point3]],
) -> Array[Point3] {
if batches.length() == 0 {
[]
} else {
let mut result = batches[0]
for batch in batches[1:] {
if batch.length() < result.length() {
result = batch
}
}
result
}
}
///|
pub fn point_cloud_batch_quality(
batches : ArrayView[Array[Point3]],
minimum_count : Int,
maximum_radius : Double,
) -> Array[Double] {
let result : Array[Double] = []
for batch in batches {
result.push(point_cloud_quality_score(batch, minimum_count, maximum_radius))
}
result
}
///|
pub fn point_cloud_quality_mean(scores : ArrayView[Double]) -> Double {
mean(scores) catch {
_ => 0.0
}
}
///|
pub fn point_cloud_quality_minimum(scores : ArrayView[Double]) -> Double {
if scores.length() == 0 {
0.0
} else {
let mut result = scores[0]
for score in scores {
result = result.min(score)
}
result
}
}
///|
pub fn point_cloud_quality_maximum(scores : ArrayView[Double]) -> Double {
let mut result = 0.0
for score in scores {
result = result.max(score)
}
result
}
///|
pub fn point_cloud_quality_pass_count(
scores : ArrayView[Double],
threshold : Double,
) -> Int {
let mut result = 0
for score in scores {
if score >= threshold {
result += 1
}
}
result
}
///|
pub fn point_cloud_quality_pass_ratio(
scores : ArrayView[Double],
threshold : Double,
) -> Double {
if scores.length() == 0 {
0.0
} else {
point_cloud_quality_pass_count(scores, threshold).to_double() /
scores.length().to_double()
}
}
///|
pub fn point_cloud_quality_label(score : Double) -> String {
if score >= 0.8 {
"good"
} else if score >= 0.5 {
"review"
} else {
"poor"
}
}
///|
pub fn point_cloud_quality_labels(scores : ArrayView[Double]) -> Array[String] {
let result : Array[String] = []
for score in scores {
result.push(point_cloud_quality_label(score))
}
result
}
///|
pub fn point_cloud_quality_weighted(
scores : ArrayView[Double],
weights : ArrayView[Double],
) -> Double {
let count = scores.length().min(weights.length())
let mut total = 0.0
let mut scale = 0.0
for i in 0.. Double {
point_cloud_quality_mean(after) - point_cloud_quality_mean(before)
}
///|
pub fn point_cloud_quality_improved(
before : ArrayView[Double],
after : ArrayView[Double],
tolerance : Double,
) -> Bool {
point_cloud_quality_gap(before, after) >= tolerance
}
///|
pub fn point_cloud_quality_report(
points : ArrayView[Point3],
minimum_count : Int,
maximum_radius : Double,
) -> String {
let summary = point_cloud_pipeline_summary(points)
let score = point_cloud_quality_score(points, minimum_count, maximum_radius)
summary.count.to_string() +
"," +
summary.radius.to_string() +
"," +
score.to_string() +
"," +
point_cloud_quality_label(score)
}