Reconstructing real-world Easting, Northing and Elevation from a flat PDF is not a text-extraction problem, it is a geometry problem wearing a text-extraction costume. Here is how we actually solved it, and why the exact same automation pattern shows up in ecommerce pricing.
Every pipe in a process plant carries a set of CML tags, short for Corrosion Monitoring Locations, points where an inspector periodically measures wall thickness to catch corrosion before it becomes a leak. Those readings only mean anything if you know exactly where in the real world each CML sits. The survey team already has that answer, sitting in a JSON file as an Easting, Northing and Elevation, what engineers call E, N and EL. The problem is that the isometric drawing an engineer actually works from does not carry that world coordinate anywhere on the page. It carries a network of pipe strokes, dimension lines and CML bubbles that only make sense once you know how to read them.
So the objective sounds almost trivial when you say it out loud: take the CML tag on the drawing, take the CML record in the JSON, and confirm they are the same physical point. In practice that meant building an enterprise AI system that can calculate a 3D coordinate for a CML directly from the 2D drawing, then compare that calculated position against every surveyed position in the JSON and pick the closest honest match. Reconstruction of the pipe network in 3D is not the goal by itself, it is the enabling mechanism that makes the matching trustworthy.
| Stage | Input | Output |
|---|---|---|
| Drawing interpretation | PDF isometric drawing | Anchors, pipe geometry, dimensions, CML labels and touchpoints |
| 3D reconstruction | Anchor coordinates + dimension graph | Calculated E, N, EL coordinates |
| Mapping | Calculated CML coordinates + JSON position | CML to surveyed JSON record |
A case counts as successful when a drawing-side CML gets a calculated 3D coordinate that identifies the correct surveyed JSON record. That is a different measurement than the automation statistic reported later in this article, which is taken one stage earlier, at dimension-to-pipe-segment mapping. Conflating the two is the single most common misreading of a project like this, so I am flagging it here before we go any further.
It is tempting to file this whole thing under
document AI
and reach for an OCR model. OCR is necessary here, we absolutely need it to read coordinate labels, dimension values and CML text, but it answers a much narrower question than the one that actually blocks the project. A correctly read value of
500 mm
is worthless on its own unless the system also knows which two points it measures. Correctly detecting the string
CML045
is worthless unless its leader line can be traced to the right point on the right pipe.
What actually made this tractable was combining OCR and text extraction with image morphology, skeletonization, local geometric analysis, graph construction and numerical coordinate propagation, and treating the PDF as a structured scene rather than a page of text to transcribe. That combination is the difference between a system that can read a drawing and an AI system that can understand one.
The breakthrough was not a clever model, it was changing what we were trying to infer directly. Instead of jumping from raw drawing to final CML coordinate in one leap, we let the drawing pass through a sequence of representations, each one removing a specific kind of ambiguity before handing a cleaner structure to the next stage.
Raw drawing containing text, strokes, dimensions, leaders and CML labels, nothing resolved yet.
Known E, N, EL values attached to precise drawing locations.
A filtered, one-pixel centreline standing in for the pipe network.
Physical lengths tied to resolved endpoints and touchpoints.
Nodes and dimension edges describing the reconstructed network.
E, N, EL coordinates propagated outward from known anchor nodes.
CML identity and pipe attachment point in the reconstructed coordinate system.
Calculated CML positions matched against surveyed position values.
The coordinate engine is deliberately independent of image processing. It receives already-resolved anchors, dimension results, pipe touchpoints and CML leader results, and works purely with those geometric objects.
Design principle behind the reconstruction stage
That separation is what let the reconstruction stage become deterministic. Once the drawing exists as a graph, the core problem stops being a vision problem and becomes a constrained traversal: points get clustered into nodes, dimension records create edges, each edge carries an axis and a physical length, and known anchor coordinates seed the graph. From there, breadth-first search propagates coordinates outward through every connected edge. It also made upstream visual ambiguity much easier to isolate and correct through disciplined Quality Engineering, because a wrong answer at the reconstruction stage almost always traces back to one specific misread relationship, not a vague model failure.
Pipe skeleton geometry tells you where a pipe sits on the page, but it never tells you the physical distance covered, an isometric sheet is frequently not drawn to scale. The printed dimension is the only authoritative source of physical length, which is exactly why dimension-to-segment assignment became the single most important upstream decision in the whole pipeline, and, as you will see in the results section, the main thing still separating us from full automation.
The implemented system is a complete end-to-end pipeline, not a set of disconnected experiments. Each module owns one geometric responsibility, much like well-scoped APIs & MicroServices, and a main orchestrator carries the coordinate-frame conventions each stage needs across the boundary between them.
Extracts E, N, EL coordinate annotations and resolves the leader or arrow geometry connecting an anchor to the drawing network.
Extracts and skeletonizes the pipe centreline while filtering annotation noise and irrelevant thin graphics.
Resolves where dimension endpoints actually reach the pipe, using local masks, witness-line analysis and ray casting.
Handles both drawing patterns, extracts values, resolves endpoints, and scores the geometric association.
Clusters points, builds dimension edges, classifies axis direction, and propagates E, N, EL from anchors via BFS.
Detects and normalises CML OCR text, identifies bubbles and leaders, resolves the CML-to-pipe attachment point.
Loads surveyed JSON positions and performs global or greedy coordinate assignment against calculated positions.
The main pipeline parses the PDF, builds the sheet and region-of-interest representation, runs anchor and CML extraction, integrates the pipe skeleton, resolves dimensions and touchpoints, calculates CML coordinates, and finally performs the JSON match, in that order, while preserving every intermediate result so a QA layer can expose it for inspection. That QA application, built on FastAPI with Full Stack Development practice, is what you are looking at in the screenshots further down this article: it distinguishes successful, review, missing and fixed states, and renders anchor and CML markers directly on top of the original drawing.

