viewtif 0.2.3__tar.gz → 0.2.5__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: viewtif
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: Lightweight GeoTIFF, NetCDF, HDF/HDF5, and Esri File Geodatabase (.gdb) viewer with optional shapefile overlay. NetCDF and cartopy support available via pip install viewtif[netcdf].
5
5
  Project-URL: Homepage, https://github.com/nkeikon/tifviewer
6
6
  Project-URL: Source, https://github.com/nkeikon/tifviewer
@@ -26,6 +26,9 @@ Description-Content-Type: text/markdown
26
26
  # viewtif
27
27
  [![Downloads](https://static.pepy.tech/badge/viewtif)](https://pepy.tech/project/viewtif)
28
28
  [![PyPI version](https://img.shields.io/pypi/v/viewtif)](https://pypi.org/project/viewtif/)
29
+ [![Python version](https://img.shields.io/badge/python-%3E%3D3.9-blue.svg)](https://pypi.org/project/viewtif/)
30
+
31
+
29
32
 
30
33
  A lightweight GeoTIFF viewer for quick visualization directly from the command line.
31
34
 
@@ -59,9 +62,9 @@ pip install GDAL
59
62
 
60
63
  #### NetCDF support
61
64
  ```bash
62
- brew install "viewtif[netcdf]"
65
+ pip install "viewtif[netcdf]"
63
66
  ```
64
- > **Note:** For enhanced geographic visualization with map projections, coastlines, and borders, install with cartopy: `pip install "viewtif[netcdf]"` (cartopy is included in the netcdf extra). If cartopy is not available, netCDF files will still display with standard RGB rendering.
67
+ > **Note:** For enhanced geographic visualization with map projections, coastlines, and borders, install with cartopy: `pip install "viewtif[netcdf]"` (cartopy is included in the netcdf extra). If cartopy is not available, netCDF files will still display with standard rendering.
65
68
  ## Quick Start
66
69
  ```bash
67
70
  # View a GeoTIFF
@@ -114,12 +117,7 @@ If the dataset is very large (e.g., >20 million pixels), it will pause and warn
114
117
  You can proceed manually or rerun with the `--scale` option for a smaller, faster preview.
115
118
 
116
119
  ### Update in v0.2.2: NetCDF support with optional cartopy visualization
117
- `viewtif` now supports NetCDF (`.nc`) files with xarray and optional cartopy geographic visualization.
118
-
119
- #### Installation with NetCDF support
120
- ```bash
121
- pip install "viewtif[netcdf]"
122
- ```
120
+ `viewtif` now supports NetCDF (`.nc`) files via xarray, with optional cartopy-based geographic visualization. Use`[` / `]` to move forward or backward through time steps.
123
121
 
124
122
  #### Examples
125
123
  ```bash
@@ -161,4 +159,10 @@ This project is released under the MIT License.
161
159
 
162
160
  ## Contributors
163
161
  - [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support; added NetCDF support with [@nkeikon](https://github.com/nkeikon)
164
- - [@p-vdp](https://github.com/p-vdp) — added File Geodatabase (.gdb) raster support
162
+ - [@p-vdp](https://github.com/p-vdp) — added File Geodatabase (.gdb) raster support
163
+
164
+ ## Useful links
165
+ - https://www.linkedin.com/posts/desmond-lartey_geospatial-analysts-heads-up-check-out-activity-7386336488050925568-G5bN?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAA0INsBVIO1f6nS_NkKqFh4Na1ZpoYo2fc
166
+ - https://www.linkedin.com/posts/keiko-nomura-0231891_dont-you-sometimes-just-want-to-see-a-activity-7383409138296528896-sbRM?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAA0INsBVIO1f6nS_NkKqFh4Na1ZpoYo2fc
167
+ - https://www.linkedin.com/posts/keiko-nomura-0231891_now-you-can-see-hdf-files-from-the-command-activity-7383963721561399296-F5i0?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAA0INsBVIO1f6nS_NkKqFh4Na1ZpoYo2fc
168
+ - https://www.linkedin.com/posts/keiko-nomura-0231891_you-can-now-view-netcdf-files-nc-from-activity-7386189562072670208-3A0V?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAA0INsBVIO1f6nS_NkKqFh4Na1ZpoYo2fc
@@ -1,6 +1,9 @@
1
1
  # viewtif
2
2
  [![Downloads](https://static.pepy.tech/badge/viewtif)](https://pepy.tech/project/viewtif)
3
3
  [![PyPI version](https://img.shields.io/pypi/v/viewtif)](https://pypi.org/project/viewtif/)
4
+ [![Python version](https://img.shields.io/badge/python-%3E%3D3.9-blue.svg)](https://pypi.org/project/viewtif/)
5
+
6
+
4
7
 
5
8
  A lightweight GeoTIFF viewer for quick visualization directly from the command line.
6
9
 
@@ -34,9 +37,9 @@ pip install GDAL
34
37
 
35
38
  #### NetCDF support
36
39
  ```bash
37
- brew install "viewtif[netcdf]"
40
+ pip install "viewtif[netcdf]"
38
41
  ```
39
- > **Note:** For enhanced geographic visualization with map projections, coastlines, and borders, install with cartopy: `pip install "viewtif[netcdf]"` (cartopy is included in the netcdf extra). If cartopy is not available, netCDF files will still display with standard RGB rendering.
42
+ > **Note:** For enhanced geographic visualization with map projections, coastlines, and borders, install with cartopy: `pip install "viewtif[netcdf]"` (cartopy is included in the netcdf extra). If cartopy is not available, netCDF files will still display with standard rendering.
40
43
  ## Quick Start
41
44
  ```bash
42
45
  # View a GeoTIFF
@@ -89,12 +92,7 @@ If the dataset is very large (e.g., >20 million pixels), it will pause and warn
89
92
  You can proceed manually or rerun with the `--scale` option for a smaller, faster preview.
90
93
 
91
94
  ### Update in v0.2.2: NetCDF support with optional cartopy visualization
92
- `viewtif` now supports NetCDF (`.nc`) files with xarray and optional cartopy geographic visualization.
93
-
94
- #### Installation with NetCDF support
95
- ```bash
96
- pip install "viewtif[netcdf]"
97
- ```
95
+ `viewtif` now supports NetCDF (`.nc`) files via xarray, with optional cartopy-based geographic visualization. Use`[` / `]` to move forward or backward through time steps.
98
96
 
99
97
  #### Examples
100
98
  ```bash
@@ -136,4 +134,10 @@ This project is released under the MIT License.
136
134
 
137
135
  ## Contributors
138
136
  - [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support; added NetCDF support with [@nkeikon](https://github.com/nkeikon)
139
- - [@p-vdp](https://github.com/p-vdp) — added File Geodatabase (.gdb) raster support
137
+ - [@p-vdp](https://github.com/p-vdp) — added File Geodatabase (.gdb) raster support
138
+
139
+ ## Useful links
140
+ - https://www.linkedin.com/posts/desmond-lartey_geospatial-analysts-heads-up-check-out-activity-7386336488050925568-G5bN?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAA0INsBVIO1f6nS_NkKqFh4Na1ZpoYo2fc
141
+ - https://www.linkedin.com/posts/keiko-nomura-0231891_dont-you-sometimes-just-want-to-see-a-activity-7383409138296528896-sbRM?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAA0INsBVIO1f6nS_NkKqFh4Na1ZpoYo2fc
142
+ - https://www.linkedin.com/posts/keiko-nomura-0231891_now-you-can-see-hdf-files-from-the-command-activity-7383963721561399296-F5i0?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAA0INsBVIO1f6nS_NkKqFh4Na1ZpoYo2fc
143
+ - https://www.linkedin.com/posts/keiko-nomura-0231891_you-can-now-view-netcdf-files-nc-from-activity-7386189562072670208-3A0V?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAA0INsBVIO1f6nS_NkKqFh4Na1ZpoYo2fc
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "viewtif"
7
- version = "0.2.3"
7
+ version = "0.2.5"
8
8
  description = "Lightweight GeoTIFF, NetCDF, HDF/HDF5, and Esri File Geodatabase (.gdb) viewer with optional shapefile overlay. NetCDF and cartopy support available via pip install viewtif[netcdf]."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes
@@ -42,7 +42,7 @@ import matplotlib.cm as cm
42
42
  import warnings
43
43
  warnings.filterwarnings("ignore", category=RuntimeWarning, module="shapely")
44
44
 
45
- __version__ = "0.2.2"
45
+ __version__ = "0.2.5"
46
46
 
47
47
  # Optional overlay deps
48
48
  try:
@@ -210,10 +210,31 @@ class TiffViewer(QMainWindow):
210
210
  self.tif_path = self.tif_path or (os.path.commonprefix([red, green, blue]) or red)
211
211
 
212
212
  elif tif_path:
213
- # --- Warn for large files before loading ---
213
+ # ---------------- Handle File Geodatabase (.gdb) ---------------- #
214
+ if tif_path and tif_path.lower().endswith(".gdb") and ":" not in tif_path:
215
+ import re, subprocess
216
+ gdb_path = tif_path # use full path to .gdb
217
+ try:
218
+ out = subprocess.check_output(["gdalinfo", "-norat", gdb_path], text=True)
219
+ rasters = re.findall(r"RASTER_DATASET=(\S+)", out)
220
+ if not rasters:
221
+ print(f"[WARN] No raster datasets found in {os.path.basename(gdb_path)}.")
222
+ sys.exit(0)
223
+ else:
224
+ print(f"Found {len(rasters)} raster dataset{'s' if len(rasters) > 1 else ''}:")
225
+ for i, r in enumerate(rasters):
226
+ print(f"[{i}] {r}")
227
+ print("\nUse one of these names to open. For example, to open the first raster:")
228
+ print(f'viewtif "OpenFileGDB:{gdb_path}:{rasters[0]}"')
229
+ sys.exit(0)
230
+ except subprocess.CalledProcessError as e:
231
+ print(f"[WARN] Could not inspect FileGDB: {e}")
232
+ sys.exit(0)
233
+
234
+ # --- Warn for large files before loading ---
214
235
  warn_if_large(tif_path, scale=self._scale_arg)
215
236
 
216
- # --------------------- Detect NetCDF --------------------- #
237
+ # --------------------- Detect NetCDF --------------------- #
217
238
  if tif_path and tif_path.lower().endswith((".nc", ".netcdf")):
218
239
  try:
219
240
  # Lazy-load NetCDF dependencies
@@ -359,26 +380,6 @@ class TiffViewer(QMainWindow):
359
380
  except Exception as e:
360
381
  raise RuntimeError(f"Error reading NetCDF file: {str(e)}")
361
382
 
362
- # ---------------- Handle File Geodatabase (.gdb) ---------------- #
363
- if tif_path and tif_path.lower().endswith(".gdb") and ":" not in tif_path:
364
- import re, subprocess
365
- gdb_path = tif_path # use full path to .gdb
366
- try:
367
- out = subprocess.check_output(["gdalinfo", "-norat", gdb_path], text=True)
368
- rasters = re.findall(r"RASTER_DATASET=(\S+)", out)
369
- if not rasters:
370
- print(f"[WARN] No raster datasets found in {os.path.basename(gdb_path)}.")
371
- sys.exit(0)
372
- else:
373
- print(f"Found {len(rasters)} raster dataset{'s' if len(rasters) > 1 else ''}:")
374
- for i, r in enumerate(rasters):
375
- print(f"[{i}] {r}")
376
- print("\nUse one of these names to open. For example, to open the first raster:")
377
- print(f'viewtif "OpenFileGDB:{gdb_path}:{rasters[0]}"')
378
- sys.exit(0)
379
- except subprocess.CalledProcessError as e:
380
- print(f"[WARN] Could not inspect FileGDB: {e}")
381
- sys.exit(0)
382
383
 
383
384
  # # --- Universal size check before loading ---
384
385
  # warn_if_large(tif_path, scale=self._scale_arg)
@@ -768,7 +769,7 @@ class TiffViewer(QMainWindow):
768
769
  # If first pixel row corresponds to southernmost lat → flip to make north at top
769
770
  # We'll assume data[0, :] corresponds to lats[0]
770
771
  if lat_ascending:
771
- print("[DEBUG] Flipping latitude orientation (lat ascending, data starts south)")
772
+ # print("[DEBUG] Flipping latitude orientation (lat ascending, data starts south)")
772
773
  frame = np.flipud(frame)
773
774
  # else:
774
775
  # print("[DEBUG] No flip (lat descending, already north-up)")
@@ -779,7 +780,7 @@ class TiffViewer(QMainWindow):
779
780
  first_col = lats[:, 0]
780
781
  lat_ascending = first_col[0] < first_col[-1]
781
782
  if lat_ascending:
782
- print("[DEBUG] Flipping latitude orientation (2D grid ascending)")
783
+ # print("[DEBUG] Flipping latitude orientation (2D grid ascending)")
783
784
  frame = np.flipud(frame)
784
785
  # else:
785
786
  # print("[DEBUG] No flip (2D grid already north-up)")
@@ -793,7 +794,7 @@ class TiffViewer(QMainWindow):
793
794
  return frame
794
795
 
795
796
  step = int(self._scale_arg)
796
- print(f"[DEBUG] Applying scale factor {step} to current frame")
797
+ print(f"Applying scale factor {step} to current frame")
797
798
 
798
799
  # Downsample the frame
799
800
  frame = frame[::step, ::step]
@@ -858,118 +859,6 @@ class TiffViewer(QMainWindow):
858
859
  pass
859
860
 
860
861
  return time_str
861
-
862
- # def update_time_label(self):
863
- # """Update the time label with the current time value"""
864
- # if hasattr(self, '_has_time_dim') and self._has_time_dim:
865
- # try:
866
- # time_value = self._time_values[self._time_index]
867
- # time_str = self.format_time_value(time_value)
868
-
869
- # # Update time label if it exists
870
- # if hasattr(self, 'time_label'):
871
- # self.time_label.setText(f"Time: {time_str}")
872
-
873
- # # Create a progress bar style display of time position
874
- # total = len(self._time_values)
875
- # position = self._time_index + 1
876
- # bar_width = 20 # Width of the progress bar
877
- # filled = int(bar_width * position / total)
878
- # bar = "[" + "#" * filled + "-" * (bar_width - filled) + "]"
879
-
880
- # # Show time info in status bar
881
- # step_info = f"Time step: {position}/{total} {bar} {self.format_time_value(self._time_values[self._time_index])}"
882
-
883
- # # Update status bar if it exists
884
- # if hasattr(self, 'statusBar') and callable(self.statusBar):
885
- # self.statusBar().showMessage(step_info)
886
- # else:
887
- # print(step_info)
888
- # except Exception as e:
889
- # print(f"Error updating time label: {e}")
890
-
891
- # def toggle_play_pause(self):
892
- # """Toggle play/pause animation of time steps"""
893
- # if self._is_playing:
894
- # self.stop_animation()
895
- # else:
896
- # self.start_animation()
897
-
898
- # def start_animation(self):
899
- # """Start the time animation"""
900
- # from PySide6.QtCore import QTimer
901
-
902
- # if not hasattr(self, '_play_timer') or self._play_timer is None:
903
- # self._play_timer = QTimer(self)
904
- # self._play_timer.timeout.connect(self.animation_step)
905
-
906
- # # Set animation speed (milliseconds between frames)
907
- # animation_speed = 500 # 0.5 seconds between frames
908
- # self._play_timer.start(animation_speed)
909
-
910
- # self._is_playing = True
911
- # self.play_button.setText("⏸") # Pause symbol
912
- # self.play_button.setToolTip("Pause animation")
913
-
914
- # def stop_animation(self):
915
- # """Stop the time animation"""
916
- # if hasattr(self, '_play_timer') and self._play_timer is not None:
917
- # self._play_timer.stop()
918
-
919
- # self._is_playing = False
920
- # self.play_button.setText("▶") # Play symbol
921
- # self.play_button.setToolTip("Play animation")
922
-
923
- # def animation_step(self):
924
- # """Advance one frame in the animation"""
925
- # # Go to next time step
926
- # next_time = (self._time_index + 1) % len(self._time_values)
927
- # self.time_slider.setValue(next_time)
928
-
929
- # def closeEvent(self, event):
930
- # """Clean up resources when the window is closed"""
931
- # # Stop animation timer if it's running
932
- # if hasattr(self, '_is_playing') and self._is_playing:
933
- # self.stop_animation()
934
-
935
- # # Call the parent class closeEvent
936
- # super().closeEvent(event)
937
-
938
- # def populate_date_combo(self):
939
- # """Populate the date combo box with time values"""
940
- # if hasattr(self, '_has_time_dim') and self._has_time_dim and hasattr(self, 'date_combo'):
941
- # try:
942
- # self.date_combo.clear()
943
-
944
- # # Add a reasonable subset of dates if there are too many
945
- # max_items = 100 # Maximum number of items to show in dropdown
946
-
947
- # if len(self._time_values) <= max_items:
948
- # # Add all time values
949
- # for i, time_value in enumerate(self._time_values):
950
- # time_str = self.format_time_value(time_value)
951
- # self.date_combo.addItem(time_str, i)
952
- # else:
953
- # # Add a subset of time values
954
- # step = len(self._time_values) // max_items
955
-
956
- # # Always include first and last
957
- # indices = list(range(0, len(self._time_values), step))
958
- # if (len(self._time_values) - 1) not in indices:
959
- # indices.append(len(self._time_values) - 1)
960
-
961
- # for i in indices:
962
- # time_str = self.format_time_value(self._time_values[i])
963
- # self.date_combo.addItem(f"{time_str} [{i+1}/{len(self._time_values)}]", i)
964
- # except Exception as e:
965
- # print(f"Error populating date combo: {e}")
966
-
967
- # def date_combo_changed(self, index):
968
- # """Handle date combo box selection change"""
969
- # if index >= 0:
970
- # time_index = self.date_combo.itemData(index)
971
- # if time_index is not None:
972
- # self.time_slider.setValue(time_index)
973
862
 
974
863
  def _render_rgb(self):
975
864
  if self.rgb_mode:
@@ -1049,12 +938,12 @@ class TiffViewer(QMainWindow):
1049
938
 
1050
939
  # --- Synchronize latitude orientation with normalized data ---
1051
940
  if np.ndim(lats) == 1 and lats[0] < lats[-1]:
1052
- print("[DEBUG] Lat ascending → flip lats_downsampled to match flipped data")
941
+ # print("[DEBUG] Lat ascending → flip lats_downsampled to match flipped data")
1053
942
  lats_downsampled = lats_downsampled[::-1]
1054
943
  elif np.ndim(lats) == 2:
1055
944
  first_col = lats[:, 0]
1056
945
  if first_col[0] < first_col[-1]:
1057
- print("[DEBUG] 2D lat grid ascending → flip lats_downsampled vertically")
946
+ # print("[DEBUG] 2D lat grid ascending → flip lats_downsampled vertically")
1058
947
  lats_downsampled = np.flipud(lats_downsampled)
1059
948
 
1060
949
  # Convert 0–360 longitude to −180–180 if needed
@@ -1321,7 +1210,7 @@ def run_viewer(
1321
1210
  import click
1322
1211
 
1323
1212
  @click.command()
1324
- @click.version_option("0.2.2", prog_name="viewtif")
1213
+ @click.version_option("0.2.5", prog_name="viewtif")
1325
1214
  @click.argument("tif_path", required=False)
1326
1215
  @click.option("--band", default=1, show_default=True, type=int, help="Band number to display")
1327
1216
  @click.option("--scale", default=1.0, show_default=True, type=int, help="Scale factor for display")
File without changes
File without changes