Changeset 3783 for trunk/LMDZ.MARS/util
- Timestamp:
- May 28, 2025, 5:31:59 PM (3 weeks ago)
- Location:
- trunk/LMDZ.MARS/util
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LMDZ.MARS/util/analyse_netcdf.py
r3648 r3783 1 #!/usr/bin/env python3 1 2 ############################################################ 2 3 ### Python script to analyse a NetCDF file for debugging ### 3 4 ############################################################ 4 5 5 ### This script gives useful information about a NetCDF file 6 ### to help for debugging. For each variable, it outputs the 7 ### dimensions, the min & max values, the average value and 8 ### warns the user in case of NaN or negative values. 9 ### The file name is asked to the user in the terminal. 6 7 """ 8 For each numeric variable, it outputs: 9 - Dimensions and shape 10 - Minimum & maximum values (ignoring NaNs) 11 - Mean value (ignoring NaNs) 12 - Warnings if the variable is entirely NaN or contains any NaNs/negative values 13 14 Usage: 15 1) Command-line mode: 16 python analyze_netcdf.py /path/to/your_file.nc 17 18 2) Interactive mode through the prompt: 19 python analyze_netcdf.py 20 """ 21 10 22 11 23 import os 24 import sys 25 import glob 12 26 import readline 13 import glob 27 import argparse 28 import numpy as np 14 29 from netCDF4 import Dataset 15 import numpy as np16 30 17 ############################################################ 18 ### Setup readline for file name autocompletion 19 def complete(text,state): 20 line = readline.get_line_buffer().split() 21 # Use glob to find all matching files/directories for the current text 22 if '*' not in text: 23 text += '*' 24 matches = glob.glob(os.path.expanduser(text)) 25 # Add '/' if the match is a directory 26 matches = [match + '/' if os.path.isdir(match) else match for match in matches] 27 31 32 def complete_filename(text, state): 33 """ 34 Tab-completion function for readline: completes filesystem paths. 35 Appends '/' if the match is a directory. 36 """ 37 # The text forms a partial path; glob for matching entries 38 if "*" not in text: 39 text_glob = text + "*" 40 else: 41 text_glob = text 42 matches = glob.glob(os.path.expanduser(text_glob)) 43 # Add a trailing slash for directories 44 matches = [m + "/" if os.path.isdir(m) else m for m in matches] 28 45 try: 29 46 return matches[state] … … 31 48 return None 32 49 33 ### Function to analyze a variable in a NetCDF file 50 34 51 def analyze_variable(variable): 35 # Get the data for the variable 36 data = variable[:] 52 """ 53 Print summary statistics (min, max, mean) for a numeric NetCDF variable. 54 Ignores NaNs when computing min/max/mean. Warns if any NaNs or negatives exist. 55 """ 56 name = variable.name 57 dims = variable.dimensions 58 shape = variable.shape 37 59 38 # Calculate min, max and mean 39 if np.isnan(data).all(): 40 min_val = np.nan 41 max_val = np.nan 42 mean_val = np.nan 43 else: 44 data_min = np.nanmin(data) # Min value ignoring NaN 45 data_max = np.nanmax(data) # Max value ignoring NaN 46 data_mean = np.nanmean(data) # Mean value ignoring NaN 47 48 # Check if there are any NaN values 60 try: 61 # Read the entire array into memory; this may be large for huge datasets 62 data = variable[:] 63 except Exception as e: 64 print(f"\nUnable to read variable '{name}': {e}") 65 return 66 67 # If the array is a masked array, convert to a NumPy array with masked values as np.nan 68 if hasattr(data, "mask"): 69 # Fill masked entries with NaN so that np.nanmin / np.nanmax works correctly 70 data = np.where(data.mask, np.nan, data.data) 71 72 # Determine if the variable has any valid (finite) data at all 73 if np.all(np.isnan(data)): 74 # Entirely NaN (or entirely masked) 75 print(f"\nAnalysis of variable: {name}") 76 print(f" Dimensions: {dims}") 77 print(f" Shape : {shape}") 78 print(" Entire variable is NaN or masked.") 79 return 80 81 # Compute min, max, mean ignoring NaNs 82 data_min = np.nanmin(data) 83 data_max = np.nanmax(data) 84 data_mean = np.nanmean(data) 85 86 # Check for presence of NaNs and negative values 49 87 has_nan = np.isnan(data).any() 88 has_negative = np.any(data < 0) 50 89 51 # Check for negative values 52 has_negative = (data < 0).any() 53 54 # Print the results 55 print(f"\nAnalysis of variable: {variable.name}") 56 print(f" Dimensions: {variable.dimensions}") 90 # Output 91 print(f"\nAnalysis of variable: {name}") 92 print(f" Dimensions: {dims}") 93 print(f" Shape : {shape}") 57 94 print(f" Min value : {data_min:>12.6e}") 58 95 print(f" Max value : {data_max:>12.6e}") … … 63 100 print(f" \033[93mWarning: contains negative values!\033[0m") 64 101 65 ### Main function 66 def analyze_netcdf(): 67 # Ask for the file name68 readline.set_completer(complete)69 readline.parse_and_bind('tab: complete')70 file = input("Enter the name of the NetCDF file:")71 72 # Open the NetCDF file 102 def analyze_netcdf_file(nc_path): 103 """ 104 Open the NetCDF file at nc_path and analyze each numeric variable. 105 """ 106 if not os.path.isfile(nc_path): 107 print(f"Error: File '{nc_path}' not found.") 108 return 109 73 110 try: 74 d ataset = Dataset(file,mode='r')75 except FileNotFoundError:76 print(f" File '{file}' not found.")111 ds = Dataset(nc_path, mode='r') 112 except Exception as e: 113 print(f"Error: Unable to open '{nc_path}': {e}") 77 114 return 78 79 # Iterate through all variables in the dataset to analyze them 80 for variable_name in dataset.variables: 81 variable = dataset.variables[variable_name] 82 if np.issubdtype(variable[:].dtype,np.number): 115 116 print(f"\nOpened NetCDF file: {nc_path}") 117 print(f"Number of variables: {len(ds.variables)}") 118 119 for var_name, variable in ds.variables.items(): 120 # Attempt to check if the dtype is numeric 121 try: 122 dtype = variable.dtype 123 except Exception: 124 # If reading dtype fails, skip it 125 print(f"\nSkipping variable with unknown type: {var_name}") 126 continue 127 128 if np.issubdtype(dtype, np.number) or hasattr(variable[:], "mask"): 83 129 analyze_variable(variable) 84 130 else: 85 print(f"\nSkipping non-numeric variable: {variable.name}") 86 87 # Close the NetCDF file 88 dataset.close() 131 print(f"\nSkipping non-numeric variable: {var_name}") 89 132 90 ### Call the main function 91 analyze_netcdf() 133 ds.close() 134 print("\nFinished analysis.\n") 135 136 137 def main(): 138 parser = argparse.ArgumentParser( 139 description="Analyze a NetCDF file and report min/max/mean for each numeric variable." 140 ) 141 parser.add_argument( 142 "nc_file", 143 nargs="?", 144 help="Path to the NetCDF file (if omitted, you'll be prompted)." 145 ) 146 args = parser.parse_args() 147 148 if args.nc_file: 149 # Command-line mode: directly analyze the provided file path 150 analyze_netcdf_file(args.nc_file) 151 else: 152 # Interactive mode: enable tab completion for filenames 153 readline.set_completer(complete_filename) 154 readline.parse_and_bind("tab: complete") 155 try: 156 user_input = input("Enter the path to the NetCDF file: ").strip() 157 except (EOFError, KeyboardInterrupt): 158 print("\nExiting.") 159 return 160 161 if not user_input: 162 print("No file specified. Exiting.") 163 return 164 165 analyze_netcdf_file(user_input) 166 167 168 if __name__ == "__main__": 169 main() 170 -
trunk/LMDZ.MARS/util/display_netcdf.py
r3681 r3783 1 #!/usr/bin/env python3 1 2 ############################################################## 2 3 ### Python script to visualize a variable in a NetCDF file ### 3 4 ############################################################## 4 5 5 ### This script can display any variable of a NetCDF file. 6 ### The file name, the variable to display and eventually the 7 ### dimension are asked to the user in the terminal. 6 """ 7 This script can display any numeric variable from a NetCDF file on a lat/lon map. 8 It supports variables of dimension: 9 - (latitude, longitude) 10 - (time, latitude, longitude) 11 - (altitude, latitude, longitude) 12 - (time, altitude, latitude, longitude) 13 14 Usage: 15 1) Command-line mode: 16 python display_netcdf.py /path/to/your_file.nc --variable VAR_NAME [--time-index 0] [--alt-index 0] [--cmap viridis] [--output out.png] 17 18 2) Interactive mode through the prompt: 19 python display_netcdf.py 20 21 The script will: 22 > Attempt to locate latitude and longitude variables (searching for 23 names like 'latitude', 'lat', 'longitude', 'lon'). 24 > If there is exactly one variable in the dataset, select it automatically. 25 > Prompt for time/altitude indices if needed (or accept via CLI). 26 > Handle masked arrays, converting masked values to NaN. 27 > Plot with a default colormap ('jet'), adjustable via --cmap. 28 > Optionally save the figure instead of displaying it interactively. 29 """ 30 8 31 9 32 import os 33 import sys 34 import glob 10 35 import readline 11 import glob 36 import argparse 37 import numpy as np 38 import matplotlib.pyplot as plt 12 39 from netCDF4 import Dataset 13 import matplotlib.pyplot as plt 14 import numpy as np 15 16 ############################################################## 17 ### Setup readline for file name autocompletion 18 def complete(text,state): 19 line = readline.get_line_buffer().split() 20 # Use glob to find all matching files/directories for the current text 21 if '*' not in text: 22 text += '*' 23 matches = glob.glob(os.path.expanduser(text)) 24 # Add '/' if the match is a directory 25 matches = [match + '/' if os.path.isdir(match) else match for match in matches] 40 41 42 def complete_filename(text, state): 43 """ 44 Readline tab-completion function for filesystem paths. 45 """ 46 if "*" not in text: 47 pattern = text + "*" 48 else: 49 pattern = text 50 matches = glob.glob(os.path.expanduser(pattern)) 51 matches = [m + "/" if os.path.isdir(m) else m for m in matches] 26 52 try: 27 53 return matches[state] … … 29 55 return None 30 56 31 ### Function to handle autocomplete for variable names 32 def complete_variable_names(variable_names): 57 58 def make_varname_completer(varnames): 59 """ 60 Returns a readline completer function for the given list of variable names. 61 """ 33 62 def completer(text, state): 34 options = [name for name in var iable_names if name.startswith(text)]35 if state < len(options):63 options = [name for name in varnames if name.startswith(text)] 64 try: 36 65 return options[state] 37 e lse:66 except IndexError: 38 67 return None 39 68 return completer 40 69 41 ### Function to visualize a variable from a NetCDF file 42 def visualize_variable(): 43 # Ask for the NetCDF file name 44 readline.set_completer(complete) 45 readline.parse_and_bind('tab: complete') 46 file = input("Enter the name of the NetCDF file: ") 47 48 # Open the NetCDF file 70 71 # Helper functions to detect common dimension names 72 TIME_DIMS = ("Time", "time", "time_counter") 73 ALT_DIMS = ("altitude",) 74 LAT_DIMS = ("latitude", "lat") 75 LON_DIMS = ("longitude", "lon") 76 77 78 def find_dim_index(dims, candidates): 79 """ 80 Search through dims tuple for any name in candidates. 81 Returns the index if found, else returns None. 82 """ 83 for idx, dim in enumerate(dims): 84 for cand in candidates: 85 if cand.lower() == dim.lower(): 86 return idx 87 return None 88 89 90 def find_coord_var(dataset, candidates): 91 """ 92 Among dataset variables, return the first variable whose name matches any candidate. 93 Returns None if none found. 94 """ 95 for name in dataset.variables: 96 for cand in candidates: 97 if cand.lower() == name.lower(): 98 return name 99 return None 100 101 102 # Core plotting helper 103 104 def plot_variable(dataset, varname, time_index=None, alt_index=None, colormap="jet", output_path=None): 105 """ 106 Extracts the requested slice from the variable and plots it on a lat/lon grid. 107 108 Parameters 109 ---------- 110 dataset : netCDF4.Dataset object (already open) 111 varname : string name of the variable to plot 112 time_index : int or None (if variable has a time dimension) 113 alt_index : int or None (if variable has an altitude dimension) 114 colormap : string colormap name (passed to plt.contourf) 115 output_path: string filepath to save figure, or None to display interactively 116 """ 117 var = dataset.variables[varname] 118 dims = var.dimensions 119 49 120 try: 50 dataset = Dataset(file,mode='r') 51 except FileNotFoundError: 52 print(f"File '{file}' not found.") 53 return 54 55 # Display available variables 56 variable_names = list(dataset.variables.keys()) 57 print("Available variables:\n",variable_names) 58 59 # Ask for the variable to display 60 readline.set_completer(complete_variable_names(variable_names)) 61 variable_name = input("\nEnter the name of the variable you want to visualize: ") 62 63 # Check if the variable exists 64 if variable_name not in dataset.variables: 65 print(f"Variable '{variable_name}' not found in the dataset.") 66 dataset.close() 67 return 68 69 # Extract the selected variable 70 variable = dataset.variables[variable_name][:] 71 72 # Extract latitude, longitude and altitude 73 latitude = dataset.variables['latitude'][:] 74 longitude = dataset.variables['longitude'][:] 75 76 # Check if the variable has altitude and time dimensions 77 dimensions = dataset.variables[variable_name].dimensions 78 print(f"\nDimensions of '{variable_name}': {dimensions}") 79 80 # If the variable has a time dimension, ask for the time index 81 if 'Time' in dimensions: 82 if variable.shape[0] == 1: 83 time_index = 0 121 data_full = var[:] 122 except Exception as e: 123 print(f"Error: Cannot read data for variable '{varname}': {e}") 124 return 125 126 # Convert masked array to NaN 127 if hasattr(data_full, "mask"): 128 data_full = np.where(data_full.mask, np.nan, data_full.data) 129 130 # Identify dimension indices 131 t_idx = find_dim_index(dims, TIME_DIMS) 132 a_idx = find_dim_index(dims, ALT_DIMS) 133 lat_idx = find_dim_index(dims, LAT_DIMS) 134 lon_idx = find_dim_index(dims, LON_DIMS) 135 136 # Check that lat and lon dims exist 137 if lat_idx is None or lon_idx is None: 138 print("Error: Could not find 'latitude'/'lat' and 'longitude'/'lon' dimensions for plotting.") 139 return 140 141 # Build a slice with defaults 142 slicer = [slice(None)] * len(dims) 143 if t_idx is not None: 144 if time_index is None: 145 print("Error: Variable has a time dimension; please supply a time index.") 146 return 147 slicer[t_idx] = time_index 148 if a_idx is not None: 149 if alt_index is None: 150 print("Error: Variable has an altitude dimension; please supply an altitude index.") 151 return 152 slicer[a_idx] = alt_index 153 154 # Extract the 2D slice 155 try: 156 data_slice = data_full[tuple(slicer)] 157 except Exception as e: 158 print(f"Error: Could not slice variable '{varname}': {e}") 159 return 160 161 # After slicing, data_slice should be 2D 162 if data_slice.ndim != 2: 163 print(f"Error: After slicing, data for '{varname}' is not 2D (ndim={data_slice.ndim}).") 164 return 165 166 nlat, nlon = data_slice.shape 167 # Handle too-small grid (1x1, 1xN, Nx1) 168 if nlat < 2 or nlon < 2: 169 lat_varname = find_coord_var(dataset, LAT_DIMS) 170 lon_varname = find_coord_var(dataset, LON_DIMS) 171 if lat_varname and lon_varname: 172 lat_vals = dataset.variables[lat_varname][:] 173 lon_vals = dataset.variables[lon_varname][:] 174 if hasattr(lat_vals, "mask"): 175 lat_vals = np.where(lat_vals.mask, np.nan, lat_vals.data) 176 if hasattr(lon_vals, "mask"): 177 lon_vals = np.where(lon_vals.mask, np.nan, lon_vals.data) 178 # Single point 179 if nlat == 1 and nlon == 1: 180 print(f"Single data point: value={data_slice[0,0]} at (lon={lon_vals[0]}, lat={lat_vals[0]})") 181 return 182 # 1 x N -> plot vs lon 183 if nlat == 1 and nlon > 1: 184 x = lon_vals 185 y = data_slice[0, :] 186 plt.figure() 187 plt.plot(x, y, marker='o') 188 plt.xlabel(f"Longitude ({getattr(dataset.variables[lon_varname], 'units', 'degrees')})") 189 plt.ylabel(varname) 190 plt.title(f"{varname} (lat={lat_vals[0]})") 191 # N x 1 -> plot vs lat 192 elif nlon == 1 and nlat > 1: 193 x = lat_vals 194 y = data_slice[:, 0] 195 plt.figure() 196 plt.plot(x, y, marker='o') 197 plt.xlabel(f"Latitude ({getattr(dataset.variables[lat_varname], 'units', 'degrees')})") 198 plt.ylabel(varname) 199 plt.title(f"{varname} (lon={lon_vals[0]})") 200 else: 201 print("Unexpected slice shape.") 202 return 203 if output_path: 204 plt.savefig(output_path, bbox_inches="tight") 205 print(f"Figure saved to '{output_path}'") 206 else: 207 plt.show() 208 plt.close() 209 return 210 211 # Locate coordinate variables 212 lat_varname = find_coord_var(dataset, LAT_DIMS) 213 lon_varname = find_coord_var(dataset, LON_DIMS) 214 if lat_varname is None or lon_varname is None: 215 print("Error: Could not locate latitude/longitude variables in the dataset.") 216 return 217 218 lat_var = dataset.variables[lat_varname][:] 219 lon_var = dataset.variables[lon_varname][:] 220 if hasattr(lat_var, "mask"): 221 lat_var = np.where(lat_var.mask, np.nan, lat_var.data) 222 if hasattr(lon_var, "mask"): 223 lon_var = np.where(lon_var.mask, np.nan, lon_var.data) 224 225 # Build 2D coordinate arrays 226 if lat_var.ndim == 1 and lon_var.ndim == 1: 227 lon2d, lat2d = np.meshgrid(lon_var, lat_var) 228 elif lat_var.ndim == 2 and lon_var.ndim == 2: 229 lat2d, lon2d = lat_var, lon_var 230 else: 231 print("Error: Latitude and longitude variables must both be either 1D or 2D.") 232 return 233 234 # Retrieve units if available 235 var_units = getattr(var, "units", "") 236 lat_units = getattr(dataset.variables[lat_varname], "units", "degrees") 237 lon_units = getattr(dataset.variables[lon_varname], "units", "degrees") 238 239 # Plot 240 plt.figure(figsize=(10, 6)) 241 cf = plt.contourf(lon2d, lat2d, data_slice, cmap=colormap) 242 cbar = plt.colorbar(cf) 243 if var_units: 244 cbar.set_label(f"{varname} ({var_units})") 245 else: 246 cbar.set_label(varname) 247 248 plt.xlabel(f"Longitude ({lon_units})") 249 plt.ylabel(f"Latitude ({lat_units})") 250 plt.title(f"{varname} Visualization") 251 252 if output_path: 253 try: 254 plt.savefig(output_path, bbox_inches="tight") 255 print(f"Figure saved to '{output_path}'") 256 except Exception as e: 257 print(f"Error saving figure: {e}") 258 else: 259 plt.show() 260 plt.close() 261 262 263 def visualize_variable_interactive(nc_path=None): 264 """ 265 Interactive mode: if nc_path is provided, skip prompting for filename. 266 Otherwise, prompt for filename. Then select variable (automatically if only one), prompt for indices. 267 """ 268 # Determine file path 269 if nc_path: 270 file_input = nc_path 271 else: 272 readline.set_completer(complete_filename) 273 readline.parse_and_bind("tab: complete") 274 file_input = input("Enter the path to the NetCDF file: ").strip() 275 276 if not file_input: 277 print("No file specified. Exiting.") 278 return 279 if not os.path.isfile(file_input): 280 print(f"Error: '{file_input}' not found.") 281 return 282 283 try: 284 ds = Dataset(file_input, mode="r") 285 except Exception as e: 286 print(f"Error: Unable to open '{file_input}': {e}") 287 return 288 289 varnames = list(ds.variables.keys()) 290 if not varnames: 291 print("Error: No variables found in the dataset.") 292 ds.close() 293 return 294 295 # Auto-select if only one variable 296 if len(varnames) == 1: 297 var_input = varnames[0] 298 print(f"Automatically selected the only variable: '{var_input}'") 299 else: 300 print("\nAvailable variables:") 301 for name in varnames: 302 print(f" - {name}") 303 print() 304 readline.set_completer(make_varname_completer(varnames)) 305 var_input = input("Enter the name of the variable to visualize: ").strip() 306 if var_input not in ds.variables: 307 print(f"Error: Variable '{var_input}' not found. Exiting.") 308 ds.close() 309 return 310 311 dims = ds.variables[var_input].dimensions 312 time_idx = None 313 alt_idx = None 314 315 t_idx = find_dim_index(dims, TIME_DIMS) 316 if t_idx is not None: 317 length = ds.variables[var_input].shape[t_idx] 318 if length > 1: 319 while True: 320 try: 321 user_t = input(f"Enter time index [0..{length - 1}]: ").strip() 322 if user_t == "": 323 print("No time index entered. Exiting.") 324 ds.close() 325 return 326 time_idx = int(user_t) 327 if 0 <= time_idx < length: 328 break 329 except ValueError: 330 pass 331 print(f"Invalid index. Enter an integer between 0 and {length - 1}.") 84 332 else: 85 time_index = int(input(f"Enter the time index (0 to {variable.shape[0] - 1}): ")) 86 else: 87 time_index = None 88 89 # If the variable has an altitude dimension, ask for the altitude index 90 if 'altitude' in dimensions: 91 altitude = dataset.variables['altitude'][:] 92 altitude_index = int(input(f"Enter the altitude index (0 to {altitude.shape[0] - 1}): ")) 93 else: 94 altitude_index = None 95 96 # Prepare the 2D slice for plotting 97 if time_index is not None and altitude_index is not None: 98 data_slice = variable[time_index,altitude_index,:,:] 99 elif time_index is not None: 100 data_slice = variable[time_index,:,:] 101 elif altitude_index is not None: 102 data_slice = variable[altitude_index,:,:] 103 else: 104 data_slice = variable[:,:] 105 106 # Plot the selected variable 107 plt.figure(figsize = (10,6)) 108 plt.contourf(longitude,latitude,data_slice,cmap = 'jet') 109 plt.colorbar(label=f"{variable_name.capitalize()} (units)") # Adjust units based on your data 110 plt.xlabel('Longitude (degrees)') 111 plt.ylabel('Latitude (degrees)') 112 plt.title(f"{variable_name.capitalize()} visualization") 113 114 # Show the plot 115 plt.show() 116 117 # Close the NetCDF file 118 dataset.close() 119 120 ### Call the main function 121 visualize_variable() 333 time_idx = 0 334 print("Only one time step available; using index 0.") 335 336 a_idx = find_dim_index(dims, ALT_DIMS) 337 if a_idx is not None: 338 length = ds.variables[var_input].shape[a_idx] 339 if length > 1: 340 while True: 341 try: 342 user_a = input(f"Enter altitude index [0..{length - 1}]: ").strip() 343 if user_a == "": 344 print("No altitude index entered. Exiting.") 345 ds.close() 346 return 347 alt_idx = int(user_a) 348 if 0 <= alt_idx < length: 349 break 350 except ValueError: 351 pass 352 print(f"Invalid index. Enter an integer between 0 and {length - 1}.") 353 else: 354 alt_idx = 0 355 print("Only one altitude level available; using index 0.") 356 357 plot_variable( 358 dataset=ds, 359 varname=var_input, 360 time_index=time_idx, 361 alt_index=alt_idx, 362 colormap="jet", 363 output_path=None 364 ) 365 ds.close() 366 367 368 def visualize_variable_cli(nc_path, varname, time_index, alt_index, colormap, output_path): 369 """ 370 Command-line mode: directly visualize based on provided arguments. 371 """ 372 if not os.path.isfile(nc_path): 373 print(f"Error: '{nc_path}' not found.") 374 return 375 try: 376 ds = Dataset(nc_path, mode="r") 377 except Exception as e: 378 print(f"Error: Unable to open '{nc_path}': {e}") 379 return 380 381 if varname not in ds.variables: 382 print(f"Error: Variable '{varname}' not found in '{nc_path}'.") 383 ds.close() 384 return 385 386 plot_variable( 387 dataset=ds, 388 varname=varname, 389 time_index=time_index, 390 alt_index=alt_index, 391 colormap=colormap, 392 output_path=output_path 393 ) 394 ds.close() 395 396 397 def main(): 398 parser = argparse.ArgumentParser( 399 description="Visualize a 2D slice of a NetCDF variable on a latitude-longitude map." 400 ) 401 parser.add_argument( 402 "nc_file", 403 nargs="?", 404 help="Path to the NetCDF file (interactive if omitted)." 405 ) 406 parser.add_argument( 407 "--variable", "-v", 408 help="Name of the variable to visualize." 409 ) 410 parser.add_argument( 411 "--time-index", "-t", 412 type=int, 413 help="Index along the time dimension (if applicable)." 414 ) 415 parser.add_argument( 416 "--alt-index", "-a", 417 type=int, 418 help="Index along the altitude dimension (if applicable)." 419 ) 420 parser.add_argument( 421 "--cmap", "-c", 422 default="jet", 423 help="Matplotlib colormap (default: 'jet')." 424 ) 425 parser.add_argument( 426 "--output", "-o", 427 help="If provided, save the plot to this file instead of displaying it." 428 ) 429 430 args = parser.parse_args() 431 432 # If nc_file is provided but variable is missing: ask only for variable 433 if args.nc_file and not args.variable: 434 visualize_variable_interactive(nc_path=args.nc_file) 435 # If either nc_file or variable is missing, run fully interactive 436 elif not args.nc_file or not args.variable: 437 visualize_variable_interactive() 438 else: 439 visualize_variable_cli( 440 nc_path=args.nc_file, 441 varname=args.variable, 442 time_index=args.time_index, 443 alt_index=args.alt_index, 444 colormap=args.cmap, 445 output_path=args.output 446 ) 447 448 449 if __name__ == "__main__": 450 main() 451
Note: See TracChangeset
for help on using the changeset viewer.