Logo

FOSSGIS-Konferenz 2025

Marius Maryniak

aviary

Ein generisches Python-Framework
zur KI-Inferenz
für georeferenzierte Daten

Hans-Jürgen Landes

Forschungsteam
für Künstliche Intelligenz
und kommunale Geoinformationen
an der Westfälischen Hochschule

Prof. Dr. Christian

Kuhlmann

Alexander

Roß

Stefan

Küpper

Marius

Maryniak

Hintergrund

Warum entwickeln wir aviary?

  • Standards statt Insellösungen

    • Mehr Flexibilität

    • Weniger Redundanz

    • Kürzere Implementierungszeiten in KI-Projekten

    • Höhere Qualität der Software

  • Gefördert im Rahmen von URBAN.KI (Projektidee des Kreises Unna)

Anforderungen

Was soll aviary können?

  • Generisch

    • Modular

    • Einfach anpass- und erweiterbar

    • Task- und KI-Framework-unabhängig

  • Effizient und skalierbar

  • Nutzungsfreundlich

Konzepte

Wie führt man KI-Modelle auf georeferenzierten Daten aus?

  • Kachelweise Verarbeitung

    • Area of Interest definieren

    • Datenquellen definieren

    • Datenverarbeitung definieren

Area of Interest definieren

Welche Kacheln sollen verarbeitet werden?

grid = aviary.Grid(
    coordinates=np.array(...),
    tile_size=256,
)

aviary.Grid

  • coordinates: Koordinaten der Kacheln (x_min, y_min)

  • tile_size: Größe der Kacheln

bounding_box = aviary.BoundingBox(
    x_min=403691,
    y_min=5756873,
    x_max=405739,
    y_max=5758921,
)

grid = aviary.Grid.from_bounding_box(
    bounding_box=bounding_box,
    tile_size=256,
    snap=False,
)

Grid mit einer
BoundingBox erstellen

bounding_box = aviary.BoundingBox(
    x_min=403691,
    y_min=5756873,
    x_max=405739,
    y_max=5758921,
)

grid = aviary.Grid.from_bounding_box(
    bounding_box=bounding_box,
    tile_size=256,
    snap=True,
)

Grid mit einer
BoundingBox erstellen

gpkg_path = 'gemarkungsgrenze_muenster.gpkg'
gdf = gpd.read_file(gpkg_path)

grid = aviary.Grid.from_gdf(
    gdf=gdf,
    tile_size=256,
    snap=True,
)

Grid mit einem
gpd.GeoDataFrame erstellen

grid = grid_1 - grid_2
grid = grid_1 + grid_2
grid = grid_1 & grid_2

Vereinigung zweier Grids mit + bilden

Differenz zweier Grids mit - bilden

Schnittmenge zweier Grids mit & bilden

grid = grid.append(coordinates=(405739, 5758921))
grid = grid.remove(coordinates=(403691, 5756873))

Koordinaten zu einem Grid
hinzufügen mit append

Koordinaten von einem Grid
entfernen mit remove

grids = grid.chunk(num_chunks=4)

Grid in mehrere Grids
unterteilen mit chunk

Datenquellen definieren

Woher sollen die Daten der Kacheln bezogen werden?

aviary.tile.
TileFetcher
 Protocol

  • coordinates: Koordinaten der Kachel (x_min, y_min)

tile_fetcher = aviary.tile.TileFetcher(...)

tile = tile_fetcher(coordinates=(404587, 5757769))
url = (
    'https://www.wms.nrw.de/geobasis/wms_nw_dop'
)

rgb_wms_fetcher = aviary.tile.WMSFetcher(
    url=url,
    version=aviary.WMSVersion.V1_3_0,
    layer='nw_dop_rgb',
    epsg_code=25832,
    response_format='image/png',
    channel_keys=[
        aviary.ChannelName.R,
        aviary.ChannelName.G,
        aviary.ChannelName.B,
    ],
    tile_size=256,
    ground_sampling_distance=.2,
)

nir_wms_fetcher = aviary.tile.WMSFetcher(
    url=url,
    version=aviary.WMSVersion.V1_3_0,
    layer='nw_dop_nir',
    epsg_code=25832,
    response_format='image/png',
    channel_keys=[
        aviary.ChannelName.NIR,
        aviary.ChannelName.NIR,
        aviary.ChannelName.NIR,
    ],
    tile_size=256,
    ground_sampling_distance=.2,
)

aviary.tile.
WMSFetcher

Rasterdaten von einem
Web Map Service laden