What the system actually starts from. One sheet of a real piping isometric: pipe strokes, dimension callouts like 127, 208 and 670 millimetres, CML bubbles with leader lines, and two anchor points carrying an absolute E, N, EL. Nowhere on this page does a CML’s world coordinate appear directly, it has to be derived.
Dimensions are the bridge between page geometry and physical reality, so the detector has to be precise about what it is looking at. It explicitly distinguishes two patterns you will find on almost any isometric sheet. A type-1 dimension carries a numeric value, a dimension line, and two opposing arrowheads defining the measured span directly. A type-2 dimension carries a numeric value connected by a short leader to a separate dimension line, the short leader has its own arrowhead, while the target line elsewhere on the sheet holds the two arrowheads that actually define the span. Treat the two the same way and you will misassign a meaningful fraction of your dimensions before reconstruction even starts.
Dimension processing runs in the full-page coordinate frame, because the marking operations depend on it, and results are only translated into the cropped region-of-interest frame that downstream geometry modules expect once processing finishes. One constraint that shaped the whole approach: the system never assumes a single pixels-per-millimetre scale across an entire sheet. Isometric drawings are routinely not drawn to scale, so the printed number, never the on-page pixel length, is treated as the only authoritative measurement.
The scoring model behind dimension-to-segment confidence is deliberately narrow. It is built from two ingredients: a phase prior, representing the weaker of the two resolution tiers on either side of the dimension, and a direction-alignment factor. The alignment term fits a local pipe direction near the better-supported touchpoint and compares it against the chord connecting both resolved touchpoints, using squared cosine similarity to produce a continuous value between 0 and 1.
The part I would actually flag to anyone building something similar is what the scorer deliberately refuses to do. It does not treat proximity to a junction as a universal correctness signal, even though that is the obvious first instinct. A perfectly legitimate dimension can terminate at a support or a tag rather than a junction, and multiple dimensions can share an origin point while measuring entirely different lengths. So instead of rewarding "close to a junction," the scoring logic rewards "directionally consistent with the pipe ink that is actually there." That single design choice is why the confidence badges you will see in the screenshots below (HIGH MEDIUM MISS) mean something specific and auditable rather than a black-box probability.

