AM-Quality¶
This section covers usage of additional API endpoints and models for AM-Quality.
Quality Control¶
The Scan endpoint has 4 quality-control related values:
qc_machine[True/False/None]: The pass/fail (True/False) judgment of the machine. None if the scan is unfinished or has failed.qc_operator[True/False/None]: The pass/fail (True/False) review judgment of a human operator. None if the scan has not been reviewed.qc_needs_review[True/False]: Whether the scan needs to be reviewed by a human operator. This is determined by the review policy, and can be overridden by the decision tree (see below). This can only be True ifqc_operatoris None (e.g. already reviewed scans can never need review).qc_pass[True/False/None]: The final pass/fail decision. This is a combination of the fields above.
The values for qc_pass and qc_needs_review are combined in one icon on the qc_status column on the review page. There are only three possibilities:
qc_passis True (✓ icon)qc_passis False (x icon)qc_passis None andqc_needs_reviewis True (! icon)
The value of qc_pass is determined by the following rules:
qc_passfollows the operator judgment if there is oneif there is no operator judgment, and the part does not need reviewing, then
qc_passfollows the machine judgmentotherwise
qc_passis None
or in pseudo-code:
if qc_operator is not None:
qc_pass = qc_operator
elif qc_needs_review is False:
qc_pass = qc_machine
else:
qc_pass = None
Operators have three ways to review scans:
Open the inspection page, inspect the scan, and press approve/reject.
Select the scans to review (using the checkboxes on the left and optionally the QC Status filter), and bulk approve/reject them
Select the scans to review as above, unassign them, and rescan them in “review mode”. In this mode, the machine will ask the operator for a judgment directly after each scan.
Review policies¶
The review policy determines whether a scan needs reviewing by a human operator.
In other words, it sets the qc_needs_review value based on the value of qc_machine. These are the possible policies:
REVIEW_FAILED[default]: Review scan whenqc_machineis False or NoneREVIEW_PASSED: Review scan whenqc_machineis True or NoneREVIEW_MISSING: Review scan whenqc_machineis None (when the scan has failed to process)REVIEW_ALL: Review all scansREVIEW_NONE: Review no scans
The policy can be changed in the settings page in the admin.
Decision trees can override the review policy and force the value of qc_needs_review.
Inspection reports¶
Comparing to AM-Vision API, there is a new endpoint inspection_report. The inspection report is the result of the quality analysis.
Each scan in the system can have exactly one or zero inspection reports. The scan is created first (at part entry), and the inspection report is added after the analysis is ready.
An inspection report carries the information of a quality scan, the evaluation metrics,
and metrology data (in case a metrology template is present, see Metrology templates to know how to use
the api to upload a .xvgt file and link it to a part).
Main fields of the inspection report are:
- uuid:
A unique id
- metrics:
dict, measurements used to determine the automatic PASS/FAIL verdict (see section Inspection profile, inspection criterium and metrics for a full overview)
- metrology_data:
dict, results for the measurements specified in the MetrologyTemplate
- scan:
string, The scan uuid for which this report was produced
- part:
string, id of the part (NOT uuid)
- pdf_url:
string, the url to download the PDF version of this report
Here’s an example to retrieve inspection reports with the Slumber API client defined earlier in this tutorial:
# retrieve the inspection report for a given scan
res = api.inspection_report.get(scan=scan_uuid)
inspection_report = res["results"][0]
# get the PDF binary content from 'render_pdf' endpoint
content = api.inspection_report(inspection_report["uuid"]).render_pdf.get()
Metrology templates¶
Metrology templates are .xvgt files that you can get from VGStudio Max software.
You can upload .xvgt files that specify what to measure on the product by using the api endpoint api/metrology_template/.
The following example uses the Slumber API client to upload a metrology template and updates a part with its id:
template_path = "/path/to/xvgt/file"
api.metrology_template.post({"id": "my_xvg_template", files={"template": open(template_path, "rb")})
# link the metrology template to an existing part in the database
part_id = "batch_01_part_1"
# set the metrology_template attribute with the id
api.part(part_id).patch({"metrology_template": "my_xvg_template"}))
Inspection profile, inspection criterium and metrics¶
An inspection profile is a collection of criteria. These criteria define the metrics to compute according their thresholds. An inspection profile can be defined for a part or for a material. AM-Quality will use the default inspection profile defined in the settings if there is no inspection profile for the part under evaluation or its recognized material.
An inspection criterium is defined by a metric (chosen between the list of available metrics) and parameters thresholds.
The available metrics and their parameters are currently:
Criterium |
Parameters |
|---|---|
points_percent |
distance_threshold (mm): Minimum deviation distance to consider a point as defective |
“ |
reject_threshold (%): Maximum percentage of points that can exceed the distance threshold before the part fails |
bbox_deviation |
threshold (mm): Maximum deviation of the most deviated bounding box axis between the scanned part and reference part |
bbox_deviation_percent |
threshold (%): Maximum deviation of the most deviated bounding box axis as a percentage of the reference part |
missing_area |
threshold (mm²): Maximum size for the largest missing area on the part |
missing_area_percent |
threshold (%): Maximum size of the largest missing area as a percentage of total part area |
mean_absolute_error |
threshold (mm): Maximum mean absolute error between scanned and reference surfaces |
median_absolute_error |
threshold (mm): Maximum median absolute error between scanned and reference surfaces |
rmse |
threshold (mm): Maximum root mean squared error between scanned and reference surfaces |
largest_area |
min_deviation_mm (mm): Minimum deviation distance to identify defective patches |
“ |
max_area_mm (mm²): Maximum area for the largest defective patch |
largest_area_percent |
min_deviation_mm (mm): Minimum deviation distance to identify defective patches |
“ |
max_area_pct (%): Maximum area of the largest defective patch as percentage of total surface |
total_area |
min_deviation_mm (mm): Minimum deviation distance to identify defective patches |
“ |
max_area_mm (mm²): Maximum combined area of all defective patches |
total_area_percent |
min_deviation_mm (mm): Minimum deviation distance to identify defective patches |
“ |
max_area_pct (%): Maximum combined area of all defective patches as percentage of total surface |
max_distance |
threshold (mm): Maximum root mean squared error between scanned and reference surfaces |
Here’s an example of usage to create a profile and some criteria for it.
# create profile
res = api.inspection_profile.post({"name": "Test"})
profile_uuid = res["uuid"]
# prepare data for the criteria
data = [{
"profile": profile_uuid,
"metric": "points_percent",
"order": 1,
"parameters": {"distance_threshold": 0.5, "reject_threshold": 0.5},
},
{
"profile": profile_uuid,
"metric": "bbox_deviation",
"order": 2,
"parameters": {"threshold": 0.5},
},
{
"profile": profile_uuid,
"metric": "missing_area",
"order": 3,
"parameters": {"threshold": 0.5},
}]
# create criteria for the profile
api.inspection_criteria.post(data)
# Assign the profile to a part
api.part(part_id).patch({"inspection_profile": profile_uuid})
Decision Trees¶
The automatic PASS/FAIL evaluation of an inspection report is based on the enabled criteria.
If all of them pass, the inspection report will have passed=True.
If one of them fails, it will have passed=False.
If metrics evaluation needs more flexibility, you can add a custom decision tree to an inspection profile.
Decision Tree YAML Syntax¶
This YAML format represents a binary decision tree used for classification or decision-making based on metric thresholds.
metric: <<metric_id>>
operator: [>,<,=,==,!=,<>,>=,<=]
threshold: <<float number>>
left: true # true path
right: false # false path
Keys left and right are either terminal leaf nodes, or define another non-leaf node in a recursive manner.
Each node in the tree contains:
metric: The feature or measurement being evaluated (e.g., bbox_deviation, missing_area)
operator: The comparison operator to apply (<, >, <=, >=, ==, =, !=, <>)
threshold: The numeric value to compare against
left: The branch to follow when the condition is true (metric compared to threshold using operator)
right: The branch to follow when the condition is false
Leaf Node format
Branches terminate with leaf nodes that represent the final classification or decision outcome. Leaf nodes can be either boolean values (true or false) or a dictionary with the following format:
passed: <<boolean>>
needs_review: <<optional boolean>>
comment: <<optional str>>
passed: the final pass/fail decision needs_review: whether the scan should be reviewed by an operator. Uses the default review policy if omitted. comment: an optional string explaining the decision (e.g. “Faulty alignment”)
Example Interpretation¶
Using the provided tree:
metric: bbox_deviation
operator: <
threshold: 0.6
left:
metric: missing_area
operator: <
threshold: 600
left:
metric: points_percent>0.50mm
operator: <
threshold: 7
left:
passed: true
needs_review: false
comment: null
right:
passed: false
needs_review: true
comment: "misaligned, rescan"
right: false
right: false
right: false
Root node: If bbox_deviation < 0.6 → go left, else return false
Second level: If missing_area < 600 → go left, else return false
Third level: If points_percent>0.50mm < 7 → return true, else return false
In this case, the tree returns true only when all three conditions are satisfied.
Notes¶
Metric names can contain special characters (like
>inpoints_percent>0.50mm)The tree is evaluated recursively from root to leaf
Each internal node represents a decision point; each leaf represents a final outcome
To use a decision tree, add it to the InspectionProfile decision_tree field and set the use_decision_tree to a true value, via AM-Quality API or the Django Admin.
A validation of the tree is performed unless use_decision_tree is false.
For the metric, use the metric_id field from profile criteria already defined.
It’s not possible to add a decision tree if there aren’t any criteria.
To inspect the available metrics/criteria and their metric_id, use the API or go to Django Admin.
While adding new nodes to the decision tree, or while changing an InspectionCriterium in a way it changes its metric_id, the decision tree might be inconsistent.
To avoid unnecessary validation errors, first disable the use_decision_tree for the profile then apply changes to criteria and decision tree,
and re-enable the use_decision_tree flag. This will properly validate the decision tree.
API examples¶
from slumber.exceptions import HttpClientError
# Create first a new profile
profile = {"name": "Test"}
res = api.inspection_profile.post(profile)
profile["uuid"] = res["uuid"]
# add criteria for the profile
criteria = [
{
"metric": "points_percent",
"order": 1,
"profile": profile["uuid"],
"parameters": {"distance_threshold": 0.5, "reject_threshold": 0.5},
},
{
"metric": "bbox_deviation",
"order": 2,
"profile": profile["uuid"],
"parameters": {"threshold": 0.5},
},
{
"metric": "missing_area",
"order": 3,
"profile": profile["uuid"],
"parameters": {"threshold": 0.5},
},
]
api.inspection_criterium.post(criteria)
# add the decision tree
decision_tree = """metric: bbox_deviation
operator: <
threshold: 0.6
left:
metric: missing_area
operator: <
threshold: 600
left:
metric: points_percent>0.50mm
operator: <
threshold: 7
left: true
right: false
right: false
right: false
"""
profile["decision_tree"] = decision_tree
profile["use_decision_tree"] = True
api.inspection_profile(profile["uuid"]).put(profile)
# Post an invalid decision tree
invalid_tree = """metric: bbox_deviation_invalid
operator: <
threshold: 0.6
left: true
right: false
"""
profile["decision_tree"] = invalid_tree
try:
res = api.inspection_profile(profile["uuid"]).put(profile)
except HttpClientError as e:
print(e.response.status_code) # 400
print(e.response.json())
# {'decision_tree': [
# 'Invalid decision tree: Invalid metric name at root: bbox_deviation_invalid'
# ]}
Webhook scan.quality_control¶
The scan.quality_control webhook is an additional event that the AM-Quality backend sends when anything happens to the quality control aspect of the scan during normal operation. There are several triggers for it:
the part has been scanned by the gocators and evaluated by evalign. this means a POST of an inspectionreport, a call to on_machine_evaluation, and a change of
qc_machinefrom None to either True or Falsethe part was approved or rejected by the operator. This causes a change of
qc_operator(or no change, in which case no webhook is needed)the scan gets a
qc_commentset by the operator.
You can register and listen to this events for further processing (e.g. align with your systems or export CSV/PDF reports):
api.webhook.post({
'event': 'scan.quality_control',
'target': 'http://yourapi.com/on_quality_control/'
})
The target endpoint callback receives a payload with the scan information (see the Webhooks section for more details).
Below is an example scan.quality_control webhook receiver. it listens to the webhook, and downloads a pdf version of the report:
# Flask python code for the callback view previously defined in ``target``
from flask import Flask, request
def on_quality_control():
data = request.json["data"]
# scan_id and report_id can be retrieved from the scan object
scan_id = data["uuid"]
report_id = data["inspectionreport"]
log.info("Scan %s had a quality control event", scan_id)
# failed or incomplete scans have no report id
if not report_id:
log.warning("Scan %s does not have an inspection report", scan_id)
return
# a pdf version of the report can be downloaded
log.info("Downloading pdf for report %s from scan %s", report_id, scan_id)
content = api.inspection_report(report_id).render_pdf.get()
with open("./report.pdf", "wb") as f:
f.write(content)
app = Flask(__name__)
app.add_url_rule(
rule="/on_quality_control/",
endpoint="on_quality_control",
view_func=on_quality_control,
methods=["POST"],
)
app.run(port=5000, debug=True)