url = (
    'https://www.wms.nrw.de/geobasis/wms_nw_dop'
)

rgb_wms_fetcher = aviary.tile.WMSFetcher(
    url=url,
    version=aviary.WMSVersion.V1_3_0,
    layer='nw_dop_rgb',
    epsg_code=25832,
    response_format='image/png',
    channel_keys=[
        aviary.ChannelName.R,
        aviary.ChannelName.G,
        aviary.ChannelName.B,
    ],
    tile_size=256,
    ground_sampling_distance=.2,
)

nir_wms_fetcher = aviary.tile.WMSFetcher(
    url=url,
    version=aviary.WMSVersion.V1_3_0,
    layer='nw_dop_nir',
    epsg_code=25832,
    response_format='image/png',
    channel_keys=[
        aviary.ChannelName.NIR,
        None,
        None,
    ],
    tile_size=256,
    ground_sampling_distance=.2,
)

aviary.tile.
WMSFetcher

Rasterdaten von einem
Web Map Service laden

rgb_tile = rgb_wms_fetcher(coordinates=(404587, 5757769))
nir_tile = nir_wms_fetcher(coordinates=(404587, 5757769))

Tile von einem Web Map Service
(RGB-Layer) laden

Tile von einem Web Map Service
(NIR-Layer) laden

url = (
    'https://www.wms.nrw.de/geobasis/wms_nw_dop'
)

rgb_wms_fetcher = aviary.tile.WMSFetcher(
    url=url,
    version=aviary.WMSVersion.V1_3_0,
    layer='nw_dop_rgb',
    epsg_code=25832,
    response_format='image/png',
    channel_keys=[
        aviary.ChannelName.R,
        aviary.ChannelName.G,
        aviary.ChannelName.B,
    ],
    tile_size=256,
    ground_sampling_distance=.2,
)

nir_wms_fetcher = aviary.tile.WMSFetcher(
    url=url,
    version=aviary.WMSVersion.V1_3_0,
    layer='nw_dop_nir',
    epsg_code=25832,
    response_format='image/png',
    channel_keys=[
        aviary.ChannelName.NIR,
        aviary.ChannelName.NIR,
        aviary.ChannelName.NIR,
    ],
    tile_size=256,
    ground_sampling_distance=.2,
)

aviary.tile.
WMSFetcher

Rasterdaten
mit Buffer von einem
Web Map Service laden

url = (
    'https://www.wms.nrw.de/geobasis/wms_nw_dop'
)

rgb_wms_fetcher = aviary.tile.WMSFetcher(
    url=url,
    version=aviary.WMSVersion.V1_3_0,
    layer='nw_dop_rgb',
    epsg_code=25832,
    response_format='image/png',
    channel_keys=[
        aviary.ChannelName.R,
        aviary.ChannelName.G,
        aviary.ChannelName.B,
    ],
    tile_size=256,
    ground_sampling_distance=.2,
    buffer_size=32,
)

nir_wms_fetcher = aviary.tile.WMSFetcher(
    url=url,
    version=aviary.WMSVersion.V1_3_0,
    layer='nw_dop_nir',
    epsg_code=25832,
    response_format='image/png',
    channel_keys=[
        aviary.ChannelName.NIR,
        aviary.ChannelName.NIR,
        aviary.ChannelName.NIR,
    ],
    tile_size=256,
    ground_sampling_distance=.2,
    buffer_size=32,
)

aviary.tile.
WMSFetcher

Rasterdaten
mit Buffer von einem
Web Map Service laden

rgb_tile = rgb_wms_fetcher(coordinates=(404587, 5757769))
nir_tile = nir_wms_fetcher(coordinates=(404587, 5757769))

Tile von einem Web Map Service
(RGB-Layer) laden

Tile von einem Web Map Service
(NIR-Layer) laden

  • CompositeFetcher

  • VRTFetcher

  • WMSFetcher

Implementierte TileFetcher

tile = aviary.Tile(
  	channels=[
        aviary.Channel(...),
        aviary.Channel(...),
        aviary.Channel(...),
        aviary.Channel(...),
    ]
    coordinates=(404587, 5757769),
    tile_size=256,
)

aviary.Tile(s)

  • channels: Kanäle der Kachel

  • coordinates: Koordinaten der Kachel (x_min, y_min)

  • tile_size: Größe der Kachel

Tile(s) und Channel

Wie strukturiert aviary georeferenzierte Daten?

channel = aviary.Channel(
  	data=...,
    name='my_channel',
    buffer_size=.125,
    time_step=None,
)

