A well-log is a strip of paper with crowded curves drawn on it. Two of them often share a single track, swinging across the same column of the page, and every so often they touch and cross. A human petrophysicist reads through the crossing without a second thought, because the eye follows continuity. Our raster-log digitizer, VeerNet, had no such instinct. It had a foreground mask, and a foreground mask is exactly the wrong tool for telling two crossing curves apart. This is the story of the taxonomy we built to fix that, and why the fix lived in the label scheme rather than in the network.
The crossing problem, stated plainly
The segmentation stage of VeerNet reads a scanned log and marks where the curves run. Early on, the target it learned was a foreground question: at each pixel, is this part of a curve, yes or no? That gives you a clean binary mask, foreground where any curve runs and background everywhere else. It is a perfectly good way to find ink on a page. It is a useless way to keep two curves apart.
Picture the moment two curves cross. Approaching the crossing, the mask is two separate strands of foreground. At the crossing, the two strands occupy the same handful of pixels, so the mask shows one fused blob. Past it, the mask is two separate strands again. Now ask what the downstream pipeline actually needs to know: of the two strands leaving the crossing, which is curve one and which is curve two? The foreground mask has no answer, because it never recorded an identity. Every foreground pixel is the same kind of pixel, so the instant two curves share pixels the mask forgets which is which and cannot recover the assignment afterward. On a well-log the curves cross constantly, so this is where a foreground mask loses the thread again and again.
This was not a rare edge case we could shrug off. The whole point of digitizing a log is to hand back a clean, depth-indexed value for each named curve, the kind of thing that loads into a LAS file and gets trusted by an interpreter [4]. A digitizer that swaps curve one and curve two every time they cross produces a log where the gamma-ray trace and the spontaneous-potential trace trade places at random depths, which is worse than no digitization at all, because it looks plausible and is wrong.
What we set ourselves to fix
The goal was narrow and concrete: the model's output had to preserve curve identity through crossings, on a track that holds two curves, without a hand-written rule that guesses the continuation by slope. We had tried the slope-continuation heuristic. It works until two curves cross at a shallow angle, or a curve goes briefly flat at the touch, and then it guesses wrong and gives you no signal that it did. We wanted the model to learn identity as part of what it predicts, so the crossing is resolved by the time the mask comes out, not patched up by a brittle untangler afterward.
The constraint that shaped everything was the track structure of a real log. Curves do not live alone; they are grouped into tracks, and the grouping is standard. Track one carries three lithology curves: gamma ray, spontaneous potential, and caliper. Track two carries three resistivity curves at shallow, medium, and deep investigation depths. Track three carries the two-curve porosity overlay, neutron porosity and bulk density, the classic NPHI-over-RHOB pair that interpreters read by where the two curves separate. Two curves sharing a track and crossing is not the exception on a log, it is the default, and the porosity overlay is built around the crossing. Whatever we did had to respect that the curves come in named groups meant to be told apart.
The taxonomy we built: one class per curve, not one shared foreground
The reframe was to stop predicting foreground and start predicting a taxonomy. Instead of a binary foreground-versus-background mask, the model learns a label scheme with one class per curve plus a background class. For a two-curve track that is three classes: background, curve one, curve two. Every pixel is assigned to exactly one of the three, and the assignment is mutually exclusive because a softmax over three classes forces the probabilities to sum to one.
The change is small in the output layer and large in what it asks of the model. Under a shared foreground, the two curves are the same thing as far as the label is concerned, so identity is something the model is never asked to represent and therefore never learns. Under a per-curve taxonomy, identity is the label. Curve one and curve two are different classes, the way a cat and a dog are in an ordinary classifier, and the model has to commit each pixel to one of them. At a crossing it still has to decide, pixel by pixel, whether each shared pixel is more curve-one or more curve-two, and the surrounding context, the approach angle, the line thickness, the side each curve came in on, is what lets it decide. The taxonomy turns the crossing from a place where information is destroyed into a place where the model makes a labeled call.
This is the same conceptual move the vision field made when it went from semantic masks to instance-aware and panoptic labels. A plain semantic mask says foreground-or-not; an instance-aware target [2] says which object each pixel belongs to; the panoptic formulation [3] gives every pixel both a class and, for countable things, an identity. Our two curves are countable things on a background stuff-class, so a per-curve taxonomy is the small, domain-specific version of that idea: the curves are the instances, the paper is the stuff, and the label records both. The encoder-decoder that learns it is the standard U-Net lineage [1]; what changed was the label set it trained against, not the architecture.
How it actually went
The first thing we learned is that a per-curve taxonomy is only as good as the ground truth that carries it, and hand-labeling crossings is miserable. A human annotator drawing the boundary between curve one and curve two through a fused crossing is guessing too, just more carefully than the slope heuristic. So we leaned on the synthetic-data pipeline. Because we generate the synthetic logs ourselves, we know the identity of every curve at every depth by construction, straight through the crossings, because we drew them. The synthetic ground truth has no ambiguity at the touch: curve one is whatever we rendered as curve one, pixel-perfect, even where it overlaps curve two. That gave the taxonomy clean targets in exactly the place real labels are weakest.
The second thing we learned is that the taxonomy has to be stable across the corpus, and the easiest way to break it is to let the number of curve classes float. If a two-curve track is sometimes labeled with two classes and sometimes with three, the model never settles on what curve two means. So we fixed it: the final multiclass dataset carries two constant curves per log, labeled into a three-class scheme, every time. Two curves, three classes, no exceptions. That constancy is unglamorous and it mattered more than any architectural choice, because it gave curve two a stable identity to latch onto rather than a definition that shifted from one training image to the next.
The third thing, which took longest to accept, is that the crossing is genuinely hard and the taxonomy does not make it easy, it makes it answerable. A model can carry identity through a crossing and still get individual shared pixels wrong, because at the exact touch the local evidence is weak. What the taxonomy buys is that the error stays local and labeled. A misassigned pixel at the touch is one wrong pixel, not a swapped curve for the rest of the log, because the classes on either side still carry their own identity and pull the assignment back. Under a shared foreground a crossing error propagates forever; under the taxonomy it is contained to the crossing.
What the taxonomy resolves
The exhibit below is the argument in motion. It sweeps two synthetic curves down a single track until they touch and cross, and renders the same crossing twice: once under a binary foreground mask, once under the three-class taxonomy. Drag the crossing depth, or let it play.
Under the binary mask, the two curves are painted into one shared foreground, so at the crossing they fuse into a single blob and the scheme has no way to say which strand is which on the far side. Under the three-class taxonomy, curve one and curve two are separate classes, so the crossing becomes a place where the model makes two labeled calls rather than a place where identity is lost; the strands keep their classes straight through the touch. The track selector reads off the structure the taxonomy mirrors: three curves in track one, three resistivity curves in track two, and the two-curve NPHI-RHOB overlay in track three, with two constant curves per log resolved into three classes. The crossing stays resolved because identity was in the target all along.
The practical payoff showed up immediately downstream. Once the model emitted a per-curve labeled mask, the post-processing step that reads the mask into a depth-indexed curve no longer needed an untangler. It read curve one off the curve-one class and curve two off the curve-two class, crossings included, and the swapped-curve failures the slope heuristic produced went away, because the model had already made the assignment.
What this changed in how we label
The lesson we carried forward is about where to put the hard decision. We had been treating the crossing as a post-processing problem, something to untangle after the model had spoken, and every untangling rule we wrote was a guess dressed up as logic. Moving the decision into the label scheme changed the model from something that finds ink to something that names curves, and naming is what the downstream user actually wanted. The taxonomy did not make the network smarter. It asked the network a question it could answer, by giving each curve a name to remember.
A curve on a log is not a generic foreground object; it is a named member of a named track, and the moment our label scheme started reflecting that, the model's failures stopped being structural and started being ordinary. The taxonomy we built for two curves is the seed of the one we will need when a track holds three, and the discipline is the same: count the curves, give each one a class, keep the count constant, and let the crossing be a labeled call instead of a lost thread.
References
-
Ronneberger, O., Fischer, P., and Brox, T. (2015). U-Net: Convolutional Networks for Biomedical Image Segmentation. MICCAI 2015. https://arxiv.org/abs/1505.04597
-
He, K., Gkioxari, G., Dollar, P., and Girshick, R. (2017). Mask R-CNN. ICCV 2017. https://arxiv.org/abs/1703.06870
-
Kirillov, A., He, K., Girshick, R., Rother, C., and Dollar, P. (2019). Panoptic Segmentation. CVPR 2019. https://arxiv.org/abs/1801.00868
-
Canadian Well Logging Society. LAS Version 2.0: A Digital Standard for Logs, Log ASCII Standard. https://www.cwls.org/products/