Fixing Visible Seamlines and Colour Banding

Two defects get reported with the same words. A seamline is a sharp edge where the mosaic switched from one image to another — it follows a footprint boundary and stops abruptly. Banding is a broad stripe of different brightness, usually the width of a flight line, with soft edges.

They have different causes and different fixes, and applying the seamline fix to a banding problem is the most common wasted afternoon in mosaic work. This page is the diagnostic path from symptom to correction, within orthomosaic radiometry and seamline control.

Telling them apart

Three questions settle it in under a minute.

Is the edge sharp or soft? A one-pixel transition is a seamline. A gradient over tens of metres is banding or a hotspot.

Does the defect follow a flight line? A stripe the width and orientation of one pass is banding, caused by that line being flown under different light or with a different exposure. A defect that zigzags along image footprints is seamlines.

Does it repeat at a regular interval? A bright blob in the middle of every image footprint is the hotspot — real bidirectional reflectance, not an error. It will not respond to any per-image gain because it varies within each image.

Three mosaic defects distinguished by shape and extent Three panels. The left panel shows a seamline: a sharp zigzag edge following image footprint boundaries, with the note that the transition is one pixel wide. The middle panel shows banding: a broad soft-edged stripe the width and orientation of one flight line, with the note that its edges are gradients tens of metres across. The right panel shows hotspot: a repeating bright blob at the same position within every image footprint, with the note that it varies within each image and so no per-image gain can reach it. seamline sharp: one-pixel transition follows footprint boundaries banding soft: gradient over tens of metres one flight line wide hotspot repeats within every footprint no per-image gain can reach it Sharp or soft, follows footprints or flight lines, repeats or not — three questions settle it.

Figure 1 — Three defects that get reported with the same sentence.

Minimal reproducible solution

Classification can be automated. Measuring how sharp the transition is, and whether the defect aligns with footprints or flight lines, turns a subjective call into a number.

import numpy as np
import rasterio
from scipy import ndimage


def classify_defect(mosaic_path: str, *, band: int = 1,
                    sharp_threshold: float = 4.0) -> dict:
    """Separate sharp edges from broad gradients in a mosaic.

    The ratio of high-frequency to low-frequency variation is the whole test.
    Seamlines put their energy at the pixel scale; banding and hotspot put
    theirs at scales of hundreds of pixels, where a smoothing filter leaves it
    untouched.
    """
    with rasterio.open(mosaic_path) as src:
        img = src.read(band, masked=True).astype("float32")

    filled = img.filled(float(img.mean()))
    smooth = ndimage.uniform_filter(filled, size=129)
    high = filled - smooth

    high_energy = float(np.std(high[~img.mask]))
    low_energy = float(np.std(smooth[~img.mask]))

    return {
        "high_frequency_sd": high_energy,
        "low_frequency_sd": low_energy,
        "ratio": high_energy / low_energy if low_energy else float("inf"),
        "likely": ("seamlines" if high_energy / max(low_energy, 1e-6) > 1.0
                   else "banding or hotspot"),
        "sharp_edges_present": high_energy > sharp_threshold,
    }

The threshold is scene-dependent — a mosaic of uniform grass has almost no legitimate high-frequency content, while one of a construction site has a great deal. Run it on a known-good mosaic from the same site first to calibrate what “normal” looks like.

Fixing seamlines

A seamline that is visible has one of two causes, and they need opposite responses.

The images genuinely differ. The balance step failed or was skipped. Feathering here is cosmetic — it converts a sharp edge into a soft one, which remains visible against uniform ground. Fix the balance and re-mosaic.

The images match but the cut is in the wrong place. A straight cut across an open field shows even a one-digital-number difference. Re-route the seamline along image content, as described on the topic page, and the same difference becomes invisible.

def diagnose_seam(step_magnitude: float, image_difference: float) -> str:
    """Which seamline fix applies?

    A seam whose step is close to the underlying image difference is a routing
    problem — the cut is simply in a visible place. A step much larger than the
    typical difference means the two images disagree locally, which routing
    cannot fix.
    """
    if image_difference < 1.5:
        return "re-route: images match, the cut is in the wrong place"
    if step_magnitude > 2.5 * image_difference:
        return "local disagreement — check for a shadow or a bad frame"
    return "re-balance: the images genuinely differ across the block"

Fixing banding

Banding along a flight line means that line was captured under different conditions from its neighbours. Three responses, in increasing order of cost.

Re-run the gain solve including that line. If the line was excluded — often because its overlaps were below the minimum pixel threshold — it kept its own exposure while everything else moved.

Weight the solve toward cross-line pairs. Pairs within a line constrain very little, because those images already share conditions. The pairs that matter are the sideways ones between adjacent lines, and a solve dominated by along-line pairs is under-constrained in exactly the direction the banding runs.

Re-fly the line. If it was flown through a different cloud regime, no correction recovers the shadow detail that was never captured. This is the honest answer more often than people like.

