pysits 1.5.3.dev0__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.
Files changed (73) hide show
  1. LICENSE +339 -0
  2. README.md +83 -0
  3. pysits/__init__.py +243 -0
  4. pysits/backend/__init__.py +18 -0
  5. pysits/backend/functions.py +50 -0
  6. pysits/backend/loaders.py +92 -0
  7. pysits/backend/pkgs.py +35 -0
  8. pysits/conversions/__init__.py +18 -0
  9. pysits/conversions/clojure.py +68 -0
  10. pysits/conversions/common.py +280 -0
  11. pysits/conversions/decorators.py +148 -0
  12. pysits/conversions/dsl/__init__.py +18 -0
  13. pysits/conversions/dsl/base.py +43 -0
  14. pysits/conversions/dsl/mask.py +337 -0
  15. pysits/conversions/dsl/tuning.py +129 -0
  16. pysits/conversions/tibble.py +444 -0
  17. pysits/conversions/tibble_arrow.py +469 -0
  18. pysits/conversions/vector.py +137 -0
  19. pysits/conversions/xarray.py +207 -0
  20. pysits/docs/__init__.py +22 -0
  21. pysits/docs/content/sits_get_data.md +85 -0
  22. pysits/docs/decorators.py +58 -0
  23. pysits/extras/__init__.py +82 -0
  24. pysits/extras/earthdatalogin.py +44 -0
  25. pysits/extras/torch.py +44 -0
  26. pysits/jinja.py +56 -0
  27. pysits/models/__init__.py +18 -0
  28. pysits/models/base.py +34 -0
  29. pysits/models/data/__init__.py +22 -0
  30. pysits/models/data/accuracy.py +94 -0
  31. pysits/models/data/base.py +51 -0
  32. pysits/models/data/cube.py +210 -0
  33. pysits/models/data/frame.py +138 -0
  34. pysits/models/data/frame_accessor.py +133 -0
  35. pysits/models/data/matrix.py +119 -0
  36. pysits/models/data/table.py +49 -0
  37. pysits/models/data/ts.py +161 -0
  38. pysits/models/data/tuning.py +307 -0
  39. pysits/models/data/vector.py +47 -0
  40. pysits/models/frame.py +191 -0
  41. pysits/models/ml.py +27 -0
  42. pysits/models/resolver.py +147 -0
  43. pysits/settings.py +44 -0
  44. pysits/sits/__init__.py +18 -0
  45. pysits/sits/classification.py +42 -0
  46. pysits/sits/colors.py +50 -0
  47. pysits/sits/config.py +45 -0
  48. pysits/sits/context.py +36 -0
  49. pysits/sits/cube.py +214 -0
  50. pysits/sits/data.py +220 -0
  51. pysits/sits/exporters/__init__.py +29 -0
  52. pysits/sits/exporters/files.py +37 -0
  53. pysits/sits/exporters/sf.py +30 -0
  54. pysits/sits/exporters/xarray.py +65 -0
  55. pysits/sits/impute.py +38 -0
  56. pysits/sits/ml.py +110 -0
  57. pysits/sits/segment.py +38 -0
  58. pysits/sits/tiles.py +35 -0
  59. pysits/sits/ts.py +216 -0
  60. pysits/sits/tuning.py +82 -0
  61. pysits/sits/utils.py +126 -0
  62. pysits/sits/visualization.py +122 -0
  63. pysits/templates/cube.html +196 -0
  64. pysits/templates/tuning.html +183 -0
  65. pysits/visualization/__init__.py +24 -0
  66. pysits/visualization/base.py +163 -0
  67. pysits/visualization/image.py +48 -0
  68. pysits/visualization/leaflet.py +94 -0
  69. pysits/visualization/tmap.py +85 -0
  70. pysits-1.5.3.dev0.dist-info/METADATA +123 -0
  71. pysits-1.5.3.dev0.dist-info/RECORD +73 -0
  72. pysits-1.5.3.dev0.dist-info/WHEEL +4 -0
  73. pysits-1.5.3.dev0.dist-info/licenses/LICENSE +339 -0