aviary.Channel ABC

  • data: Daten des Kanals

  • name: Name des Kanals

  • buffer_size: Buffer-Größe des Kanals

  • time_step: Zeitschritt des Kanals

Tile(s) und Channel

Wie strukturiert aviary georeferenzierte Daten?

r_channel = aviary.RasterChannel(
  	data=np.array(...),
    name=aviary.ChannelName.R,
    buffer_size=.125,
    time_step=None,
)
g_channel = aviary.RasterChannel(
  	data=np.array(...),
    name=aviary.ChannelName.G,
    buffer_size=.125,
    time_step=None,
)
b_channel = aviary.RasterChannel(
  	data=np.array(...),
    name=aviary.ChannelName.B,
    buffer_size=.125,
    time_step=None,
)
nir_channel = aviary.RasterChannel(
  	data=np.array(...),
    name=aviary.ChannelName.NIR,
    buffer_size=.125,
    time_step=None,
)
  • RasterChannel

  • VectorChannel

Implementierte Channel

Datenverarbeitung definieren

Wie sollen die Daten der Kacheln verarbeitet werden?

aviary.tile.
TilesProcessor
 Protocol

  • tiles: Batch von Kacheln

tiles_processor = aviary.tile.TilesProcessor(...)

tiles = tiles_processor(tiles=aviary.Tiles(...))

NormalizeProcessor

NormalizeProcessor

NormalizeProcessor

NormalizeProcessor

Adois

RemoveBufferProcessor

VectorizeProcessor

VectorExporter

GridExporter

  • Rasterdaten normalisieren

  • adois ausführen

  • Buffer entfernen

  • Rasterdaten vektorisieren

  • Vektordaten exportieren

  • Koordinaten exportieren

Tiles

Tiles

Beispiel 1:

adois

NormalizeProcessor

NormalizeProcessor

NormalizeProcessor

NormalizeProcessor

MyModel1

RemoveBufferProcessor

VectorizeProcessor

VectorExporter

GridExporter

  • Rasterdaten normalisieren

  • KI-Modell ausführen

  • Buffer entfernen

  • Rasterdaten vektorisieren

  • Vektordaten exportieren

  • Koordinaten exportieren

Tiles

Tiles

Beispiel 2:

Zwei KI-Modelle

MyModel1

MyModel1

RemoveBufferProcessor

VectorizeProcessor

VectorExporter

MyModel2

RasterizeProcessor

RasterExporter

RasterExporter

GridExporter

  • Vektordaten rasterisieren

  • Rasterdaten exportieren

  • Koordinaten exportieren

Tiles

Tiles

Beispiel 3:

Trainingsdaten

  • CopyProcessor

  • NormalizeProcessor

  • ParallelCompositeProcessor

  • RemoveBufferProcessor

  • RemoveProcessor

  • SelectProcessor

  • SequentialCompositeProcessor

  • StandardizeProcessor

  • VectorizeProcessor

Implementierte TilesProcessor

  • Adois

Implementierte TilesProcessor

  • GridExporter

  • VectorExporter

aviary verwenden

Wie installiert man aviary?

pip install --pre geospaitial-lab-aviary
uv pip install --pre geospaitial-lab-aviary
docker pull ghcr.io/geospaitial-lab/aviary

Option 1:

pip

Option 2:

uv

Option 3:

Docker

aviary verwenden

Welche Nutzungsmöglichkeiten bietet aviary?

  • ​3 Optionen:

    • Python API verwenden

    • Python API mit Pipelines verwenden

    • CLI mit Pipelines verwenden

tile_pipeline = aviary.pipeline.TilePipeline(
    grid=aviary.Grid(...),
    tile_fetcher=aviary.tile.TileFetcher(...),
    tiles_processor=aviary.tile.TilesProcessor(...),
)

tile_pipeline()

Option 2:

Python API mit Pipelines

grid_config:
  bounding_box_coordinates:
    - 403691
    - 5756873
    - 405739
    - 5758921
  tile_size: 256
  snap: true