Why cross-line overlap pairs constrain a gain solve and along-line pairs do not Three parallel flight lines are drawn with their image footprints. Overlap pairs within a single line are marked as weakly constraining, because those frames were captured seconds apart under identical conditions. Overlap pairs between adjacent lines are marked as strongly constraining, because those frames were captured minutes apart under conditions that may differ, which is precisely the difference the solve needs to estimate. A note states that a solve dominated by along-line pairs is under-constrained in the direction the banding runs. along-line pairs seconds apart, identical light — weakly constraining cross-line pairs minutes apart, light may differ — strongly constraining A solve dominated by along-line pairs is under-constrained exactly where banding runs.

Figure 2 — The pairs that matter are the sideways ones.

Living with the hotspot

The third defect is the one with no clean fix, and recognising that early saves a great deal of time. The hotspot is the bright region where the view direction is close to the anti-solar direction — the ground really does reflect more light back toward the camera there, and every frame shows it at roughly the same place relative to its own centre.

Because it varies within each image, no per-image gain touches it. Because it sits at the same relative position in every frame, it repeats across the mosaic at the frame spacing, which is exactly the pattern that makes a mosaic look quilted even after the balance is perfect.

Three responses are available, and only one of them is cheap.

Crop each frame harder. Using only the central portion of each image discards the parts of the field of view where the effect is strongest. This requires overlap to spare — around 80% forward and 70% side — but it needs no modelling at all and it is the approach most survey pipelines should reach for first.

Model it. A bidirectional reflectance model, fitted per surface type against view and sun geometry, can remove the effect properly. It is the right answer for multispectral work, where the reflectance values themselves are the deliverable, and usually overkill for an RGB mosaic that just needs to look consistent.

Fly when it is weakest. The hotspot is most pronounced with a low sun and least pronounced under overcast, where the illumination is close to uniform from every direction. An overcast day that looks disappointing to the pilot produces a noticeably more even mosaic.

def hotspot_severity(frame_means: np.ndarray, centre_means: np.ndarray) -> dict:
    """Compare full-frame brightness with central-crop brightness per image.

    A consistent positive difference across the survey means the discarded
    margins really were brighter, which both confirms the hotspot and estimates
    how much cropping would gain.
    """
    delta = centre_means - frame_means
    return {
        "mean_delta_dn": float(delta.mean()),
        "consistent": bool((delta > 0).mean() > 0.8),
        # Above a couple of DN, cropping is worth the overlap it costs.
        "cropping_worthwhile": float(delta.mean()) > 2.0,
    }
Matching the fix to the defect, and the mismatches that waste a day Two columns. The correct pairings column lists feathering or re-routing for a sharp seamline, a block gain solve weighted to cross-line pairs for banding along a flight line, and harder cropping or a reflectance model for the repeating hotspot. The wasted effort column lists feathering applied to banding, which changes nothing because the defect is far wider than any blend zone, a gain solve applied to a hotspot, which chases an effect that varies within each frame rather than between frames, and a global contrast stretch applied to any of them, which shifts everything and hides nothing. the fix reaches the defect seamline → re-route, then feather banding → gain solve, cross-line weighted hotspot → crop harder, or model it the fix cannot reach it feathering applied to banding a gain solve applied to the hotspot a global stretch applied to any of them each leaves the defect exactly where it was Classify before correcting. The three defects respond to three different things.

Figure 3 — Three fixes, three defects, and six ways to pair them wrongly.

Edge-case matrix

Situation Effect Handling
Feathering applied to a banding problem Nothing changes Fix the gain solve instead
Straight seam over uniform ground Highly visible Re-route along content
Seam step ≫ image difference Local disagreement Find the bad frame
One line excluded from the solve That line bands Lower the overlap threshold
Only along-line pairs used Solve under-constrained Weight cross-line pairs
Hotspot mistaken for banding Gains chase a real effect Model it, or crop each frame harder
Defect only visible zoomed out Overview artefact Check at native resolution
Cloud shadow crossed a line Detail never captured Re-fly that line

Verification snippet

def confirm_fix(before: dict, after: dict, *, defect: str) -> dict:
    """Confirm the correction reached the defect it was aimed at.

    Checking the wrong statistic is how a fix gets declared successful without
    changing anything visible: a seamline correction moves high-frequency
    energy, a banding correction moves low-frequency energy, and each leaves
    the other largely alone.
    """
    key = "high_frequency_sd" if defect == "seamlines" else "low_frequency_sd"
    other = "low_frequency_sd" if defect == "seamlines" else "high_frequency_sd"
    reduction = (before[key] - after[key]) / before[key] if before[key] else 0.0
    collateral = abs(after[other] - before[other]) / max(before[other], 1e-6)
    return {
        "targeted_reduction": float(reduction),
        "collateral_change": float(collateral),
        "ok": reduction > 0.4 and collateral < 0.25,
    }

When to escalate

  • Neither statistic moves. The defect is probably in the source imagery — motion blur, a dirty lens, a partly failed sensor — and no mosaicking parameter will reach it.
  • Every fix trades one defect for another. The overlap is too low to give the seamline search anywhere good to route. Increase side overlap on the next flight.
  • The client sees it and the statistics do not. Trust the client. Find where they are looking, measure there, and adjust the thresholds rather than the perception.

Orthomosaic Radiometry and Seamline Control