diff --git a/README.md b/README.md index 7e16f3d..4c8ff7b 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # ZarrViewer -Un explorador rápido y ligero para archivos `.zarr` desde la terminal. Permite visualizar la estructura, los tiempos y generar gráficos inteligentes (mapas o series de tiempo) con un solo comando. +Un explorador rápido y ligero para archivos `.zarr` desde la terminal. Permite visualizar la estructura, los tiempos y generar gráficos inteligentes (mapas, series de tiempo o **snapshots estáticos**) con un solo comando. ## Instalación 1. Asegúrate de tener Python 3.6 o superior. 2. Instala las dependencias necesarias: + ```bash pip install xarray pandas zarr matplotlib ``` 3. Convierte el script en un comando nativo del sistema: + ```bash # Quítale la extensión .py mv zarrviewer.py zarrviewer @@ -26,6 +28,7 @@ Un explorador rápido y ligero para archivos `.zarr` desde la terminal. Permite ## Modo de Uso ### 1. Exploración Básica (Listar variables y estructura) + Si pasas solo la ruta del archivo (o usas la bandera `-h`), el programa imprimirá la estructura completa del archivo (similar a `ncdump -h`) y listará todas las variables disponibles para graficar. ```bash @@ -35,23 +38,40 @@ zarrviewer ruta/al/archivo.zarr -h ``` ### 2. Ver Tiempos Disponibles + Muestra el inicio y el final de la serie de tiempo disponible. + ```bash zarrviewer ruta/al/archivo.zarr ``` + Opciones adicionales: -* `-f 10`: Muestra los primeros 10 tiempos (por defecto son 3). -* `-l 10`: Muestra los últimos 10 tiempos. -* `-a`: Imprime todos los tiempos disponibles en el archivo. + +* `-f 10`: Muestra los primeros 10 tiempos (por defecto son 3). +* `-l 10`: Muestra los últimos 10 tiempos. +* `-a`: Imprime todos los tiempos disponibles en el archivo. ### 3. Generar un Mapa Geográfico (2D) -Si la variable tiene 2 dimensiones o más (ej. Lat/Lon, o X/Y en WRF), generará automáticamente un mapa georreferenciado del **último frame temporal disponible** con la escala corregida (1:1). + +Si la variable tiene 2 dimensiones o más (ej. Lat/Lon, o X/Y en WRF y WindNinja), generará automáticamente un mapa georreferenciado con la escala corregida (1:1). Por defecto, grafica el **último frame temporal disponible** (o el único, si es un snapshot estático). ```bash zarrviewer archivo.zarr -v T2 ``` +**Seleccionar un frame específico:** +Puedes usar la opción `--frame` para elegir qué índice de tiempo graficar (0 es el primero, 1 el segundo, -1 el último). + +```bash +# Graficar el primer frame (índice 0) +zarrviewer archivo.zarr -v T2 --frame 0 + +# Graficar el décimo frame (índice 9) +zarrviewer archivo.zarr -v T2 --frame 9 +``` + ### 4. Generar una Serie de Tiempo (1D o Estaciones) + Si la variable está asociada a una dimensión unidimensional (como estaciones meteorológicas), debes especificar el valor/estación deseada separada por dos puntos `:` o un punto `.`. ```bash @@ -60,9 +80,11 @@ zarrviewer archivo.zarr -v wind_speed_5m_prom:LSC ``` **Sugerencia Interactiva:** Si olvidas especificar la estación, el programa te listará todos los valores posibles y te dará ejemplos listos para copiar y pegar: + ```bash zarrviewer archivo.zarr -v wind_speed_5m_prom ``` Opciones de graficado: -* `-n 100`: Número de últimos registros a incluir en la serie de tiempo (por defecto extrae 50 puntos). + +* `-n 100`: Número de últimos registros a incluir en la serie de tiempo (por defecto extrae 50 puntos). diff --git a/zarrviewer b/zarrviewer index 0d37ce3..31c3a86 100755 --- a/zarrviewer +++ b/zarrviewer @@ -30,10 +30,14 @@ def main(): help="Mostrar todos los tiempos") parser.add_argument("--coord", "-c", type=str, default=None, help="Nombre de la coord. temporal (auto-detectada por defecto)") + + # ARGUMENTOS DE GRAFICADO parser.add_argument("--variable", "-v", type=str, default=None, help="Variable a graficar. Sintaxis: var, var:dim, var.dim o 'var(dim)'") parser.add_argument("-n", "--npoints", type=int, default=50, help="Número de registros a graficar en series de tiempo (default: 50)") + parser.add_argument("--frame", type=int, default=-1, + help="Índice del frame temporal a graficar en mapas (default: -1, el último)") args = parser.parse_args() @@ -84,7 +88,6 @@ def main(): print("No se encontró una coordenada temporal.", file=sys.stderr) sys.exit(1) - # === CORRECCIÓN VITAL: Manejo de snapshot estático (0 dimensiones temporales) === dims_of_time = ds[time_coord].dims if len(dims_of_time) > 0: time_dim = dims_of_time[0] @@ -92,7 +95,6 @@ def main(): total = len(times) print(f"Usando coordenada temporal: '{time_coord}' (Eje dimensional: '{time_dim}')") else: - # Es un único frame estático time_dim = None times = [ds[time_coord].values[()]] total = 1 @@ -148,18 +150,28 @@ def main(): # CASO A: Mapa (2D o más) if len(other_dims) >= 2: - ultimo_time_str = format_time(times[-1]) - print(f"-> Detectado formato de mapa (Dimensiones: {other_dims}).") - print(f"-> Extrayendo último frame ({ultimo_time_str})...") + frame_idx = args.frame + # Validamos que el frame exista + if time_dim: + if frame_idx >= total or frame_idx < -total: + print(f"[ERROR] El frame {frame_idx} está fuera de rango. Rango válido: 0 a {total-1}.", file=sys.stderr) + sys.exit(1) + target_time_str = format_time(times[frame_idx]) + print(f"-> Detectado formato de mapa (Dimensiones: {other_dims}).") + print(f"-> Extrayendo frame temporal {frame_idx} ({target_time_str})...") + else: + target_time_str = format_time(times[0]) + print(f"-> Detectado formato de mapa estático (Dimensiones: {other_dims}).") + # Cortamos en tiempo SOLO si la variable depende del tiempo ds_plot = ds[var_name] if time_dim and time_dim in ds_plot.dims: - ds_plot = ds_plot.isel({time_dim: -1}) + ds_plot = ds_plot.isel({time_dim: frame_idx}) plot_kwargs = {} - # Buscamos georreferencia (agregué 'y' y 'x' para que use los de UTM por defecto) + # Buscamos georreferencia lat_name, lon_name = None, None for lat in ['lat', 'latitude', 'XLAT', 'xlat', 'y']: if lat in ds.variables or lat in ds.coords: @@ -174,11 +186,11 @@ def main(): print(f"-> Georreferencia detectada: '{lat_name}', '{lon_name}'. Aplicando ejes...") lat_arr = ds[lat_name] if time_dim and time_dim in lat_arr.dims: - lat_arr = lat_arr.isel({time_dim: -1}) + lat_arr = lat_arr.isel({time_dim: frame_idx}) lon_arr = ds[lon_name] if time_dim and time_dim in lon_arr.dims: - lon_arr = lon_arr.isel({time_dim: -1}) + lon_arr = lon_arr.isel({time_dim: frame_idx}) ds_plot = ds_plot.assign_coords({lat_name: lat_arr, lon_name: lon_arr}) plot_kwargs['x'] = lon_name @@ -192,8 +204,12 @@ def main(): ds_plot.plot(**plot_kwargs) plt.gca().set_aspect('equal') - plt.title(f"{var_name} | Frame: {ultimo_time_str}") - nombre_imagen = f"mapa_{var_name}.png" if time_dim is None else f"mapa_{var_name}_{pd.to_datetime(times[-1]).strftime('%Y%m%d_%H%M')}.png" + plt.title(f"{var_name} | Frame: {target_time_str}") + + if time_dim is None: + nombre_imagen = f"mapa_{var_name}.png" + else: + nombre_imagen = f"mapa_{var_name}_frame{frame_idx}_{pd.to_datetime(times[frame_idx]).strftime('%Y%m%d_%H%M')}.png" # CASO B: Puntos / Estaciones (1D adicional) elif len(other_dims) == 1: @@ -204,7 +220,7 @@ def main(): print(f"Se detectó que la variable '{var_name}' está asociada a la dimensión: '{dim_name}'") print(f"Valores posibles para {dim_name}:") print(", ".join(map(str, dim_values)) + "\n") - print("Ejemplos de uso (cópialos y pégalos):") + print("Ejemplos de uso:") for ex in dim_values[:3]: print(f" zarrviewer {args.zarr_path} -v {var_name}:{ex}") sys.exit(0)