///|
pub fn radial_caliper_profile(
image : GrayImage,
center : Point,
angle : Double,
length : Double,
samples : Int,
) -> Array[ProfileSample] {
let direction = Vector2::Vector2(x=@math.cos(angle), y=@math.sin(angle))
edge_profile(
image,
center,
Point::Point(
x=center.x + direction.x * length,
y=center.y + direction.y * length,
),
steps=samples,
)
}
///|
pub fn radial_edge_points(
image : GrayImage,
center : Point,
angles : Array[Double],
length : Double,
threshold : Double,
) -> Array[EdgePoint] {
let result : Array[EdgePoint] = []
for angle in angles {
let profile = radial_caliper_profile(image, center, angle, length, 128)
let edges = ProfileSample::detect_edges(profile, threshold)
if !edges.is_empty() {
let strongest = edges[0]
let mut best = strongest
for edge in edges[1:] {
if edge.strength > best.strength {
best = edge
}
}
result.push(best)
}
}
result
}
///|
pub fn radial_circle_measurement(
image : GrayImage,
center : Point,
angles : Array[Double],
length : Double,
threshold : Double,
_unit : String,
) -> Circle {
let edges = radial_edge_points(image, center, angles, length, threshold)
let points = edges.map(fn(edge) { edge.point })
if points.length() < 3 {
Circle::Circle(center~, radius=0.0, rms_error=0.0)
} else {
let circle = detect_circle_from_points(points)
{ ..circle, radius: circle.radius, rms_error: circle.rms_error }
}
}
///|
pub fn multi_angle_widths(
image : GrayImage,
center : Point,
angles : Array[Double],
length : Double,
threshold : Double,
) -> Array[Double] {
let result : Array[Double] = []
for angle in angles {
let direction = Vector2::Vector2(x=@math.cos(angle), y=@math.sin(angle))
let start = Point::Point(
x=center.x - direction.x * length,
y=center.y - direction.y * length,
)
let finish = Point::Point(
x=center.x + direction.x * length,
y=center.y + direction.y * length,
)
let profile = summarize_profile(
edge_profile(image, start, finish),
threshold,
)
if profile.edges.length() >= 2 {
result.push(edge_pair_width(profile.edges, direction))
}
}
result
}
///|
pub fn width_statistics_by_angle(
image : GrayImage,
center : Point,
angle_count : Int,
length : Double,
threshold : Double,
) -> StatsSummary {
let angles : Array[Double] = []
for i in 0.. Array[CaliperConfig] {
let result : Array[CaliperConfig] = []
for i in 0.. PipelineResult {
let measurements : Array[Measurement] = []
let diagnostics : Array[PipelineDiagnostic] = []
let mut accepted = true
let mut confidence = 1.0
for config in configs {
let current = measure_caliper(image, config, tolerance)
measurements.append(current.measurements)
diagnostics.append(current.diagnostics)
accepted = accepted && current.accepted
confidence *= current.confidence
}
{ measurements, diagnostics, accepted, confidence }
}
///|
pub fn caliper_suite_widths(result : PipelineResult) -> Array[Double] {
measurement_values(result.measurements)
}
///|
pub fn caliper_suite_spread(result : PipelineResult) -> Double {
repeatability(caliper_suite_widths(result))
}
///|
pub fn caliper_suite_report(
result : PipelineResult,
part_id : String,
) -> String {
render_pipeline_markdown(result, part_id) +
"\nRepeatability: " +
caliper_suite_spread(result).to_string()
}
///|
pub fn edge_distance_along_ray(
edge : EdgePoint,
center : Point,
direction : Vector2,
) -> Double {
Vector2::Vector2(x=edge.point.x - center.x, y=edge.point.y - center.y).dot(
direction.normalized(),
)
}
///|
pub fn sort_edges_by_distance(
edges : Array[EdgePoint],
center : Point,
direction : Vector2,
) -> Array[EdgePoint] {
let result : Array[EdgePoint] = []
for edge in edges {
let distance_value = edge_distance_along_ray(edge, center, direction)
let mut index = 0
while index < result.length() &&
edge_distance_along_ray(result[index], center, direction) <=
distance_value {
index += 1
}
result.insert(index, edge)
}
result
}
///|
pub fn nearest_edge(edges : Array[EdgePoint], point : Point) -> EdgePoint? {
if edges.is_empty() {
None
} else {
let mut result = edges[0]
let mut best = distance(result.point, point)
for edge in edges[1:] {
let current = distance(edge.point, point)
if current < best {
result = edge
best = current
}
}
Some(result)
}
}
///|
pub fn strongest_edges(
edges : Array[EdgePoint],
minimum_strength : Double,
) -> Array[EdgePoint] {
edges.filter(fn(edge) { edge.strength >= minimum_strength })
}
///|
pub fn merge_nearby_edges(
edges : Array[EdgePoint],
distance_limit : Double,
) -> Array[EdgePoint] {
let result : Array[EdgePoint] = []
for edge in edges {
let nearby = nearest_edge(result, edge.point)
match nearby {
Some(existing) =>
if distance(existing.point, edge.point) <= distance_limit {
if edge.strength > existing.strength {
for i in 0.. result.push(edge)
}
}
result
}
///|
pub fn edge_confidence(
edge : EdgePoint,
profile_noise : Double,
threshold : Double,
) -> Double {
let denominator = profile_noise.max(0.0000001) + threshold.max(0.0)
clamp_double(edge.strength / denominator, 0.0, 1.0)
}
///|
pub fn caliper_direction(config : CaliperConfig) -> Vector2 {
Vector2::Vector2(
x=config.finish.x - config.start.x,
y=config.finish.y - config.start.y,
).normalized()
}
///|
pub fn caliper_length(config : CaliperConfig) -> Double {
distance(config.start, config.finish)
}
///|
pub fn caliper_midpoint(config : CaliperConfig) -> Point {
Point::Point(
x=(config.start.x + config.finish.x) / 2.0,
y=(config.start.y + config.finish.y) / 2.0,
)
}
///|
pub fn caliper_is_valid(config : CaliperConfig) -> Bool {
caliper_length(config) > 0.0 &&
config.expected_edges > 0 &&
config.threshold >= 0.0
}
///|
pub fn caliper_config_report(config : CaliperConfig) -> String {
"start=(" +
config.start.x.to_string() +
"," +
config.start.y.to_string() +
"); finish=(" +
config.finish.x.to_string() +
"," +
config.finish.y.to_string() +
"); threshold=" +
config.threshold.to_string()
}