@@ -0,0 +1,444 @@
1
+ #
2
+ # Copyright (C) 2025 sits developers.
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify it
5
+ # under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
16
+ #
17
+
18
+ """tibble conversions."""
19
+
20
+ import warnings
21
+ from collections.abc import Callable
22
+
23
+ from geopandas import GeoDataFrame as GeoPandasDataFrame
24
+ from pandas import DataFrame as PandasDataFrame
25
+ from pandas import to_datetime as pandas_to_datetime
26
+ from rpy2 import robjects
27
+ from rpy2.robjects import StrVector, pandas2ri
28
+ from rpy2.robjects.conversion import localconverter
29
+ from rpy2.robjects.vectors import DataFrame as RDataFrame
30
+ from shapely import wkt
31
+
32
+ from pysits.backend.functions import r_fnc_class
33
+ from pysits.backend.pkgs import r_pkg_base, r_pkg_sf
34
+ from pysits.models.frame import SITSFrameArray
35
+
36
+
37
+ #
38
+ # Auxiliary functions
39
+ #
40
+ def _column_to_datetime(data: PandasDataFrame, colname: str) -> PandasDataFrame:
41
+ """Transform a columns from R to a valid datetime column in Python.
42
+
43
+ Args:
44
+ data (pandas.DataFrame): Pandas Data Frame from an R tibble/data.frame.
45
+
46
+ colname (str): Column to be converted datetime (from R format to Python format).
47
+
48
+ Returns:
49
+ pandas.DataFrame: Pandas data frame with ``colname`` as datetime.
50
+ """
51
+ # Convert if column is available
52
+ if colname in data.columns:
53
+ data[colname] = pandas_to_datetime(data[colname], origin="1970-01-01", unit="D")
54
+
55
+ # Return!
56
+ return data
57
+
58
+
59
+ def _sf_to_shapely(sf_object: RDataFrame) -> list:
60
+ """Transform a columns from R to a valid geometry column in Python.
61
+
62
+ Args:
63
+ sf_object (rpy2.robjects.vectors.ListVector): R (tibble/data.frame) Data frame.
64
+
65
+ Returns:
66
+ list: List of Shapely geometries.
67
+ """
68
+ # Extract geometry and convert to WKT in R directly
69
+ geom_wkt = r_pkg_sf.st_as_text(r_pkg_sf.st_geometry(sf_object))
70
+
71
+ # Convert R character vector to Python list of strings
72
+ geom_wkt_py = list(geom_wkt)
73
+
74
+ # Convert each WKT string to a Shapely geometry
75
+ return [wkt.loads(g) for g in geom_wkt_py]
76
+
77
+
78
+ #
79
+ # Base conversion function
80
+ #
81
+ def _tibble_to_pandas(
82
+ data: RDataFrame,
83
+ nested_columns: list | None = None,
84
+ table_processor: Callable[[PandasDataFrame], PandasDataFrame] | None = None,
85
+ nested_processor: Callable[[PandasDataFrame], PandasDataFrame] | None = None,
86
+ ) -> PandasDataFrame:
87
+ """Convert an R tibble containing nested data frames to a Pandas DataFrame.
88
+
89
+ Args:
90
+ data (RDataFrame): An R tibble/data.frame object that contains
91
+ nested data frames.
92
+
93
+ nested_columns (list): List of column names that contain nested
94
+ data frames.
95
+
96
+ table_processor (Callable | None, optional):
97
+ A function to process the main table after conversion. The function should
98
+ take a pandas DataFrame as input and return a processed pandas DataFrame.
99
+ Defaults to None.
100
+
101
+ nested_processor (Callable | None, optional):
102
+ A function to process each nested data frame after conversion. The function
103
+ should take a pandas DataFrame as input and return a processed pandas
104
+ DataFrame. Defaults to None.
105
+
106
+ Returns:
107
+ PandasDataFrame: A pandas DataFrame where the nested columns are converted to
108
+ SITSFrameArray objects containing the processed nested
109
+ data frames.
110
+ """
111
+ # Check if the data is an SF object
112
+ has_geometries = "sf" in r_fnc_class(data)
113
+
114
+ # Define shapely geometries and CRS
115
+ shapely_crs = None
116
+ shapely_geometries = None
117
+
118
+ # Handle SF objects
119
+ if has_geometries:
120
+ # Convert geometry to Shapely geometries
121
+ shapely_geometries = _sf_to_shapely(data)
122
+
123
+ # Get CRS
124
+ shapely_crs = r_pkg_sf.st_crs(data)
125
+
126
+ # Check if CRS is available
127
+ if "NULL" not in r_fnc_class(shapely_crs):
128
+ shapely_crs = shapely_crs.rx2("wkt")[0]
129
+
130
+ # Drop geometry column
131
+ data = r_pkg_sf.st_drop_geometry(data)
132
+
133
+ # Convert columns definitions
134
+ nested_columns = nested_columns if nested_columns else []
135
+
136
+ # Extract columns from the data
137
+ data_columns = r_pkg_base.colnames(data)
138
+
139
+ # Remove invalid columns
140
+ data_columns_valid = []
141
+
142
+ for data_column in data_columns:
143
+ col = data.rx2(data_column)
144
+
145
+ # Remove invalid columns
146
+ if r_fnc_class(col[0])[0] not in ["function", "NULL"]:
147
+ data_columns_valid.append(data_column)
148
+
149
+ # Replace old data columns with the filtered one
150
+ data_columns = data_columns_valid
151
+
152
+ # If user define nested columns, verify if they are available.
153
+ if nested_columns:
154
+ # Filter available nested columns
155
+ nested_columns = [v for v in nested_columns if v in data_columns]
156
+
157
+ # If selected columns are available, remove them from the data columns
158
+ # This allows us to handle them individually.
159
+ if nested_columns:
160
+ data_columns = list(set(data_columns).difference(nested_columns))
161
+
162
+ # Select regular columns (using ``[]``) and convert to Pandas
163
+ rdf_data = data.rx(StrVector(data_columns))
164
+ rdf_data = pandas2ri.rpy2py(rdf_data)
165
+
166
+ # Handle nested columns if available
167
+ for nested_column in nested_columns:
168
+ # Select nested column (using ``[[]]``)
169
+ nested_column_data = data.rx2(nested_column)
170
+
171
+ # As it is a nested column, handle it as a list of ``tibble/data.frame``
172
+ nested_column_processed = []
173
+
174
+ for nested_row in nested_column_data:
175
+ # Convert to pandas
176
+ nested_row_df = pandas2ri.rpy2py(nested_row)
177
+
178
+ # If a processor function is available, apply data to use
179
+ if nested_processor and isinstance(nested_row_df, PandasDataFrame):
180
+ nested_row_df = nested_processor(nested_row_df)
181
+
182
+ # Save
183
+ nested_column_processed.append(nested_row_df)
184
+
185
+ # Convert column to SITS Array and save to the main data frame
186
+ rdf_data[nested_column] = SITSFrameArray(nested_column_processed)
187
+
188
+ # If a processor function is available to the main table, use it
189
+ if table_processor:
190
+ rdf_data = table_processor(rdf_data)
191
+
192
+ # Transform dataframe to geodataframe
193
+ if shapely_geometries:
194
+ rdf_data = GeoPandasDataFrame(
195
+ rdf_data, geometry=shapely_geometries, crs=shapely_crs
196
+ )
197
+
198
+ # Return!
199
+ return rdf_data
200
+
201
+
202
+ #
203
+ # General function
204
+ #
205
+ def tibble_to_pandas(data: RDataFrame) -> PandasDataFrame:
206
+ """Convert any tibble to Pandas DataFrame.
207
+
208
+ Args:
209
+ data (rpy2.robjects.vectors.DataFrame): R (tibble/data.frame) Data frame.
210
+
211
+ Returns:
212
+ pandas.DataFrame: R Data Frame as Pandas.
213
+ """
214
+
215
+ # Define table processor
216
+ def _table_processor(x):
217
+ """Table processor."""
218
+ # Update date columns
219
+ x = _column_to_datetime(x, "start_date")
220
+ x = _column_to_datetime(x, "end_date")
221
+
222
+ return x
223
+
224
+ # Convert and return
225
+ return _tibble_to_pandas(
226
+ data=data,
227
+ table_processor=_table_processor,
228
+ )
229
+
230
+
231
+ def tibble_nested_to_pandas(
232
+ data: RDataFrame,
233
+ nested_columns: list,
234
+ table_processor: Callable[[PandasDataFrame], PandasDataFrame] | None = None,
235
+ nested_processor: Callable[[PandasDataFrame], PandasDataFrame] | None = None,
236
+ ) -> PandasDataFrame:
237
+ """(Public) Convert an R tibble containing nested data frames to a Pandas DataFrame.
238
+
239
+ Args:
240
+ data (RDataFrame): An R tibble/data.frame object that contains
241
+ nested data frames.
242
+
243
+ nested_columns (list): List of column names that contain nested
244
+ data frames.
245
+
246
+ table_processor (Callable | None, optional):
247
+ A function to process the main table after conversion. The function should
248
+ take a pandas DataFrame as input and return a processed pandas DataFrame.
249
+ Defaults to None.
250
+
251
+ nested_processor (Callable | None, optional):
252
+ A function to process each nested data frame after conversion. The function
253
+ should take a pandas DataFrame as input and return a processed pandas
254
+ DataFrame. Defaults to None.
255
+
256
+ Returns:
257
+ PandasDataFrame: A pandas DataFrame where the nested columns are converted to
258
+ SITSFrameArray objects containing the processed nested
259
+ data frames.
260
+ """
261
+ return _tibble_to_pandas(
262
+ data=data,
263
+ nested_columns=nested_columns,
264
+ table_processor=table_processor,
265
+ nested_processor=nested_processor,
266
+ )
267
+
268
+
269
+ #
270
+ # SITS conversions function
271
+ #
272
+ def tibble_sits_to_pandas(data: RDataFrame) -> PandasDataFrame:
273
+ """Convert sits tibble to Pandas Data Frame.
274
+
275
+ Args:
276
+ data (rpy2.robjects.vectors.DataFrame): R (tibble/data.frame) Data frame.
277
+
278
+ Returns:
279
+ pandas.DataFrame: R Data Frame as Pandas.
280
+ """
281
+ # Define column order (from R)
282
+ column_order = [
283
+ "longitude",
284
+ "latitude",
285
+ "start_date",
286
+ "end_date",
287
+ "label",
288
+ "cube",
289
+ "time_series",
290
+ ]
291
+
292
+ # Define nested columns
293
+ nested_columns = ["time_series", "predicted"]
294
+
295
+ # Define table processor
296
+ def _table_processor(x):
297
+ """Table processor."""
298
+ # Update date columns
299
+ x = _column_to_datetime(x, "start_date")
300
+ x = _column_to_datetime(x, "end_date")
301
+
302
+ x_columns = list(filter(lambda y: y in x.columns, column_order))
303
+ x_columns = x_columns + list(set(x.columns).difference(x_columns))
304
+
305
+ return x[x_columns]
306
+
307
+ # Define nested processor
308
+ def _nested_processor(x):
309
+ """Nested processor."""
310
+ return _column_to_datetime(x, "Index")
311
+
312
+ # Convert and return
313
+ return _tibble_to_pandas(
314
+ data=data,
315
+ nested_columns=nested_columns,
316
+ table_processor=_table_processor,
317
+ nested_processor=_nested_processor,
318
+ )
319
+
320
+
321
+ #
322
+ # Cube conversions function
323
+ #
324
+ def tibble_cube_to_pandas(data: RDataFrame) -> PandasDataFrame:
325
+ """Convert sits tibble to Pandas Data Frame.
326
+
327
+ Args:
328
+ data (rpy2.robjects.vectors.DataFrame): R (tibble/data.frame) Data frame.
329
+
330
+ Returns:
331
+ pandas.DataFrame: R Data Frame as Pandas.
332
+ """
333
+ # Define column order (from R)
334
+ column_order = [
335
+ "source",
336
+ "collection",
337
+ "satellite",
338
+ "sensor",
339
+ "tile",
340
+ "xmin",
341
+ "xmax",
342
+ "ymin",
343
+ "ymax",
344
+ "crs",
345
+ "labels",
346
+ "file_info",
347
+ "vector_info",
348
+ ]
349
+
350
+ # Define nested columns
351
+ nested_columns = ["labels", "file_info", "vector_info"]
352
+
353
+ # Define table processor
354
+ def _table_processor(x):
355
+ """Table processor."""
356
+ columns_available = [v for v in column_order if v in x.columns]
357
+
358
+ return x[columns_available]
359
+
360
+ # Define nested processor
361
+ def _nested_processor(x):
362
+ """Nested processor."""
363
+ x = _column_to_datetime(x, "date")
364
+ x = _column_to_datetime(x, "start_date")
365
+ x = _column_to_datetime(x, "end_date")
366
+
367
+ return x
368
+
369
+ # Convert and return
370
+ return _tibble_to_pandas(
371
+ data=data,
372
+ nested_columns=nested_columns,
373
+ table_processor=_table_processor,
374
+ nested_processor=_nested_processor,
375
+ )
376
+
377
+
378
+ #
379
+ # Pandas to R conversions
380
+ #
381
+ def pandas_to_tibble(data: PandasDataFrame) -> RDataFrame:
382
+ """Convert a pandas DataFrame to an R DataFrame object.
383
+
384
+ This function converts a pandas DataFrame to an R DataFrame using
385
+ rpy2's conversion infrastructure. It handles the conversion context
386
+ to ensure proper type mapping between Python and R objects.
387
+
388
+ Args:
389
+ data (pandas.DataFrame): The pandas DataFrame to convert to R.
390
+
391
+ Returns:
392
+ rpy2.robjects.vectors.DataFrame: The converted R DataFrame object.
393
+
394
+ Notes:
395
+ - The function uses rpy2's localconverter to ensure proper conversion context
396
+ - Handles both DataFrame and non-DataFrame inputs
397
+ - Preserves column names and data types where possible
398
+ - For non-DataFrame inputs, falls back to rpy2's default converter
399
+ """
400
+ with localconverter(robjects.default_converter + pandas2ri.converter):
401
+ return robjects.conversion.py2rpy(data)
402
+
403
+
404
+ def geopandas_to_tibble(data: GeoPandasDataFrame) -> RDataFrame:
405
+ """Convert pandas DataFrame or GeoDataFrame to R DataFrame or sf object.
406
+
407
+ Removes columns that contain embedded pandas DataFrames.
408
+ """
409
+ data = GeoPandasDataFrame(data)
410
+
411
+ if data.crs is None:
412
+ raise ValueError("GeoDataFrame must have a CRS")
413
+
414
+ # Identify columns where no cell is a DataFrame
415
+ safe_columns = [col for col in data.columns if not data[col].dtype.name == "sits"]
416
+
417
+ # Warn if columns are dropped
418
+ dropped_columns = set(data.columns) - set(safe_columns)
419
+ if dropped_columns:
420
+ warnings.warn(
421
+ f"Warning: Dropping columns with embedded DataFrames: {dropped_columns}"
422
+ )
423
+
424
+ # Keep only safe columns
425
+ data_safe = data[safe_columns].copy()
426
+
427
+ # If GeoDataFrame, convert geometry to WKT and include geometry column
428
+ if isinstance(data, GeoPandasDataFrame):
429
+ geom_col = data.geometry.name
430
+ data_safe[geom_col] = data.geometry.to_wkt()
431
+
432
+ # Convert to R DataFrame
433
+ with localconverter(robjects.default_converter + pandas2ri.converter):
434
+ r_df = robjects.conversion.py2rpy(data_safe)
435
+
436
+ # If GeoDataFrame, convert to sf
437
+ if isinstance(data, GeoPandasDataFrame):
438
+ r_df = r_pkg_sf.st_as_sf(
439
+ r_df,
440
+ wkt=robjects.StrVector([geom_col]),
441
+ crs=robjects.StrVector([data.crs.to_wkt()]),
442
+ )
443
+
444
+ return r_df