tile_fetcher_config:
  name: 'CompositeFetcher'
  config:
    tile_fetcher_configs:
      - name: 'WMSFetcher'
        config:
          url: 'https://www.wms.nrw.de/geobasis/wms_nw_dop'
          version: '1.3.0'
          layer: 'nw_dop_rgb'
          epsg_code: 25832
          response_format: 'image/png'
          channel_keys:
            - 'r'
            - 'g'
            - 'b'
          tile_size: 256
          ground_sampling_distance: .2
          buffer_size: 32
      - name: 'WMSFetcher'
        config:
          url: 'https://www.wms.nrw.de/geobasis/wms_nw_dop'
          version: '1.3.0'
          layer: 'nw_dop_nir'
          epsg_code: 25832
          response_format: 'image/png'
          channel_keys:
            - 'nir'
            - null
            - null
          tile_size: 256
          ground_sampling_distance: .2
          buffer_size: 32

tile_loader_config:
  batch_size: 4
  num_prefetched_tiles: 1

tiles_processor_config:
  name: 'SequentialCompositeProcessor'
  config:
    tiles_processor_configs:
      - name: 'NormalizeProcessor'
        config:
          channel_key: 'r'
          min_value: 0
          max_value: 255
      - name: 'NormalizeProcessor'
        config:
          channel_key: 'g'
          min_value: 0
          max_value: 255
      - name: 'NormalizeProcessor'
        config:
          channel_key: 'b'
          min_value: 0
          max_value: 255
      - name: 'NormalizeProcessor'
        config:
          channel_key: 'nir'
          min_value: 0
          max_value: 255
      - name: 'Adois'
        config:
      - name: 'RemoveBufferProcessor'
        config:
      - name: 'VectorizeProcessor'
        config:
          channel_key: 'adois'
      - name: 'VectorExporter'
        config:
          channel_key: 'adois'
          epsg_code: 25832
          dir_path: 'output'
          gpkg_name: 'adois.gpkg'
      - name: 'GridExporter'
        config:
          dir_path: 'output'
          json_name: 'grid.json'
aviary tile-pipeline config.yaml

Option 3:

CLI mit Pipelines

aviary erweitern

Wie implementiert man eigene Komponenten?

  • Komponenten:

    • TileFetcher

    • TilesProcessor

  • 2 Optionen:

    • Eigene Komponente in der Python API verwenden

    • Eigene Komponente in der CLI als Plugin verwenden

class MyTilesProcessor:

    def __init__(self) -> None:
        ...

    def __call__(
        self,
        tiles: Tiles,
    ) -> Tiles:
        ...

Option 1:

Python API

  • Protocol implementieren

  • MyTilesProcessor wie aviary’s Komponenten verwenden

class MyTilesProcessorConfig(pydantic.BaseModel):
    ...

@register_tiles_processor(config_class=MyTilesProcessorConfig)
class MyTilesProcessor:

    def __init__(self) -> None:
        ...

    @classmethod
    def from_config(
        cls,
        config: MyTilesProcessorConfig,
    ) -> MyTilesProcessor:
        ...

    def __call__(
        self,
        tiles: Tiles,
    ) -> Tiles:
        ...

Option 2:

CLI

  • Protocol implementieren

  • Config implementieren

  • from_config
    implementieren

  • Komponente registrieren

  • MyTilesProcessor wie aviary’s Komponenten verwenden

Backlog

Woran arbeiten wir als nächstes?

  • Web-App für eine interaktive Konfiguration entwickeln

  • How-to-Guides erstellen

  • Neue Komponenten implementieren

    • TileFetcher:

      • COGFetcher

      • GPKGFetcher

    • TilesProcessor:

      • RasterizeProcessor

      • GraphProcessor

    • ​aviary.vector

  • ​​Neue KI-Modelle integrieren

  • Neue KI-Modelle zur Auswertung von Luftbildern entwickeln

  • Basis-Modell trainieren

  • Fine-Tuning für konkrete Use-Cases durchführen

  • Gefördert im Rahmen von URBAN.KI (Projektidee des Kreises Recklinghausen)

Neue KI-Modelle

Woran arbeiten wir parallel?

Use-Case 1:

Gebäude

  • Geneigtes Dach ohne Begrünung

  • Geneigtes Dach mit Begrünung

  • Flachdach ohne Begrünung

  • Flachdach mit Begrünung

  • Flachdach mit Kiesschüttung

Use-Case 2:

Versiegelte Flächen

  • Verkehrsflächen

  • Zuwegungen

  • Schottergärten

  • Pools

  • Sonstige

Use-Case 3:

Nicht versiegelte Flächen

  • Niedrige Vegetation

  • Mittlere Vegetation

  • Hohe Vegetation

  • Agrarflächen

  • Gewässer

  • Sonstige

Use-Case 4:

Solaranlagen

  • Photovoltaik

  • Solarthermie

Community

Wie kannst du beitragen?

Link

zu

den

Folien