pygndc 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pygndc/__init__.py ADDED
@@ -0,0 +1,92 @@
1
+ """
2
+ pygndc - Geographic Neural Data Cube
3
+
4
+ A Python library for compressing geospatial time-series data using neural networks
5
+
6
+ Quick Start:
7
+ import pygndc
8
+
9
+ # High-level API (recommended)
10
+ with pygndc.open("satellite.gndc") as ds:
11
+ print(ds.crs, ds.bounds, ds.shape)
12
+ frame = ds.read(t=0) # (H, W, C)
13
+ frame = ds.read_at_time("2024-06-15") # continuous interpolation
14
+ ts = ds.sample(lon=116.5, lat=39.9) # (T, C) point query
15
+
16
+ # Low-level API (backward compatible)
17
+ reader = pygndc.GNDCReader()
18
+ reader.load("model.gndc")
19
+ frame = reader.reconstruct_single_frame(0)
20
+ """
21
+
22
+ # Fix Intel OpenMP conflict
23
+ import os
24
+ os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
25
+
26
+ from pygndc._version import __version__, __author__, __license__
27
+
28
+ # Public API - Reader
29
+ from pygndc.reader import GNDCReader
30
+
31
+ # Public API - Dataset (high-level)
32
+ from pygndc.dataset import GNDCDataset
33
+
34
+ # Public API - Analysis
35
+ from pygndc.analysis import (
36
+ compute_ndvi,
37
+ compute_evi,
38
+ compute_ndwi,
39
+ compute_ndmi,
40
+ compute_temporal_gradient,
41
+ compute_spatial_gradient
42
+ )
43
+
44
+ # Public API - Viewer
45
+ from pygndc.viewer import GNDCInteractiveViewer, start_viewer
46
+
47
+ def open(path: str, mode: str = "auto") -> GNDCDataset:
48
+ """
49
+ Open a .gndc file and return a GNDCDataset.
50
+
51
+ This is the recommended entry point for working with .gndc files.
52
+
53
+ Args:
54
+ path: Path to the .gndc file.
55
+ mode: Inference mode.
56
+ - 'auto': auto-detect best available (default)
57
+ - 'tcnn_cuda': tinycudann on GPU (fastest)
58
+ - 'torch_gpu': pure PyTorch on GPU
59
+ - 'torch_cpu': pure PyTorch on CPU
60
+
61
+ Returns:
62
+ GNDCDataset instance.
63
+
64
+ Examples:
65
+ >>> ds = pygndc.open("data.gndc")
66
+ >>> with pygndc.open("data.gndc", mode="torch_cpu") as ds:
67
+ ... frame = ds.read(t=0)
68
+ """
69
+ return GNDCDataset(path, mode=mode)
70
+
71
+
72
+ __all__ = [
73
+ # Version info
74
+ "__version__",
75
+ "__author__",
76
+ "__license__",
77
+ # High-level API
78
+ "open",
79
+ "GNDCDataset",
80
+ # Reader
81
+ "GNDCReader",
82
+ # Analysis
83
+ "compute_ndvi",
84
+ "compute_evi",
85
+ "compute_ndwi",
86
+ "compute_ndmi",
87
+ "compute_temporal_gradient",
88
+ "compute_spatial_gradient",
89
+ # Viewer
90
+ "GNDCInteractiveViewer",
91
+ "start_viewer",
92
+ ]
pygndc/__main__.py ADDED
@@ -0,0 +1,202 @@
1
+ """
2
+ CLI entry point for pygndc.
3
+ """
4
+
5
+ import argparse
6
+ import sys
7
+ import os
8
+
9
+ from pygndc import __version__
10
+ from pygndc.reader import GNDCReader
11
+
12
+ MODE_CHOICES = ["auto", "tcnn_cuda", "torch_gpu", "torch_cpu"]
13
+
14
+
15
+ def _add_mode_arg(parser):
16
+ """Add --mode argument to a subparser."""
17
+ parser.add_argument(
18
+ "--mode", choices=MODE_CHOICES, default="auto",
19
+ help="Inference mode (default: auto)"
20
+ )
21
+
22
+
23
+ def main():
24
+ parser = argparse.ArgumentParser(
25
+ description="pygndc - Geographic Neural Data Compression",
26
+ prog="pygndc"
27
+ )
28
+ parser.add_argument("--version", action="version", version=f"pygndc {__version__}")
29
+
30
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
31
+
32
+ # Decompress command
33
+ decompress_parser = subparsers.add_parser("decompress", help="Decompress/reconstruct data")
34
+ decompress_parser.add_argument("-i", "--input", required=True, help="Input .gndc model file")
35
+ decompress_parser.add_argument("-o", "--output", required=True, help="Output path (file or directory)")
36
+ decompress_parser.add_argument("--timestamp", help="Specific timestamp (YYYY-MM-DD)")
37
+ decompress_parser.add_argument("--start", dest="start_date", help="Start date (YYYY-MM-DD)")
38
+ decompress_parser.add_argument("--end", dest="end_date", help="End date (YYYY-MM-DD)")
39
+ decompress_parser.add_argument("--interval", type=int, default=5, help="Interval in days")
40
+ decompress_parser.add_argument("--format", choices=["tif", "npy"], default="tif", help="Output format")
41
+ _add_mode_arg(decompress_parser)
42
+
43
+ # Info command
44
+ info_parser = subparsers.add_parser("info", help="Show model information")
45
+ info_parser.add_argument("model", help="Path to .gndc model file")
46
+ info_parser.add_argument("--json", action="store_true", help="Output as JSON")
47
+
48
+ # Viewer command
49
+ viewer_parser = subparsers.add_parser("viewer", help="Launch interactive viewer")
50
+ viewer_parser.add_argument("-i", "--input", required=True, help="Input .gndc model file")
51
+ _add_mode_arg(viewer_parser)
52
+
53
+ # NDVI command
54
+ ndvi_parser = subparsers.add_parser("ndvi", help="Compute NDVI and export as TIF")
55
+ ndvi_parser.add_argument("-i", "--input", required=True, help="Input .gndc model file")
56
+ ndvi_parser.add_argument("-o", "--output", required=True, help="Output NDVI TIF file")
57
+ ndvi_parser.add_argument("--timestamp", type=int, default=0, help="Time index (default: 0)")
58
+ ndvi_parser.add_argument("--red-band", type=int, default=0, help="0-based red band index")
59
+ ndvi_parser.add_argument("--nir-band", type=int, default=1, help="0-based NIR band index")
60
+ _add_mode_arg(ndvi_parser)
61
+
62
+ # Derivative command
63
+ deriv_parser = subparsers.add_parser("derivative", help="Compute spatial gradients")
64
+ deriv_parser.add_argument("-i", "--input", required=True, help="Input .gndc model file")
65
+ deriv_parser.add_argument("-o", "--output", required=True, help="Output gradient TIF file")
66
+ deriv_parser.add_argument("--grad-mode", choices=["dx", "dy", "mag", "dir"], default="mag",
67
+ help="Derivative mode (default: mag)")
68
+ deriv_parser.add_argument("--timestamp", type=int, default=0, help="Time index (default: 0)")
69
+ _add_mode_arg(deriv_parser)
70
+
71
+ # Sample command
72
+ sample_parser = subparsers.add_parser("sample", help="Point query by coordinates")
73
+ sample_parser.add_argument("-i", "--input", required=True, help="Input .gndc model file")
74
+ sample_parser.add_argument("--lon", type=float, required=True, help="Longitude")
75
+ sample_parser.add_argument("--lat", type=float, required=True, help="Latitude")
76
+ sample_parser.add_argument("--timestamp", type=int, default=None,
77
+ help="Time index (omit for full series)")
78
+ _add_mode_arg(sample_parser)
79
+
80
+ # Timeseries command
81
+ ts_parser = subparsers.add_parser("timeseries", help="Export pixel time series to CSV")
82
+ ts_parser.add_argument("-i", "--input", required=True, help="Input .gndc model file")
83
+ ts_parser.add_argument("--row", type=int, required=True, help="Pixel row")
84
+ ts_parser.add_argument("--col", type=int, required=True, help="Pixel column")
85
+ ts_parser.add_argument("-o", "--output", required=True, help="Output CSV file")
86
+ _add_mode_arg(ts_parser)
87
+
88
+ args = parser.parse_args()
89
+
90
+ if args.command == "decompress":
91
+ reader = GNDCReader(mode=args.mode)
92
+ reader.load(args.input)
93
+
94
+ if args.timestamp:
95
+ from datetime import datetime
96
+ from pygndc.utils.date_utils import normalize_time
97
+ import numpy as np
98
+ meta = reader.dataset_meta
99
+ g_start = datetime.strptime(str(meta['global_start_date']), "%Y-%m-%d %H:%M:%S")
100
+ g_end = datetime.strptime(str(meta['global_end_date']), "%Y-%m-%d %H:%M:%S")
101
+ t_norms = meta.get('t_norms', [])
102
+ target_dt = datetime.strptime(args.timestamp, "%Y-%m-%d")
103
+ target_tnorm = normalize_time(target_dt, g_start, g_end)
104
+ t_idx = min(range(len(t_norms)), key=lambda i: abs(t_norms[i] - target_tnorm))
105
+ frame = reader.reconstruct_single_frame(t_idx)
106
+ np.save(args.output, frame)
107
+ print(f"Saved frame {t_idx} ({args.timestamp}) to {args.output}")
108
+ else:
109
+ recons_dates = None
110
+ if args.start_date and args.end_date:
111
+ recons_dates = (args.start_date, args.end_date, args.interval)
112
+ reader.reconstruct_and_save_all(args.output, recons_dates=recons_dates)
113
+
114
+ elif args.command == "info":
115
+ import json
116
+ import pygndc
117
+ ds = pygndc.open(args.model)
118
+
119
+ if args.json:
120
+ print(json.dumps(ds.meta, indent=2, default=str))
121
+ else:
122
+ print(ds.info())
123
+ ds.close()
124
+
125
+ elif args.command == "viewer":
126
+ from pygndc.viewer.interactive import GNDCInteractiveViewer
127
+ app = GNDCInteractiveViewer()
128
+ app.root.mainloop()
129
+
130
+ elif args.command == "ndvi":
131
+ import pygndc
132
+ import numpy as np
133
+ ds = pygndc.open(args.input, mode=args.mode)
134
+ ndvi = ds.ndvi(t=args.timestamp, red_band=args.red_band, nir_band=args.nir_band)
135
+
136
+ from pygndc.interop.interop import save_frame_as_tif
137
+ save_frame_as_tif(
138
+ ndvi[:, :, np.newaxis], args.output, ds.crs, ds.transform, "float32"
139
+ )
140
+ print(f"NDVI saved to {args.output}")
141
+ ds.close()
142
+
143
+ elif args.command == "derivative":
144
+ import pygndc
145
+ ds = pygndc.open(args.input, mode=args.mode)
146
+ result = ds.gradient(t=args.timestamp, mode=args.grad_mode)
147
+ if isinstance(result, tuple):
148
+ import numpy as np
149
+ result = np.sqrt(result[0] ** 2 + result[1] ** 2)
150
+
151
+ from pygndc.interop.interop import save_frame_as_tif
152
+ if result.ndim == 2:
153
+ import numpy as np
154
+ result = result[:, :, np.newaxis]
155
+ save_frame_as_tif(result, args.output, ds.crs, ds.transform, "float32")
156
+ print(f"Gradient ({args.grad_mode}) saved to {args.output}")
157
+ ds.close()
158
+
159
+ elif args.command == "sample":
160
+ import pygndc
161
+ import numpy as np
162
+ ds = pygndc.open(args.input, mode=args.mode)
163
+
164
+ if args.timestamp is not None:
165
+ vals = ds.sample(lon=args.lon, lat=args.lat, t=args.timestamp)
166
+ print(f"Values at ({args.lon}, {args.lat}), t={args.timestamp}: {vals}")
167
+ else:
168
+ series = ds.sample(lon=args.lon, lat=args.lat)
169
+ print(f"Time series at ({args.lon}, {args.lat}): shape={series.shape}")
170
+ for t_idx in range(series.shape[0]):
171
+ ts_str = ds.timestamps[t_idx] if t_idx < len(ds.timestamps) else str(t_idx)
172
+ vals_str = ", ".join(f"{v:.4f}" for v in series[t_idx])
173
+ print(f" [{t_idx}] {ts_str}: {vals_str}")
174
+ ds.close()
175
+
176
+ elif args.command == "timeseries":
177
+ import pygndc
178
+ import numpy as np
179
+ ds = pygndc.open(args.input, mode=args.mode)
180
+ series = ds.pixel_series(row=args.row, col=args.col)
181
+
182
+ # Write CSV
183
+ os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True)
184
+ n_bands = series.shape[1]
185
+ band_headers = [f"band_{i}" for i in range(n_bands)]
186
+
187
+ with open(args.output, "w") as f:
188
+ f.write("t_idx,timestamp," + ",".join(band_headers) + "\n")
189
+ for t_idx in range(series.shape[0]):
190
+ ts_str = str(ds.timestamps[t_idx]) if t_idx < len(ds.timestamps) else str(t_idx)
191
+ vals_str = ",".join(f"{v:.6f}" for v in series[t_idx])
192
+ f.write(f"{t_idx},{ts_str},{vals_str}\n")
193
+
194
+ print(f"Time series ({args.row}, {args.col}) saved to {args.output}")
195
+ ds.close()
196
+
197
+ else:
198
+ parser.print_help()
199
+
200
+
201
+ if __name__ == "__main__":
202
+ main()
pygndc/_version.py ADDED
@@ -0,0 +1,11 @@
1
+ """
2
+ pygndc version information.
3
+ """
4
+
5
+ __version__ = "1.0.0"
6
+ __author__ = "Jianbo Qi"
7
+ __email__ = "jianboqi@126.com"
8
+ __license__ = "MIT"
9
+ __url__ = "https://github.com/jianboqi/pygndc"
10
+
11
+ VERSION = __version__
@@ -0,0 +1,24 @@
1
+ """
2
+ Analysis module for GNDC.
3
+
4
+ This module provides functions for analyzing compressed geospatial data,
5
+ including vegetation indices and derivatives.
6
+ """
7
+
8
+ from pygndc.analysis.vegetation import (
9
+ compute_ndvi,
10
+ compute_evi,
11
+ compute_ndwi,
12
+ compute_ndmi,
13
+ compute_temporal_gradient,
14
+ compute_spatial_gradient
15
+ )
16
+
17
+ __all__ = [
18
+ 'compute_ndvi',
19
+ 'compute_evi',
20
+ 'compute_ndwi',
21
+ 'compute_ndmi',
22
+ 'compute_temporal_gradient',
23
+ 'compute_spatial_gradient'
24
+ ]
@@ -0,0 +1,160 @@
1
+ """
2
+ Analysis module for GNDC.
3
+
4
+ This module provides functions for analyzing compressed data,
5
+ including derivatives and vegetation indices.
6
+ """
7
+
8
+ import numpy as np
9
+
10
+
11
+ def compute_ndvi(red, nir):
12
+ """
13
+ Compute Normalized Difference Vegetation Index.
14
+
15
+ Args:
16
+ red: Red band data
17
+ nir: Near-infrared band data
18
+
19
+ Returns:
20
+ NDVI values in range [-1, 1]
21
+ """
22
+ with np.errstate(divide='ignore', invalid='ignore'):
23
+ ndvi = (nir - red) / (nir + red + 1e-8)
24
+ ndvi = np.where(np.isfinite(ndvi), ndvi, np.nan)
25
+ return ndvi
26
+
27
+
28
+ def compute_evi(blue, green, nir, g=2.5, c1=6, c2=7.5, l=1):
29
+ """
30
+ Compute Enhanced Vegetation Index.
31
+
32
+ Args:
33
+ blue: Blue band data
34
+ green: Green band data
35
+ nir: Near-infrared band data
36
+ g: Gain factor
37
+ c1, c2: Coefficient parameters
38
+ l: Canopy background adjustment
39
+
40
+ Returns:
41
+ EVI values
42
+ """
43
+ with np.errstate(divide='ignore', invalid='ignore'):
44
+ evi = g * (nir - red) / (nir + c1 * red - c2 * blue + l + 1e-8)
45
+ evi = np.where(np.isfinite(evi), evi, np.nan)
46
+ return evi
47
+
48
+
49
+ def compute_ndwi(green, nir):
50
+ """
51
+ Compute Normalized Difference Water Index.
52
+
53
+ Args:
54
+ green: Green band data
55
+ nir: Near-infrared band data
56
+
57
+ Returns:
58
+ NDWI values in range [-1, 1]
59
+ """
60
+ with np.errstate(divide='ignore', invalid='ignore'):
61
+ ndwi = (green - nir) / (green + nir + 1e-8)
62
+ ndwi = np.where(np.isfinite(ndwi), ndwi, np.nan)
63
+ return ndwi
64
+
65
+
66
+ def compute_ndmi(nir, swir):
67
+ """
68
+ Compute Normalized Difference Moisture Index.
69
+
70
+ Args:
71
+ nir: Near-infrared band data
72
+ swir: Short-wave infrared band data
73
+
74
+ Returns:
75
+ NDMI values in range [-1, 1]
76
+ """
77
+ with np.errstate(divide='ignore', invalid='ignore'):
78
+ ndmi = (nir - swir) / (nir + swir + 1e-8)
79
+ ndmi = np.where(np.isfinite(ndmi), ndmi, np.nan)
80
+ return ndmi
81
+
82
+
83
+ def compute_temporal_gradient(time_series_data):
84
+ """
85
+ Compute temporal gradient (first derivative) of time series.
86
+
87
+ Args:
88
+ time_series_data: 3D array [T, H, W] or 2D array [T, pixels]
89
+
90
+ Returns:
91
+ Temporal gradient array of same shape
92
+ """
93
+ if time_series_data.ndim == 3:
94
+ T, H, W = time_series_data.shape
95
+ gradient = np.zeros_like(time_series_data)
96
+ gradient[1:-1] = (time_series_data[2:] - time_series_data[:-2]) / 2
97
+ gradient[0] = time_series_data[1] - time_series_data[0]
98
+ gradient[-1] = time_series_data[-1] - time_series_data[-2]
99
+ else:
100
+ T = time_series_data.shape[0]
101
+ gradient = np.zeros_like(time_series_data)
102
+ gradient[1:-1] = (time_series_data[2:] - time_series_data[:-2]) / 2
103
+ gradient[0] = time_series_data[1] - time_series_data[0]
104
+ gradient[-1] = time_series_data[-1] - time_series_data[-2]
105
+
106
+ return gradient
107
+
108
+
109
+ def compute_spatial_gradient(image_data):
110
+ """
111
+ Compute spatial gradient using Sobel operator.
112
+
113
+ Args:
114
+ image_data: 2D array [H, W] or 3D array [H, W, C]
115
+
116
+ Returns:
117
+ Tuple of (grad_x, grad_y) gradient arrays
118
+ """
119
+ if image_data.ndim == 3:
120
+ grad_x = np.zeros_like(image_data)
121
+ grad_y = np.zeros_like(image_data)
122
+ for c in range(image_data.shape[2]):
123
+ gx, gy = _sobel_2d(image_data[:, :, c])
124
+ grad_x[:, :, c] = gx
125
+ grad_y[:, :, c] = gy
126
+ else:
127
+ grad_x, grad_y = _sobel_2d(image_data)
128
+
129
+ return grad_x, grad_y
130
+
131
+
132
+ def _sobel_2d(img):
133
+ """
134
+ Apply Sobel operator to 2D image.
135
+ """
136
+ sx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
137
+ sy = sx.T
138
+
139
+ pad_img = np.pad(img, 1, mode='edge')
140
+
141
+ gx = np.zeros_like(img)
142
+ gy = np.zeros_like(img)
143
+
144
+ for i in range(img.shape[0]):
145
+ for j in range(img.shape[1]):
146
+ window = pad_img[i:i+3, j:j+3]
147
+ gx[i, j] = np.sum(window * sx)
148
+ gy[i, j] = np.sum(window * sy)
149
+
150
+ return gx, gy
151
+
152
+
153
+ __all__ = [
154
+ 'compute_ndvi',
155
+ 'compute_evi',
156
+ 'compute_ndwi',
157
+ 'compute_ndmi',
158
+ 'compute_temporal_gradient',
159
+ 'compute_spatial_gradient'
160
+ ]
@@ -0,0 +1,9 @@
1
+ """
2
+ Dataset module for pygndc.
3
+
4
+ Provides the high-level GNDCDataset API for GDAL/rasterio-style access.
5
+ """
6
+
7
+ from pygndc.dataset.dataset import GNDCDataset
8
+
9
+ __all__ = ["GNDCDataset"]