///|
pub fn inspection_report(
part_id : String,
image : GrayImage,
measurements : Array[Measurement],
diagnostics : Array[PipelineDiagnostic],
) -> MeasurementReport {
let notes : Array[String] = []
notes.push(diagnostics_to_markdown(diagnostics))
notes.push(
"image=" + image.width.to_string() + "x" + image.height.to_string(),
)
{ part_id, measurements, notes }
}
///|
pub fn inspect_rectangle(
_image : GrayImage,
bounds : Bounds,
nominal_width : Double,
nominal_height : Double,
scale : Double,
) -> PipelineResult {
let width = bounding_width(bounds) * scale
let height = bounding_height(bounds) * scale
let width_tolerance = Tolerance::Tolerance(
nominal=nominal_width,
lower=nominal_width * 0.99,
upper=nominal_width * 1.01,
unit="mm",
)
let height_tolerance = Tolerance::Tolerance(
nominal=nominal_height,
lower=nominal_height * 0.99,
upper=nominal_height * 1.01,
unit="mm",
)
suite_result(
apply_tolerances(
[
{
name: "rectangle-width",
value: width,
unit: "mm",
decision: Pass,
deviation: width - nominal_width,
},
{
name: "rectangle-height",
value: height,
unit: "mm",
decision: Pass,
deviation: height - nominal_height,
},
],
[width_tolerance, height_tolerance],
),
)
}
///|
pub fn inspect_circle(
image : GrayImage,
center : Point,
radius : Double,
threshold : Double,
scale : Double,
nominal_diameter : Double,
) -> PipelineResult {
let mask = threshold_image(image, threshold)
let component = largest_component(mask)
let points = component_pixels(mask, component.label)
let circle = if points.length() >= 3 {
detect_circle_from_points(points)
} else {
Circle::Circle(center~, radius~, rms_error=0.0)
}
let tolerance = Tolerance::Tolerance(
nominal=nominal_diameter,
lower=nominal_diameter * 0.99,
upper=nominal_diameter * 1.01,
unit="mm",
)
let measurement = analyze_gap(
"circle-diameter",
measure_diameter(circle) * scale,
1.0,
tolerance,
)
suite_result([measurement])
}
///|
pub fn inspect_slot(
image : GrayImage,
bounds : Bounds,
scale : Double,
nominal : Double,
) -> PipelineResult {
let mask = threshold_image(
crop_image(image, bounds),
otsu_threshold(crop_image(image, bounds)),
)
let components = label_components(mask)
let largest = largest_component(mask)
let value = if components.is_empty() {
0.0
} else {
bounding_width(largest.bounds) * scale
}
let tolerance = Tolerance::Tolerance(
nominal~,
lower=nominal * 0.98,
upper=nominal * 1.02,
unit="mm",
)
suite_result([analyze_gap("slot-width", value, 1.0, tolerance)])
}
///|
pub fn inspect_edge_pair(
image : GrayImage,
config : CaliperConfig,
scale : Double,
tolerance : Tolerance,
) -> PipelineResult {
measure_caliper_with_scale(image, config, scale, tolerance)
}
///|
pub fn inspect_batch(
images : Array[GrayImage],
config : CaliperConfig,
tolerance : Tolerance,
) -> BatchRun {
process_frames(images, config, tolerance)
}
///|
pub fn inspection_acceptance(
result : PipelineResult,
minimum_confidence : Double,
) -> Bool {
result.accepted &&
result.confidence >= minimum_confidence.clamp(min=0.0, max=1.0) &&
diagnostics_ok(result.diagnostics)
}
///|
pub fn inspection_status(result : PipelineResult) -> String {
if inspection_acceptance(result, 0.5) {
"ACCEPT"
} else {
"REJECT"
}
}
///|
pub fn inspection_summary(result : PipelineResult) -> String {
inspection_status(result) +
"; confidence=" +
result.confidence.to_string() +
"; measurements=" +
result.measurements.length().to_string() +
"; diagnostics=" +
result.diagnostics.length().to_string()
}
///|
pub fn inspection_markdown(
part_id : String,
image : GrayImage,
result : PipelineResult,
) -> String {
let report = inspection_report(
part_id,
image,
result.measurements,
result.diagnostics,
)
render_markdown_report(report)
}
///|
pub fn inspection_csv(result : PipelineResult) -> String {
render_measurements_csv(result.measurements)
}
///|
pub fn inspection_diagnostics(result : PipelineResult) -> String {
diagnostics_to_markdown(result.diagnostics)
}
///|
pub fn inspection_quality(
image : GrayImage,
result : PipelineResult,
) -> QualitySummary {
let profile = summarize_profile(
edge_profile(
image,
Point::Point(x=0.0, y=Double::from_int(image.height / 2)),
Point::Point(
x=Double::from_int((image.width - 1).max(0)),
y=Double::from_int(image.height / 2),
),
),
0.05,
)
let summary = profile_quality(profile, 2.0)
{ ..summary, confidence: summary.confidence * result.confidence }
}
///|
pub fn inspection_events(
image : GrayImage,
result : PipelineResult,
) -> Array[DiagnosticEvent] {
let result_events = diagnostics_for_image(image)
for measurement in result.measurements {
result_events.push(
diagnostic_event(
"MEASUREMENT",
measurement.name,
"value",
measurement.value,
),
)
}
result_events
}
///|
pub fn inspection_fingerprint(
image : GrayImage,
result : PipelineResult,
) -> String {
image_checksum(image).to_string() +
":" +
result.confidence.to_string() +
":" +
decision_name(pipeline_decision(result))
}
///|
pub fn inspection_compare(
first : PipelineResult,
second : PipelineResult,
) -> Array[Measurement] {
measurement_delta(first.measurements, second.measurements)
}
///|
pub fn inspection_merge(
first : PipelineResult,
second : PipelineResult,
) -> PipelineResult {
suite_merge(first, second)
}
///|
pub fn inspection_retry(
image : GrayImage,
config : CaliperConfig,
tolerance : Tolerance,
attempts : Int,
) -> PipelineResult {
let mut result = process_frame(image, config, tolerance)
let mut attempt = 1
while !result.accepted && attempt < attempts.max(1) {
let smoother = gaussian_blur(
image,
attempt,
1.0 + Double::from_int(attempt),
)
result = process_frame(smoother, config, tolerance)
attempt += 1
}
result
}
///|
pub fn inspection_with_preprocessing(
image : GrayImage,
config : CaliperConfig,
tolerance : Tolerance,
blur_radius : Int,
) -> PipelineResult {
let filtered = median_filter(
gaussian_blur(image, blur_radius, 1.0),
radius=blur_radius,
)
process_frame(filtered, config, tolerance)
}
///|
pub fn inspection_with_roi(
image : GrayImage,
roi : Roi,
config : CaliperConfig,
tolerance : Tolerance,
) -> PipelineResult {
let masked = roi.masked_image(image, 0.0)
process_frame(masked, config, tolerance)
}
///|
pub fn inspection_with_calibration(
image : GrayImage,
config : CaliperConfig,
tolerance : Tolerance,
fit : CalibrationFit,
) -> PipelineResult {
let raw = process_frame(image, config, tolerance)
{
..raw,
measurements: raw.measurements.map(fn(measurement) {
calibration_apply_to_measurement(fit, measurement)
}),
}
}
///|
pub fn inspection_with_uncertainty(
result : PipelineResult,
uncertainty : UncertaintyEstimate,
tolerance : Tolerance,
) -> PipelineResult {
let measurements = result.measurements.map(fn(measurement) {
{
..measurement,
decision: guard_band_decision(measurement.value, tolerance, uncertainty),
}
})
{
..result,
measurements,
accepted: measurements.all(fn(measurement) { measurement.decision != Fail }),
}
}
///|
pub fn inspection_all(
image : GrayImage,
calipers : Array[CaliperConfig],
tolerance : Tolerance,
fit : CalibrationFit?,
) -> PipelineResult {
let raw = run_caliper_suite(image, calipers, tolerance)
match fit {
Some(model) =>
{
..raw,
measurements: raw.measurements.map(fn(measurement) {
calibration_apply_to_measurement(model, measurement)
}),
}
None => raw
}
}
///|
pub fn inspection_version() -> String {
"moonbit-visual-caliper/0.2.0"
}
///|
pub fn inspection_capabilities() -> Array[String] {
[
"gray-image", "profiles", "edges", "morphology", "components", "contours", "calibration",
"calipers", "tolerances", "reports", "benchmarks",
]
}