Changeset 3839 for trunk/LMDZ.MARS/util
- Timestamp:
- Jul 7, 2025, 6:00:37 PM (4 days ago)
- Location:
- trunk/LMDZ.MARS/util
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LMDZ.MARS/util/display_netcdf.py
r3824 r3839 85 85 # Attempt to load MOLA topography 86 86 try: 87 MOLA = np.load('MOLA_1px_per_deg.npy') 87 MOLA = np.load('MOLA_1px_per_deg.npy') # shape (nlat, nlon) at 1° per pixel: lat from -90 to 90, lon from 0 to 360 88 88 nlat, nlon = MOLA.shape 89 89 topo_lats = np.linspace(90 - 0.5, -90 + 0.5, nlat) … … 171 171 transform=transform 172 172 ) 173 174 175 def attach_format_coord(ax, mat, x, y, is_pcolormesh=True): 176 """ 177 Attach a format_coord function to the axes to display x, y, and value at cursor. 178 Works for both pcolormesh and imshow style grids. 179 """ 180 # Determine dimensions 181 if mat.ndim == 2: 182 ny, nx = mat.shape 183 elif mat.ndim == 3 and mat.shape[2] in (3, 4): 184 ny, nx, nc = mat.shape 185 else: 186 raise ValueError(f"Unsupported mat shape {mat.shape}") 187 # Edges or extents 188 if is_pcolormesh: 189 xedges, yedges = x, y 190 else: 191 x0, x1 = x.min(), x.max() 192 y0, y1 = y.min(), y.max() 193 194 def format_coord(xp, yp): 195 # Map to indices 196 if is_pcolormesh: 197 col = np.searchsorted(xedges, xp) - 1 198 row = np.searchsorted(yedges, yp) - 1 199 else: 200 col = int((xp - x0) / (x1 - x0) * nx) 201 row = int((yp - y0) / (y1 - y0) * ny) 202 # Within bounds? 203 if 0 <= row < ny and 0 <= col < nx: 204 if mat.ndim == 2: 205 v = mat[row, col] 206 return f"x={xp:.3g}, y={yp:.3g}, val={v:.3g}" 207 else: 208 vals = mat[row, col] 209 txt = ", ".join(f"{vv:.3g}" for vv in vals[:3]) 210 return f"x={xp:.3g}, y={yp:.3g}, val=({txt})" 211 return f"x={xp:.3g}, y={yp:.3g}" 212 213 ax.format_coord = format_coord 173 214 174 215 … … 396 437 data_full = np.where(data_full.mask, np.nan, data_full.data) 397 438 439 # If Time and altitude are both present and neither indexed, 440 # and every other dim has size 1: 441 t_idx = find_dim_index(dims, TIME_DIMS) 442 a_idx = find_dim_index(dims, ALT_DIMS) 443 shape = var.shape 444 if (t_idx is not None and a_idx is not None 445 and time_index is None and alt_index is None 446 and all(size == 1 for i, size in enumerate(shape) if i not in (t_idx, a_idx))): 447 448 # Build a slicer that keeps Time & altitude, drops other singletons 449 slicer = [0] * len(dims) 450 slicer[t_idx] = slice(None) 451 slicer[a_idx] = slice(None) 452 data2d = data_full[tuple(slicer)] # shape (ntime, nalt) 453 454 # Coordinate arrays 455 tvar = find_coord_var(dataset, TIME_DIMS) 456 avar = find_coord_var(dataset, ALT_DIMS) 457 tvals = dataset.variables[tvar][:] 458 avals = dataset.variables[avar][:] 459 460 # Unmask if necessary 461 if hasattr(tvals, "mask"): 462 tvals = np.where(tvals.mask, np.nan, tvals.data) 463 if hasattr(avals, "mask"): 464 avals = np.where(avals.mask, np.nan, avals.data) 465 466 # Plot heatmap with x=time, y=altitude 467 fig, ax = plt.subplots(figsize=(10, 6)) 468 T, A = np.meshgrid(tvals, avals) 469 im = ax.pcolormesh( 470 T, A, data2d.T, 471 shading="auto", cmap=colormap 472 ) 473 dt = tvals[1] - tvals[0] 474 da = avals[1] - avals[0] 475 x_edges = np.concatenate([tvals - dt/2, [tvals[-1] + dt/2]]) 476 y_edges = np.concatenate([avals - da/2, [avals[-1] + da/2]]) 477 attach_format_coord(ax, data2d.T, x_edges, y_edges, is_pcolormesh=True) 478 ax.set_xlabel(tvar) 479 ax.set_ylabel(avar) 480 cbar = fig.colorbar(im, ax=ax) 481 cbar.set_label(varname + (f" ({getattr(var, 'units','')})")) 482 ax.set_title(f"{varname} — {avar} vs {tvar}", fontweight="bold") 483 484 if output_path: 485 fig.savefig(output_path, bbox_inches="tight") 486 print(f"Saved to {output_path}") 487 else: 488 plt.show() 489 return 490 398 491 # Pure 1D time series 399 492 if len(dims) == 1 and find_dim_index(dims, TIME_DIMS) is not None: … … 434 527 if hasattr(lons, "mask"): 435 528 lons = np.where(lons.mask, np.nan, lons.data) 436 plt.figure(figsize=(10, 6)) 437 plt.pcolormesh(lons, tvals, data_avg, shading="auto", cmap=colormap) 438 plt.xlabel(f"Longitude ({getattr(dataset.variables[lon_var], 'units', 'deg')})") 439 plt.ylabel(time_var) 440 cbar = plt.colorbar() 529 fig, ax = ax.subplots(figsize=(10, 6)) 530 im = plt.pcolormesh(lons, tvals, data_avg, shading="auto", cmap=colormap) 531 dx = lons[1] - lons[0] 532 dy = tvals[1] - tvals[0] 533 x_edges = np.concatenate([lons - dx/2, [lons[-1] + dx/2]]) 534 y_edges = np.concatenate([tvals - dy/2, [tvals[-1] + dy/2]]) 535 attach_format_coord(ax, data_avg.T, x_edges, y_edges, is_pcolormesh=True) 536 ax.set_xlabel(f"Longitude ({getattr(dataset.variables[lon_var], 'units', 'deg')})") 537 ax.set_ylabel(time_var) 538 cbar = fig.colorbar(im, ax=ax) 441 539 cbar.set_label(varname + (f" ({var.units})" if hasattr(var, "units") else "")) 442 plt.title(f"{varname} averaged over latitude", fontweight='bold')540 ax.set_title(f"{varname} averaged over latitude", fontweight='bold') 443 541 if output_path: 444 plt.savefig(output_path, bbox_inches="tight")542 fig.savefig(output_path, bbox_inches="tight") 445 543 print(f"Saved to {output_path}") 446 544 else: … … 700 798 701 799 # Generic 2D 702 plt.figure(figsize=(8, 6)) 703 plt.imshow(dslice, aspect="auto") 704 plt.colorbar(label=varname + (f" ({var.units})" if hasattr(var, "units") else "")) 705 plt.xlabel("Dim 2 index") 706 plt.ylabel("Dim 1 index") 707 plt.title(f"{varname} (2D)", fontweight='bold') 800 fig, ax = plt.subplots(figsize=(8, 6)) 801 im = ax.imshow( 802 dslice, 803 aspect="auto", 804 interpolation='nearest' 805 ) 806 x0, x1 = 0, dslice.shape[1] - 1 807 y0, y1 = 0, dslice.shape[0] - 1 808 x_centers = np.linspace(x0, x1, dslice.shape[1]) 809 y_centers = np.linspace(y0, y1, dslice.shape[0]) 810 attach_format_coord(ax, dslice, x_centers, y_centers, is_pcolormesh=False) 811 cbar = fig.colorbar(im, ax=ax, orientation='vertical') 812 cbar.set_label(varname + (f" ({var.units})" if hasattr(var, "units") else "")) 813 ax.set_xlabel("Dim 2 index") 814 ax.set_ylabel("Dim 1 index") 815 ax.set_title(f"{varname} (2D)") 816 708 817 if output_path: 709 plt.savefig(output_path, bbox_inches="tight")818 fig.savefig(output_path, bbox_inches="tight") 710 819 print(f"Saved to {output_path}") 711 820 else: -
trunk/LMDZ.MARS/util/display_netcdf.yml
r3824 r3839 8 8 - aiohappyeyeballs=2.6.1=pyhd8ed1ab_0 9 9 - aiohttp=3.12.13=py312h178313f_0 10 - aiosignal=1. 3.2=pyhd8ed1ab_010 - aiosignal=1.4.0=pyhd8ed1ab_0 11 11 - alsa-lib=1.2.14=hb9d3cd8_0 12 12 - aom=3.9.1=hac33072_0 … … 38 38 - fonts-conda-ecosystem=1=0 39 39 - fonts-conda-forge=1=0 40 - fonttools=4.58. 4=py312h178313f_040 - fonttools=4.58.5=py312h178313f_0 41 41 - freetype=2.13.3=ha770c72_1 42 42 - fribidi=1.0.10=h36c2ea0_0 … … 44 44 - gdk-pixbuf=2.42.12=hb9ae30d_0 45 45 - geos=3.13.1=h97f6797_0 46 - gettext=0.2 4.1=h5888daf_047 - gettext-tools=0.2 4.1=h5888daf_046 - gettext=0.25.1=h5888daf_0 47 - gettext-tools=0.25.1=h5888daf_0 48 48 - gl2ps=1.4.2=hae5d5c5_1 49 49 - gmp=6.3.0=hac33072_2 … … 56 56 - jsoncpp=1.9.6=hf42df4d_1 57 57 - keyutils=1.6.1=h166bdaf_0 58 - kiwisolver=1.4.8=py312h 84d6215_058 - kiwisolver=1.4.8=py312h68727a3_1 59 59 - krb5=1.21.3=h659f571_0 60 60 - lame=3.100=h166bdaf_1003 61 61 - lcms2=2.17=h717163a_0 62 - ld_impl_linux-64=2.4 3=h1423503_562 - ld_impl_linux-64=2.44=h1423503_0 63 63 - lerc=4.0.0=h0aef613_1 64 64 - level-zero=1.23.0=h84d6215_0 65 65 - libabseil=20250127.1=cxx17_hbbce691_0 66 66 - libaec=1.1.4=h3f801dc_0 67 - libasprintf=0.2 4.1=h8e693c7_068 - libasprintf-devel=0.2 4.1=h8e693c7_067 - libasprintf=0.25.1=h8e693c7_0 68 - libasprintf-devel=0.25.1=h8e693c7_0 69 69 - libass=0.17.3=h52826cd_2 70 70 - libblas=3.9.0=32_h59b9bed_openblas … … 91 91 - libgcc-ng=15.1.0=h69a702a_3 92 92 - libgcrypt-lib=1.11.1=hb9d3cd8_0 93 - libgettextpo=0.2 4.1=h5888daf_094 - libgettextpo-devel=0.2 4.1=h5888daf_093 - libgettextpo=0.25.1=h5888daf_0 94 - libgettextpo-devel=0.25.1=h5888daf_0 95 95 - libgfortran=15.1.0=h69a702a_3 96 96 - libgfortran5=15.1.0=hcea5267_3 … … 129 129 - libopus=1.5.2=hd0c01bc_0 130 130 - libpciaccess=0.18=hb9d3cd8_0 131 - libpng=1.6. 49=h943b412_0131 - libpng=1.6.50=h943b412_0 132 132 - libpq=17.5=h27ae623_0 133 133 - libprotobuf=5.29.3=h501fc15_1 … … 179 179 - pango=1.56.4=hadf4263_0 180 180 - pcre2=10.45=hc749103_0 181 - pillow=11. 2.1=py312h80c1187_0181 - pillow=11.3.0=py312h80c1187_0 182 182 - pip=25.1.1=pyh8b19718_0 183 183 - pixman=0.46.2=h29eaf8c_0 … … 211 211 - tk=8.6.13=noxft_hd72426e_102 212 212 - tornado=6.5.1=py312h66e93f0_0 213 - typing_extensions=4.14. 0=pyhe01879c_0213 - typing_extensions=4.14.1=pyhe01879c_0 214 214 - tzdata=2025b=h78e105d_0 215 215 - unicodedata2=16.0.0=py312h66e93f0_0 … … 219 219 - vtk-base=9.4.2=py312h28cca35_1 220 220 - vtk-io-ffmpeg=9.4.2=he57672f_1 221 - wayland=1.2 3.1=h3e06ad9_1221 - wayland=1.24.0=h3e06ad9_0 222 222 - wayland-protocols=1.45=hd8ed1ab_0 223 223 - wheel=0.45.1=pyhd8ed1ab_1
Note: See TracChangeset
for help on using the changeset viewer.