The QA dashboard, mid-confidence case. Two 127 millimetre dimensions score MEDIUM (0.66 and 0.73) on direction alignment, while a third case is flagged MISS because no arrow tips were detected on the printed dimension line and no touchpoint pair could be resolved automatically. This is what “needs a human” looks like from the inside, a specific, explainable geometric gap, not a vague low-confidence blob.
With dimensions resolved and scored, the coordinate engine's job becomes almost mechanical, which is exactly the point. Points that fall within a clustering tolerance get consolidated into graph nodes, collapsing small pixel-level disagreements between independently detected endpoints into one stable node. Dimension records become edges between those nodes. Each dimension edge is classified against a small set of calibrated isometric directions using a reference angle for the three drawing axes, and the sign of the measured vector determines whether the movement is positive or negative along E, N or EL. That step is what turns a 2D isometric measurement into a 3D coordinate delta without needing a full perspective camera model.
Here is a simplified version of that traversal, written for clarity rather than as production code, to show the actual shape of the idea:
from collections import deque AXES = {"E": 0, "N": 1, "EL": 2} def propagate_coordinates(anchors, edges): """ anchors: {node_id: (E, N, EL)} known world coordinates edges: {node_id: [(neighbor_id, axis, signed_length_mm)]} returns: {node_id: (E, N, EL)} for every node reachable from a known anchor """ coords = dict(anchors) queue = deque(anchors.keys()) while queue: node = queue.popleft() e, n, el = coords[node] for neighbor, axis, length_mm in edges.get(node, []): if neighbor in coords: continue # already resolved, skip delta = [0.0, 0.0, 0.0] delta[AXES[axis]] = length_mm / 1000.0 # mm -> m coords[neighbor] = ( e + delta[0], n + delta[1], el + delta[2], ) queue.append(neighbor) return coords
Every edge moves exactly one axis, that constraint is what makes the traversal deterministic once the graph itself is correct. Which is also exactly why an error in dimension-to-segment assignment upstream is so expensive: it does not corrupt one point, it corrupts the edge, and BFS will faithfully carry that error into every node downstream of it.
CML detection itself runs on OCR with a fallback path, normalising
recognised labels
against the expected tag types and correcting common OCR substitutions we
kept seeing in practice, things like
U1G or
UIG reading back as
UTG, or
PR7 reading back as
PRT. Once a CML's bubble centre
and leader colour are resolved and tied to a pipe attachment point, that
point gets transformed into the reconstructed 3D coordinate system, giving
every CML a calculated E, N, EL to work with.
The matching stage derives the expected JSON file, recursively extracts every
position value, and converts
JSON coordinates
from metres to millimetres to line up with the units used throughout
reconstruction. Then comes the part that actually determines whether the
match is trustworthy: the system runs a global one-to-one
assignment
using the Hungarian algorithm, minimising the total 3D Euclidean distance
across every calculated-to-surveyed pairing at once, rather than greedily
grabbing the nearest neighbour for each point in isolation.

Six position values, one globally optimal assignment. Each row pairs a calculated CML position against its closest unused surveyed coordinate, resolved across both sheets of the same line at once rather than sheet by sheet, with the 3D distance in millimetres reported alongside each match for auditability.

