# Matrycowy filtr dla idealnego downsamplingu wykresów rozrzutu z 50 mln punktów
Matrycowy filtr zapewnia 100% pokrycie i wizualne podobieństwo przy renderowaniu 50 mln punktów ciśnienia w odwiertach naftowych. Klasyczne algorytmy typu LTTB tracą do 59% gęstości danych. Podejście działa w jednym przejściu bez sortowania, ograniczając wynik do liczby pikseli ekranu.
Wykresy rozrzutu są niezbędne dla nieregularnych danych z lukami: linie zniekształcają przerwy i ukrywają klastry. Standardowe metody są zoptymalizowane pod wykresy liniowe, ignorując strukturę dwuwymiarową.
Ograniczenia klasycznych algorytmów
MinMax, M4, LTTB i hybrydy dzielą oś X na kosze, wybierając 1–4 punkty po Y. W gęstych słupkach traci się 96–99% danych: chmura zapada się w ekstrema.
- MinMax: minimum i maksimum — zachowuje szczyty, ale ignoruje rozkład.
- M4: pierwsza, ostatnia, min/maks — idealne dla linii.
- LTTB: maksymalna powierzchnia trójkąta — sylwetka krzywej.
- MinMaxLTTB: fiksuje ekstrema, potem wygładza.
Dla rozrzutu to fundamentalna wada: algorytmy nie uwzględniają współrzędnej Y w koszach.
Zasada matrycowego filtra
Analog filtra Voxel Grid z grafiki: projektuje punkty na siatkę pikselową W×H. W każdej komórce zostaje jeden punkt w kolejności przetwarzania.
public static void downsample(
double[] times, double[] values,
int width, int height,
List<double[]> result) {
int n = times.length;
if (n == 0) return;
// Krok 1: granice
double tMin = times[0], tMax = times[0];
double vMin = values[0], vMax = values[0];
for (int i = 1; i < n; i++) {
if (times[i] < tMin) tMin = times[i];
if (times[i] > tMax) tMax = times[i];
if (values[i] < vMin) vMin = values[i];
if (values[i] > vMax) vMax = values[i];
}
double tRange = Math.max(tMax - tMin, 1e-10);
double vRange = Math.max(vMax - vMin, 1e-10);
// Krok 2: siatka
boolean[] grid = new boolean[width * height];
// Krok 3: filtrowanie
for (int i = 0; i < n; i++) {
int px = (int) Math.min(width - 1, (times[i] - tMin) / tRange * (width - 1));
int py = (int) Math.min(height - 1, (values[i] - vMin) / vRange * (height - 1));
int idx = py * width + px;
if (!grid[idx]) {
grid[idx] = true;
result.add(new double[]{times[i], values[i]});
}
}
}
Metoda imituje renderowanie: aktywuje piksele, które zapaliłyby się przy pełnym zestawie. Ograniczenie — tylko do wizualizacji, nie do statystyki.
Metodologia testów
~3000 zbiorów danych telemetrii odwiertów: od 19k do 50M+ punktów. Porównanie piksel po pikselu z oryginałem w trybach rozrzut (2×2px) i linie (1px).
Metryki:
- Pokrycie: % pikseli oryginału pokrytych przez algorytm.
- Precyzja: % pikseli algorytmu w oryginale.
- F1-score: średnia harmoniczna.
- Wynik wizualny: F1 z tolerancją ±1px (uwzględnia subpiksele).
Budżet dla klasyki — wynik MatrixFilter 1920×1080. Stanowisko: i7-7700K, Java 17, jeden wątek.
Wyniki na rzeczywistych danych
Regularne (~20k punktów, 2930 plików)
Rozrzut: MatrixFilter 1920×1080 — 100% we wszystkich metrykach. MatrixFilter 800×600 — Wizualne 99.6%, dwukrotnie mniej punktów. Klasyka: Pokrycie 63–84%, Wizualne 76–95%.
| Algorytm | Śr. Wyj. | Razem ms | Pokrycie | Prec. | F1 | Wizualne |
|-------------------|----------|----------|----------|-------|------|----------|
| Matrix 1920×1080 | 4939 | 695 | 100% | 100% | 100% | 100% |
| Matrix 800×600 | 2867 | 497 | 84.6% | 100% | 91.5%| 99.6% |
| MinMaxLTTB x4 | 4932 | 494 | 84.1% | 100% | 90.9%| 95.2% |
Średnie i duże
Na 500k–50M punktów Matrix zachowuje 100% Wizualne przy czasie ~ms/10k punktów. LTTB spada do 16–40% Pokrycia na gęstych danych.
Co ważne
- Matrycowy filtr daje idealny rozrzut bez sortowania, złożoność O(n).
- Klasyczne algorytmy tracą strukturę gęstych chmur, mimo hojnego budżetu.
- Wynik wizualny z ±1px bliższy percepcji: Matrix 800×600 nieodróżnialny od pełnego renderu.
- Zastosowanie ściśle wizualne: dla linii/statystyki inne metody.
- Testowane na przemysłowych zbiorach danych wydobycia ropy z nieregularnymi lukami.
— Editorial Team
Brak komentarzy.