Skip to content

Edge Trimming

The edge trimming API provides post-processing for invalid border artifacts around nodata regions.

Import from coregix.postprocess:

1
from coregix.postprocess import trim_edge_invalid_pixels, EdgeTrimResult

coregix.postprocess

Post-processing utilities for aligned rasters.

EdgeTrimResult dataclass

Summary of edge trimming execution.

Attributes:

Name Type Description
output_image_path str

Path to the trimmed raster.

nodata_value float

Nodata value used when writing trimmed pixels.

pixels_trimmed int

Number of first-band pixels newly set to nodata.

Source code in coregix/postprocess/edge_trim.py
16
17
18
19
20
21
22
23
24
25
26
27
28
@dataclass
class EdgeTrimResult:
    """Summary of edge trimming execution.

    Attributes:
        output_image_path: Path to the trimmed raster.
        nodata_value: Nodata value used when writing trimmed pixels.
        pixels_trimmed: Number of first-band pixels newly set to nodata.
    """

    output_image_path: str
    nodata_value: float
    pixels_trimmed: int

trim_edge_invalid_pixels(input_image_path, *, output_image_path=None, in_place=False, edge_depth=8, detection_band_index=0, invalid_below=None, invalid_above=None, nodata_value=None, row_chunk_size=1024, col_chunk_size=1024)

Trim pixels adjacent to invalid raster regions.

The function detects invalid pixels from a selected band, dilates that invalid mask by edge_depth pixels, and writes the resolved nodata value to all bands wherever the trim mask is true. It can write a new raster or update the input raster in place.

Parameters:

Name Type Description Default
input_image_path str

Path to the raster to trim.

required
output_image_path Optional[str]

Optional output raster path. Required unless in_place=True.

None
in_place bool

If True, overwrite input_image_path after trimming.

False
edge_depth int

Number of pixels to trim around each invalid region.

8
detection_band_index int

0-based band index used to detect invalid pixels.

0
invalid_below Optional[float]

Optional threshold; values less than or equal to this value are treated as invalid.

None
invalid_above Optional[float]

Optional threshold; values greater than or equal to this value are treated as invalid.

None
nodata_value Optional[float]

Optional nodata override. Defaults to the raster's declared nodata value.

None
row_chunk_size int

Number of rows processed per window.

1024
col_chunk_size int

Number of columns processed per window.

1024

Returns:

Type Description
EdgeTrimResult

EdgeTrimResult summary with output path, nodata value, and pixel count.

Raises:

Type Description
FileNotFoundError

If input_image_path does not exist.

ValueError

If arguments are inconsistent or no invalid-pixel criteria are available.

Source code in coregix/postprocess/edge_trim.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def trim_edge_invalid_pixels(
    input_image_path: str,
    *,
    output_image_path: Optional[str] = None,
    in_place: bool = False,
    edge_depth: int = 8,
    detection_band_index: int = 0,
    invalid_below: Optional[float] = None,
    invalid_above: Optional[float] = None,
    nodata_value: Optional[float] = None,
    row_chunk_size: int = 1024,
    col_chunk_size: int = 1024,
) -> EdgeTrimResult:
    """Trim pixels adjacent to invalid raster regions.

    The function detects invalid pixels from a selected band, dilates that
    invalid mask by ``edge_depth`` pixels, and writes the resolved nodata value
    to all bands wherever the trim mask is true. It can write a new raster or
    update the input raster in place.

    Args:
        input_image_path: Path to the raster to trim.
        output_image_path: Optional output raster path. Required unless
            ``in_place=True``.
        in_place: If ``True``, overwrite ``input_image_path`` after trimming.
        edge_depth: Number of pixels to trim around each invalid region.
        detection_band_index: 0-based band index used to detect invalid pixels.
        invalid_below: Optional threshold; values less than or equal to this
            value are treated as invalid.
        invalid_above: Optional threshold; values greater than or equal to this
            value are treated as invalid.
        nodata_value: Optional nodata override. Defaults to the raster's declared
            nodata value.
        row_chunk_size: Number of rows processed per window.
        col_chunk_size: Number of columns processed per window.

    Returns:
        EdgeTrimResult summary with output path, nodata value, and pixel count.

    Raises:
        FileNotFoundError: If ``input_image_path`` does not exist.
        ValueError: If arguments are inconsistent or no invalid-pixel criteria
            are available.
    """
    if edge_depth <= 0:
        raise ValueError("edge_depth must be > 0.")
    if detection_band_index < 0:
        raise ValueError("detection_band_index must be >= 0.")
    if row_chunk_size <= 0 or col_chunk_size <= 0:
        raise ValueError("row_chunk_size and col_chunk_size must be > 0.")
    if not os.path.isfile(input_image_path):
        raise FileNotFoundError(input_image_path)
    if in_place and output_image_path is not None:
        raise ValueError("Use either output_image_path or in_place, not both.")
    if not in_place and output_image_path is None:
        raise ValueError("Provide output_image_path or set in_place=True.")

    final_path = input_image_path if in_place else output_image_path
    assert final_path is not None
    os.makedirs(os.path.dirname(final_path) or ".", exist_ok=True)

    with tempfile.TemporaryDirectory(
        prefix="edge_trim_",
        dir=os.path.dirname(input_image_path) or None,
    ) as temp_dir:
        temp_output_path = os.path.join(temp_dir, os.path.basename(final_path))
        shutil.copyfile(input_image_path, temp_output_path)

        pixels_trimmed = 0
        with rasterio.open(input_image_path) as src, rasterio.open(temp_output_path, "r+") as dst:
            if detection_band_index >= src.count:
                raise ValueError(
                    f"detection_band_index={detection_band_index} is out of range for raster with {src.count} band(s)."
                )

            resolved_nodata = nodata_value if nodata_value is not None else dst.nodata
            if resolved_nodata is None:
                raise ValueError("Raster has no nodata value; provide nodata_value explicitly.")
            if invalid_below is None and invalid_above is None and src.nodata is None:
                raise ValueError("No invalid criteria available; provide invalid_below and/or invalid_above.")

            if dst.nodata != resolved_nodata:
                dst.nodata = resolved_nodata

            detection_band = detection_band_index + 1

            pixels_trimmed = _trim_invalid_edges_windowed(
                src,
                dst,
                detection_band=detection_band,
                edge_depth=edge_depth,
                nodata_value=resolved_nodata,
                invalid_below=invalid_below,
                invalid_above=invalid_above,
                row_chunk_size=row_chunk_size,
                col_chunk_size=col_chunk_size,
            )

        if in_place:
            os.replace(temp_output_path, final_path)
        else:
            shutil.copyfile(temp_output_path, final_path)

    return EdgeTrimResult(
        output_image_path=final_path,
        nodata_value=float(resolved_nodata),
        pixels_trimmed=pixels_trimmed,
    )