The other end of the confidence spectrum. On a cleaner section of the network, three consecutive dimensions score a full 1.00 on direction alignment and get accepted automatically, no reviewer touches these. The contrast between this sheet and the one above is the entire human-in-the-loop story in two screenshots.
I want to step back from pipe drawings for a moment, because the part of this project I keep reusing on completely unrelated engagements is not the isometric-specific code, it is the shape of the solution. Strip away the piping vocabulary and what is left is a pattern that shows up constantly in ecommerce, supply chain, insurance, and industrial operations alike: a flat document or a noisy signal encodes a graph of real-world relationships that a computer cannot read off the surface, so you detect the pieces, resolve how they relate, reconstruct a structured representation, score how confident you are in that reconstruction, and route only the genuinely uncertain fraction to a person.
A supplier spec sheet for an ecommerce catalog is not so different from an isometric drawing when you look at it this way. It encodes product attributes, compatible variants, pricing tiers and compliance data through layout conventions, tables, footnotes, cross-references, rather than through clean structured fields. Building a pipeline that reliably turns that PDF into catalog-ready structured data requires the same detection, resolution, reconstruction, confidence-scoring sequence we just walked through for pipe geometry. The domain vocabulary changes, the architecture does not.
That is also exactly the kind of problem our Custom AI & ML Development and Data Engineering teams get pulled into most often, and it is where Agentic AI Solutions earn their keep: not by replacing the reviewer, but by narrowing what reaches them down to the cases that actually need a human judgment call. The same reconstruction discipline underwrites our wider AI & Data practice and the Generative AI Services work we do around document understanding.
The pull toward industrial infrastructure is even more direct, given where this project actually lives. A CML coordinate pipeline does not stay useful in isolation, it eventually has to hand its results to the systems that run a plant day to day: an ERP Integrations layer that keeps inspection schedules and work orders in sync with the asset register, often sitting on top of Oracle Implementation for the asset-management backbone itself, and an Edge to Cloud AI layer that lets the same detection-and-confidence pattern run closer to the pipe rack instead of waiting on an overnight batch job. Once CML positions are trustworthy in 3D, they plug naturally into broader IoT Solutions for continuous condition monitoring, Edge Gateway & Connectivity for getting sensor readings off the plant floor reliably, Industrial Communication Systems for speaking whatever protocol the plant already runs, and Energy Management or Energy Monitoring & Analytics work for the utilities and process plants where corrosion, throughput and energy draw are read off the same physical network. Even EV & Smart Home Management projects lean on a smaller version of the same idea, a physical network of unknown geometry that has to be reconstructed from sparse, noisy signals before anything can be automated safely. None of it stays on one engineer's laptop for long, which is where Cloud Services & Migration and Cloud & Data Security Services come in.
Since this article is going to reach a lot of ecommerce and analytics readers who have never opened a piping isometric in their life, it is worth grounding the pattern above in a concept most of them already work with directly: price elasticity of demand. If you run pricing for an online catalog, this is the number that tells you whether raising a price by a few percent will barely move sales or will send customers straight to a competitor's listing.
The formula is simple on paper:
def point_elasticity(pct_change_qty, pct_change_price):
"""
Ed = %change in quantity demanded / %change in price
|Ed| > 1 -> elastic (demand is price-sensitive)
|Ed| < 1 -> inelastic (demand is price-insensitive)
|Ed| == 1 -> unit elastic
"""
if pct_change_price == 0:
raise ValueError("price did not change, elasticity undefined")
return pct_change_qty / pct_change_price
def arc_elasticity(q1, q2, p1, p2):
"""
Midpoint (arc) elasticity avoids the asymmetry problem of
point elasticity: the result is the same whether you treat
the move as p1->p2 or p2->p1, which matters a lot when you
are comparing elasticity across many SKUs.
"""
avg_q = (q1 + q2) / 2
avg_p = (p1 + p2) / 2
pct_q = (q2 - q1) / avg_q
pct_p = (p2 - p1) / avg_p
return pct_q / pct_p
# Example: a SKU sells 480 units/week at $24.99.
# Price moves to $27.99, weekly sales drop to 360 units.
e = arc_elasticity(q1=480, q2=360, p1=24.99, p2=27.99)
print(round(e, 2)) # -2.62 -> highly elastic, the increase cost more
# in volume than it gained in marginA coefficient with an absolute value above 1 means demand is elastic, customers are price-sensitive and a price increase costs you more in lost volume than it earns in margin, which is exactly what the worked example above shows. Below 1, demand is inelastic, customers keep buying at roughly the same rate regardless of the move, which is common for habitual, low-cost, or hard-to-substitute items. Exactly at 1, you are at unit elasticity, where a price change and the resulting demand change offset each other in percentage terms.
Arc elasticity, the midpoint version shown above, matters more in practice than the simpler point formula because it gives you the same answer regardless of which direction you measured the price move, which is what lets you compare elasticity honestly across hundreds or thousands of SKUs without a systematic bias baked in.
Here is where the two halves of this article actually meet. An elasticity coefficient calculated from a SKU with a long, clean pricing history and a real experiment behind it deserves a very different level of trust than one calculated from three data points over a chaotic promotional weekend. That is precisely the same judgment call the dimension scorer in the piping pipeline is making when it decides whether a resolved touchpoint pair is directionally consistent enough with real pipe ink to trust automatically.
In both cases, the fix is not "trust everything" or "review everything," both of those default answers are wrong for the same reason: they ignore the fact that confidence is unevenly distributed across your cases. The fix is a narrow, explainable scoring function, sample size, price-range coverage, seasonality contamination, promotional confounding, for elasticity, and touchpoint direction alignment for pipe dimensions, that decides which cases can be trusted to an automated pricing engine and which ones need an analyst to look before a live price moves. That is the same architecture as the piping pipeline's confidence badges, just wearing pricing-analyst clothing instead of dimension-line clothing.
A confidence-gated elasticity pipeline typically sits downstream of a Data Platforms & Lakehouse layer that has already unified transaction history across channels through solid Data Engineering , gets its trust scores maintained through Analytics & Governance discipline and ongoing Quality Engineering so pricing decisions stay auditable, and often gets deployed as an Enterprise AI Application with human approval built into the workflow for anything below the trust threshold, rather than as a fully autonomous black box wired straight into Process Automation .
The broader point, and the reason I keep bringing this pattern up in client conversations well outside piping engineering, is that "how confident are we, and what do we do below the threshold" is one of the few genuinely reusable pieces of architecture in applied AI. Whether the graph you are reconstructing is a pipe network or a demand curve, the discipline of scoring narrowly, explaining the score, and routing only the uncertain fraction to a person is what separates a system people actually trust from one they quietly stop using.
Projects like the one described in this article rarely fit inside a single service line. Reconstructing a pipe network in 3D touches computer vision, graph algorithms, confidence scoring, a review UI, and a deployment story, in roughly that order. Here is how the pieces map onto how our teams are actually organised, in case you are trying to figure out who to talk to about something similar.
The FastAPI QA viewer that generated the dashboard screenshots earlier in this article is a good example of how these teams overlap in practice: the confidence-scoring core is Custom AI & ML Development paired with Data & Analytics, the reviewer-facing application is Full Stack Development built on Web & App Development foundations, the JSON ingestion and coordinate storage runs through Data Engineering on top of a Data Platforms & Lakehouse layer, and the whole thing ships and scales through MLOps & DevOps and AIOps Solutions practice, deployed on Cloud Services & Migration infrastructure with Cloud & Data Security Services baked in from day one. Nobody builds a pipeline like this inside one team's swim lane.
Corrosion Monitoring Location. It is the industry term for a fixed point on a pipe or vessel where thickness is measured repeatedly over time to track corrosion or erosion trends. Getting the CML's real-world position right is what makes those repeated readings comparable at all within an Enterprise Data & Analytics workflow.
Because reading text correctly and understanding geometric relationships are two different problems. A model can transcribe "208 mm" perfectly and still have no idea which two points that measures, or which pipe segment it belongs to. This project needed skeletonisation, ray casting, graph construction and coordinate propagation layered on top of text extraction, powered by Custom AI & ML Development.
Because it is a plausible-sounding heuristic that is actually wrong often enough to matter. Valid dimensions regularly terminate at supports or tags rather than junctions, and multiple dimensions can share an origin point while measuring completely different spans. Direction alignment against real pipe ink turned out to be a far more honest signal, supported through Analytics & Governance.
Read on its own, that number undersells the project. It measures one specific upstream stage, dimension-to-pipe-segment mapping, and once a human corrects that stage, the observed downstream CML-to-JSON matching is correct. The honest framing is: the pipeline works end to end today, and the remaining work is expanding how much of that one upstream stage can be trusted to Process Automation.
Both problems reduce to the same architecture: extract a noisy signal, score how much you trust the extraction with a narrow and explainable model, and only route the low-confidence fraction to a human. A dimension confidence score and a price elasticity coefficient calculated from thin data are answering structurally the same question through an Enterprise AI Application, even though one is measured in millimetres and the other in percent change in demand.
Senior AI Engineer, XFactr.AI
Shrikant builds computer vision and spatial reconstruction systems for engineering and industrial data at XFactr.AI, with a focus on turning unstructured drawings and documents into coordinate-accurate, auditable data. This article is drawn directly from a live production pipeline, including the validation numbers and the QA dashboard screenshots shown above.