PyMVP 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.
- {pymvp-0.2.3 → pymvp-0.2.5}/PKG-INFO +1 -1
- {pymvp-0.2.3 → pymvp-0.2.5}/PyMVP/main.py +289 -282
- {pymvp-0.2.3 → pymvp-0.2.5}/PyMVP/mvp_routines.py +52 -0
- {pymvp-0.2.3 → pymvp-0.2.5}/PyMVP.egg-info/PKG-INFO +1 -1
- {pymvp-0.2.3 → pymvp-0.2.5}/pyproject.toml +1 -1
- {pymvp-0.2.3 → pymvp-0.2.5}/PyMVP/__init__.py +0 -0
- {pymvp-0.2.3 → pymvp-0.2.5}/PyMVP.egg-info/SOURCES.txt +0 -0
- {pymvp-0.2.3 → pymvp-0.2.5}/PyMVP.egg-info/dependency_links.txt +0 -0
- {pymvp-0.2.3 → pymvp-0.2.5}/PyMVP.egg-info/requires.txt +0 -0
- {pymvp-0.2.3 → pymvp-0.2.5}/PyMVP.egg-info/top_level.txt +0 -0
- {pymvp-0.2.3 → pymvp-0.2.5}/setup.cfg +0 -0
|
@@ -114,8 +114,8 @@ class Analyzer:
|
|
|
114
114
|
self.PH_mvp = nc['PH'].values
|
|
115
115
|
self.SALT_mvp = nc['SAL'].values
|
|
116
116
|
self.TIME_mvp = nc['TIME_s'].values
|
|
117
|
-
self.
|
|
118
|
-
self.
|
|
117
|
+
self.Lat_mvp = nc['LATITUDE'].values
|
|
118
|
+
self.Lon_mvp = nc['LONGITUDE'].values
|
|
119
119
|
self.DATETIME_mvp = nc['profile_time'].values
|
|
120
120
|
self.DIR = nc['direction'].values
|
|
121
121
|
self.label_mvp = nc['profile'].values
|
|
@@ -293,8 +293,8 @@ class Analyzer:
|
|
|
293
293
|
self.PH_mvp = PH_mvp
|
|
294
294
|
self.SALT_mvp = SALT_mvp
|
|
295
295
|
self.TIME_mvp = TIME_mvp
|
|
296
|
-
self.
|
|
297
|
-
self.
|
|
296
|
+
self.Lat_mvp = LAT_mvp
|
|
297
|
+
self.Lon_mvp = LON_mvp
|
|
298
298
|
self.DATETIME_mvp = DATETIME_mvp
|
|
299
299
|
self.DIR = DIR
|
|
300
300
|
self.label_mvp = Label_mvp
|
|
@@ -341,8 +341,8 @@ class Analyzer:
|
|
|
341
341
|
self.PH_mvp = nc['PH'].values
|
|
342
342
|
self.SALT_mvp = nc['SAL'].values
|
|
343
343
|
self.TIME_mvp = nc['TIME'].values
|
|
344
|
-
self.
|
|
345
|
-
self.
|
|
344
|
+
self.Lat_mvp = nc['LATITUDE'].values
|
|
345
|
+
self.Lon_mvp = nc['LONGITUDE'].values
|
|
346
346
|
self.DATETIME_mvp = nc['profile_time'].values
|
|
347
347
|
self.DIR = nc['direction'].values
|
|
348
348
|
self.Label_mvp = nc['profile'].values
|
|
@@ -481,8 +481,8 @@ class Analyzer:
|
|
|
481
481
|
self.PH_mvp = np.hstack((self.PH_mvp, nan_cols))
|
|
482
482
|
self.SALT_mvp = np.hstack((self.SALT_mvp, nan_cols))
|
|
483
483
|
self.TIME_mvp = np.hstack((self.TIME_mvp, nan_cols))
|
|
484
|
-
self.
|
|
485
|
-
self.
|
|
484
|
+
self.Lat_mvp = np.hstack((self.Lat_mvp, nan_cols))
|
|
485
|
+
self.Lon_mvp = np.hstack((self.Lon_mvp, nan_cols))
|
|
486
486
|
|
|
487
487
|
|
|
488
488
|
|
|
@@ -547,8 +547,8 @@ class Analyzer:
|
|
|
547
547
|
self.PH_mvp = np.concatenate((self.PH_mvp, PH_mvp), axis=0)
|
|
548
548
|
self.SALT_mvp = np.concatenate((self.SALT_mvp, SALT_mvp), axis=0)
|
|
549
549
|
self.TIME_mvp = np.concatenate((self.TIME_mvp, TIME_mvp), axis=0)
|
|
550
|
-
self.
|
|
551
|
-
self.
|
|
550
|
+
self.Lat_mvp = np.concatenate((self.Lat_mvp, LAT_mvp), axis=0)
|
|
551
|
+
self.Lon_mvp = np.concatenate((self.Lon_mvp, LON_mvp), axis=0)
|
|
552
552
|
|
|
553
553
|
self.DATETIME_mvp.extend(DATETIME_mvp)
|
|
554
554
|
self.DIR.extend(DIR)
|
|
@@ -585,7 +585,7 @@ class Analyzer:
|
|
|
585
585
|
TEMP_ctd_temp = []
|
|
586
586
|
COND_ctd_temp = []
|
|
587
587
|
TURB_ctd_temp = []
|
|
588
|
-
|
|
588
|
+
DO_ctd_temp = []
|
|
589
589
|
FLUO_ctd_temp = []
|
|
590
590
|
CDOM_ctd_temp = []
|
|
591
591
|
DATETIME_ctd = []
|
|
@@ -603,8 +603,8 @@ class Analyzer:
|
|
|
603
603
|
SALT_ctd_temp.append(nc['SAL'].values[1])
|
|
604
604
|
TURB_ctd_temp.append(nc['TURB'].values[0])
|
|
605
605
|
TURB_ctd_temp.append(nc['TURB'].values[1])
|
|
606
|
-
|
|
607
|
-
|
|
606
|
+
DO_ctd_temp.append(nc['OXY'].values[0])
|
|
607
|
+
DO_ctd_temp.append(nc['OXY'].values[1])
|
|
608
608
|
FLUO_ctd_temp.append(nc['FLUO'].values[0])
|
|
609
609
|
FLUO_ctd_temp.append(nc['FLUO'].values[1])
|
|
610
610
|
CDOM_ctd_temp.append(nc['CDOM'].values[0])
|
|
@@ -622,12 +622,13 @@ class Analyzer:
|
|
|
622
622
|
self.COND_ctd = np.array(COND_ctd_temp)
|
|
623
623
|
self.SALT_ctd = np.array(SALT_ctd_temp)
|
|
624
624
|
self.TURB_ctd = np.array(TURB_ctd_temp)
|
|
625
|
-
self.
|
|
625
|
+
self.DO_ctd = np.array(DO_ctd_temp)
|
|
626
626
|
self.FLUO_ctd = np.array(FLUO_ctd_temp)
|
|
627
627
|
self.CDOM_ctd = np.array(CDOM_ctd_temp)
|
|
628
628
|
self.LAT_ctd = np.array(LAT_ctd_temp)
|
|
629
629
|
self.LON_ctd = np.array(LON_ctd_temp)
|
|
630
630
|
self.DATETIME_ctd = np.array(DATETIME_ctd)
|
|
631
|
+
self.TIME_ctd = np.array([(np.datetime64(dt) - np.datetime64(self.date_ref)) / np.timedelta64(1, 'D') for dt in self.DATETIME_ctd])
|
|
631
632
|
|
|
632
633
|
|
|
633
634
|
print('CTD data loaded successfully.')
|
|
@@ -687,7 +688,7 @@ class Analyzer:
|
|
|
687
688
|
print('MVP data:')
|
|
688
689
|
print('Number of profiles: ' + str(len(self.DATETIME_mvp)))
|
|
689
690
|
for i in range(0,len(self.DATETIME_mvp)):
|
|
690
|
-
print(f" Profil down {2*i} - Profil up {2*i+1} - Latitude: {self.
|
|
691
|
+
print(f" Profil down {2*i} - Profil up {2*i+1} - Latitude: {self.Lat_mvp[2*i,0]:.5f}, Longitude: {self.Lon_mvp[2*i,0]:.5f}, Date/Heure: {self.DATETIME_mvp[i]}")
|
|
691
692
|
|
|
692
693
|
if self.ctd:
|
|
693
694
|
print('CTD data:')
|
|
@@ -731,8 +732,8 @@ class Analyzer:
|
|
|
731
732
|
self.PH_mvp = self.PH_mvp[l_id,:]
|
|
732
733
|
self.SALT_mvp = self.SALT_mvp[l_id,:]
|
|
733
734
|
self.TIME_mvp = self.TIME_mvp[l_id,:]
|
|
734
|
-
self.
|
|
735
|
-
self.
|
|
735
|
+
self.Lat_mvp = self.Lat_mvp[l_id,:]
|
|
736
|
+
self.Lon_mvp = self.Lon_mvp[l_id,:]
|
|
736
737
|
self.DATETIME_mvp = np.array(self.DATETIME_mvp)[l_id2]
|
|
737
738
|
self.DIR = np.array(self.DIR)[l_id]
|
|
738
739
|
self.label_mvp = np.array(self.label_mvp)[l_id]
|
|
@@ -751,7 +752,7 @@ class Analyzer:
|
|
|
751
752
|
self.SALT_ctd = self.SALT_ctd[l_id,:]
|
|
752
753
|
self.COND_ctd = self.COND_ctd[l_id,:]
|
|
753
754
|
self.TURB_ctd = self.TURB_ctd[l_id,:]
|
|
754
|
-
self.
|
|
755
|
+
self.DO_ctd = self.DO_ctd[l_id,:]
|
|
755
756
|
self.FLUO_ctd = self.FLUO_ctd[l_id,:]
|
|
756
757
|
self.CDOM_ctd = self.CDOM_ctd[l_id,:]
|
|
757
758
|
self.LAT_ctd = self.LAT_ctd[l_id,:]
|
|
@@ -834,7 +835,7 @@ class Analyzer:
|
|
|
834
835
|
|
|
835
836
|
put_label = True
|
|
836
837
|
c = 0
|
|
837
|
-
for i in range(0,self.
|
|
838
|
+
for i in range(0,self.Lat_mvp.shape[0],2):
|
|
838
839
|
if i>0:
|
|
839
840
|
if self.label_mvp[i] == self.label_mvp[i-1]:
|
|
840
841
|
put_label = False
|
|
@@ -842,8 +843,8 @@ class Analyzer:
|
|
|
842
843
|
put_label = True
|
|
843
844
|
c+=1
|
|
844
845
|
|
|
845
|
-
lat = self.
|
|
846
|
-
lon = self.
|
|
846
|
+
lat = self.Lat_mvp[i,0] if self.Lat_mvp.ndim == 2 else self.Lat_mvp[i]
|
|
847
|
+
lon = self.Lon_mvp[i,0] if self.Lon_mvp.ndim == 2 else self.Lon_mvp[i]
|
|
847
848
|
ax.scatter(lon, lat, color=colors[c], marker='o', label='MVP '+self.label_mvp[i] if put_label else "", transform=ccrs.PlateCarree())
|
|
848
849
|
|
|
849
850
|
# CTD
|
|
@@ -925,8 +926,8 @@ class Analyzer:
|
|
|
925
926
|
plt.plot(self.DO_mvp[id_mvp],self.PRES_mvp[id_mvp],label='MVP down')
|
|
926
927
|
plt.plot(self.DO_mvp[id_mvp+1],self.PRES_mvp[id_mvp+1],label='MVP up')
|
|
927
928
|
if self.ctd:
|
|
928
|
-
plt.plot(self.
|
|
929
|
-
plt.plot(self.
|
|
929
|
+
plt.plot(self.DO_ctd[id_ctd],self.PRES_ctd[id_ctd],label='CTD down')
|
|
930
|
+
plt.plot(self.DO_ctd[id_ctd+1],self.PRES_ctd[id_ctd+1],label='CTD up')
|
|
930
931
|
plt.legend()
|
|
931
932
|
plt.gca().invert_yaxis()
|
|
932
933
|
plt.grid()
|
|
@@ -1073,7 +1074,7 @@ class Analyzer:
|
|
|
1073
1074
|
|
|
1074
1075
|
TEMP_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.TEMP_ctd[id_ctd1,:], pressure_grid)
|
|
1075
1076
|
SALT_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.SALT_ctd[id_ctd1,:], pressure_grid)
|
|
1076
|
-
DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.
|
|
1077
|
+
DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.DO_ctd[id_ctd1,:], pressure_grid)
|
|
1077
1078
|
COND_ctd_interp = mvp.vertical_interp(self.PRES_ctd[id_ctd1,:],self.COND_ctd[id_ctd1,:], pressure_grid)
|
|
1078
1079
|
|
|
1079
1080
|
# differences study between MVP down and CTD profiles
|
|
@@ -1256,7 +1257,7 @@ class Analyzer:
|
|
|
1256
1257
|
print(f" MVP down: {rmse_cond_down:.4f} S/m (deep: {rmse_cond_down_deep:.4f} S/m)")
|
|
1257
1258
|
print(f" MVP up: {rmse_cond_up:.4f} S/m (deep: {rmse_cond_up_deep:.4f} S/m)")
|
|
1258
1259
|
|
|
1259
|
-
def correct_oxygen(self,id_mvp=None,id_ctd=None,
|
|
1260
|
+
def correct_oxygen(self,id_mvp=None,id_ctd=None,plotting=False,):
|
|
1260
1261
|
"""
|
|
1261
1262
|
Apply oxygen correction to MVP dissolved oxygen profiles thanks to CTD data.
|
|
1262
1263
|
Args:
|
|
@@ -1278,63 +1279,64 @@ class Analyzer:
|
|
|
1278
1279
|
id_ctd = id_mvp
|
|
1279
1280
|
|
|
1280
1281
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
pressure_grid = np.linspace(pmin, pmax, num_sample)
|
|
1285
|
-
|
|
1282
|
+
if hasattr(self,'DO_mvp_corr_interp') == False:
|
|
1283
|
+
raise ValueError("Please run the interpolation method first to create the DO_mvp_corr_interp attribute.")
|
|
1284
|
+
|
|
1286
1285
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1286
|
+
oxy_mvp = self.DO_mvp_corr_interp[id_mvp]
|
|
1287
|
+
oxy_ctd = self.DO_ctd_interp[id_ctd]
|
|
1288
|
+
pres = self.PRES_mvp_corr_interp[id_mvp]
|
|
1289
1289
|
|
|
1290
|
-
mask = ~np.isnan(
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1290
|
+
mask = ~np.isnan(oxy_mvp) & ~np.isnan(oxy_ctd)
|
|
1291
|
+
oxy_mvp = oxy_mvp[mask]
|
|
1292
|
+
oxy_ctd = oxy_ctd[mask]
|
|
1293
|
+
pres = pres[mask]
|
|
1294
1294
|
|
|
1295
|
-
diff =
|
|
1295
|
+
diff = oxy_mvp - oxy_ctd
|
|
1296
1296
|
|
|
1297
|
-
A = np.vstack([
|
|
1298
|
-
print(A.shape, diff.shape)
|
|
1297
|
+
A = np.vstack([oxy_ctd, np.ones(len(oxy_ctd))]).T
|
|
1299
1298
|
diff = diff.flatten()
|
|
1300
|
-
a_estime, b_estime = np.linalg.lstsq(A, diff, rcond=None)[0]
|
|
1301
|
-
|
|
1302
|
-
print(f"Pente estimée (a) : {a_estime:.6f} ")
|
|
1303
|
-
print(f"Biais estimé (b) : {b_estime:.6f} ")
|
|
1304
|
-
|
|
1305
|
-
DO_mvp_corr = DO_mvp_interp - (a_estime*pressure_grid + b_estime)
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
1299
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
print(f"RMSE before correction: {rmse_before:.4f}")
|
|
1312
|
-
print(f"RMSE after correction: {rmse_after:.4f}")
|
|
1300
|
+
a_estim, b_estim = np.linalg.lstsq(A, diff, rcond=None)[0]
|
|
1301
|
+
print(f"Estimated linear relationship: diff = {a_estim:.4f} * oxy_ctd + {b_estim:.4f}")
|
|
1313
1302
|
|
|
1314
|
-
|
|
1303
|
+
Do_mvp_corr = self.DO_mvp_corr_interp[id_mvp] - (a_estim * self.DO_ctd_interp[id_ctd] + b_estim)
|
|
1315
1304
|
|
|
1316
|
-
DO_mvp_corr_full_interp = mvp.vertical_interp(self.PRES_mvp, DO_mvp_corr_full, pressure_grid)
|
|
1317
|
-
rmse_after_full = np.mean(np.sqrt(np.nanmean((DO_mvp_corr_full_interp - DO_ctd_interp)**2,axis=1)))
|
|
1318
|
-
print(f"RMSE after correction (full profile): {rmse_after_full:.4f}")
|
|
1319
1305
|
|
|
1320
|
-
self.
|
|
1321
|
-
|
|
1306
|
+
rmse_before = np.sqrt(np.nanmean((self.DO_mvp_corr_interp[id_mvp] - self.DO_ctd_interp[id_ctd])**2))
|
|
1307
|
+
rmse_after = np.sqrt(np.nanmean((Do_mvp_corr - self.DO_ctd_interp[id_ctd])**2))
|
|
1308
|
+
if plotting:
|
|
1309
|
+
print(f"RMSE before correction: {rmse_before:.4f}")
|
|
1310
|
+
print(f"RMSE after correction: {rmse_after:.4f}")
|
|
1322
1311
|
|
|
1323
1312
|
|
|
1324
1313
|
if plotting:
|
|
1325
1314
|
|
|
1326
|
-
plt.figure()
|
|
1327
|
-
plt.plot(
|
|
1328
|
-
plt.plot(
|
|
1329
|
-
plt.plot(
|
|
1315
|
+
plt.figure(figsize=(6,8))
|
|
1316
|
+
plt.plot(oxy_mvp, pres, label='MVP DO')
|
|
1317
|
+
plt.plot(Do_mvp_corr[mask],pres,label='MVP DO corrigé')
|
|
1318
|
+
plt.plot(oxy_ctd, pres, label='CTD DO')
|
|
1330
1319
|
plt.gca().invert_yaxis()
|
|
1331
|
-
plt.xlabel('
|
|
1332
|
-
plt.ylabel('
|
|
1333
|
-
plt.title('
|
|
1320
|
+
plt.xlabel('Oxygène dissous [µmol/kg]')
|
|
1321
|
+
plt.ylabel('Profondeur [m]')
|
|
1322
|
+
plt.title(f'Profil de DO - Profil {id_mvp} MVP vs Profil {id_ctd} CTD')
|
|
1334
1323
|
plt.legend()
|
|
1335
|
-
plt.grid()
|
|
1336
1324
|
plt.show()
|
|
1337
1325
|
|
|
1326
|
+
self.DO_mvp_corr_interp[id_mvp] = Do_mvp_corr
|
|
1327
|
+
|
|
1328
|
+
|
|
1329
|
+
def correct_oxygen_all(self,mode):
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
for id_mvp in range(0,self.PRES_mvp.shape[0]):
|
|
1334
|
+
|
|
1335
|
+
id_nearest_ctd = mvp.find_nearest_profile(self.TIME_mvp_corr_interp[id_mvp],self.Lat_mvp_corr_interp[id_mvp], self.Lon_mvp_corr_interp[id_mvp],self.TIME_ctd ,self.LAT_ctd, self.LON_ctd,mode)[0]
|
|
1336
|
+
self.correct_oxygen(id_mvp=id_mvp, id_ctd=id_nearest_ctd, plotting=False)
|
|
1337
|
+
|
|
1338
|
+
print("Oxygen correction applied to all MVP profiles using nearest CTD profiles.")
|
|
1339
|
+
|
|
1338
1340
|
|
|
1339
1341
|
def mvp_correction(self,high_cutoff=1,dp=0.1):
|
|
1340
1342
|
|
|
@@ -1437,7 +1439,7 @@ class Analyzer:
|
|
|
1437
1439
|
self.PRES_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.PRES_ctd, pressure_grid)
|
|
1438
1440
|
self.COND_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.COND_ctd, pressure_grid)
|
|
1439
1441
|
self.SALT_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.SALT_ctd, pressure_grid)
|
|
1440
|
-
self.DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.
|
|
1442
|
+
self.DO_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.DO_ctd, pressure_grid)
|
|
1441
1443
|
self.FLUO_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.FLUO_ctd, pressure_grid)
|
|
1442
1444
|
self.TURB_ctd_interp = mvp.vertical_interp(self.PRES_ctd, self.TURB_ctd, pressure_grid)
|
|
1443
1445
|
self.TEMP_mvp_corr_interp = mvp.vertical_interp(PRES_mvp_corr_mat, TEMP_mvp_corr_mat, pressure_grid)
|
|
@@ -1500,7 +1502,6 @@ class Analyzer:
|
|
|
1500
1502
|
print("Mean conductivity difference between MVP and CTD profiles:", np.mean(mean_cond_diff))
|
|
1501
1503
|
|
|
1502
1504
|
|
|
1503
|
-
|
|
1504
1505
|
def corrige_MVP_offset_on_ctd_simple(self,id_mvp,id_ctd,min_depth):
|
|
1505
1506
|
"""
|
|
1506
1507
|
This function corrects the offset between the MVP and CTD profiles by aligning the temperature, conductivity profiles. It calculates the mean difference in temperature between the two profiles and applies this correction to the CTD temperature data.
|
|
@@ -1550,235 +1551,241 @@ class Analyzer:
|
|
|
1550
1551
|
print("Mean conductivity difference between MVP and CTD profiles:", np.mean(mean_cond_diff))
|
|
1551
1552
|
|
|
1552
1553
|
|
|
1554
|
+
def corrige_MVP_offset_on_ctd_all(self,min_depth,mode):
|
|
1553
1555
|
|
|
1556
|
+
for id_mvp in range(self.PRES_mvp.shape[0]):
|
|
1557
|
+
id_nearest_ctd = mvp.find_nearest_profile(self.TIME_mvp_corr_interp[id_mvp],self.Lat_mvp_corr_interp[id_mvp], self.Lon_mvp_corr_interp[id_mvp],self.TIME_ctd ,self.LAT_ctd, self.LON_ctd,mode)[0]
|
|
1558
|
+
self.corrige_MVP_offset_on_ctd_simple(id_mvp=id_mvp, id_ctd=id_nearest_ctd, min_depth=min_depth)
|
|
1554
1559
|
|
|
1555
|
-
|
|
1556
|
-
"""
|
|
1557
|
-
Export MVP data to a NetCDF file using xarray.
|
|
1560
|
+
print("MVP profiles corrected for offset against CTD profiles using nearest CTD profiles.")
|
|
1558
1561
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
compression (bool): Enable compression (engine dependent). Default True.
|
|
1563
|
-
engine (str|None): One of 'netcdf4', 'h5netcdf', 'scipy'. If None, choose netcdf4.
|
|
1564
|
-
per_profile_files (bool): If True, write one .nc per MVP cycle (two rows: down and up).
|
|
1565
|
-
"""
|
|
1566
|
-
if not getattr(self, 'mvp', False):
|
|
1567
|
-
raise RuntimeError("No MVP data loaded. Call load_mvp_data() first.")
|
|
1562
|
+
def to_netcdf(self, filepath, corrected=False, compression=True, engine=None, per_profile_files=False):
|
|
1563
|
+
"""
|
|
1564
|
+
Export MVP data to a NetCDF file using xarray.
|
|
1568
1565
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1566
|
+
Args:
|
|
1567
|
+
filepath (str): Output NetCDF file path.
|
|
1568
|
+
corrected (bool): Also write corrected arrays if present (*_mvp_corr). Default False.
|
|
1569
|
+
compression (bool): Enable compression (engine dependent). Default True.
|
|
1570
|
+
engine (str|None): One of 'netcdf4', 'h5netcdf', 'scipy'. If None, choose netcdf4.
|
|
1571
|
+
per_profile_files (bool): If True, write one .nc per MVP cycle (two rows: down and up).
|
|
1572
|
+
"""
|
|
1573
|
+
if not getattr(self, 'mvp', False):
|
|
1574
|
+
raise RuntimeError("No MVP data loaded. Call load_mvp_data() first.")
|
|
1573
1575
|
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
+
engine = 'netcdf4' if engine is None else engine
|
|
1577
|
+
if engine == 'scipy' and compression:
|
|
1578
|
+
print('Warning: scipy backend does not support compression; writing without compression.')
|
|
1579
|
+
compression = False
|
|
1576
1580
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
sample_idx = np.arange(n_samp, dtype=np.int32)
|
|
1581
|
+
# Dimensions
|
|
1582
|
+
n_prof, n_samp = self.PRES_mvp.shape
|
|
1580
1583
|
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
#
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1584
|
+
# Coordinates
|
|
1585
|
+
profile_idx = np.arange(n_prof, dtype=np.int32)
|
|
1586
|
+
sample_idx = np.arange(n_samp, dtype=np.int32)
|
|
1587
|
+
|
|
1588
|
+
# Direction per profile (down/up)
|
|
1589
|
+
direction = None
|
|
1590
|
+
if hasattr(self, 'DIR') and len(self.DIR) == n_prof:
|
|
1591
|
+
direction = np.array(self.DIR, dtype=object)
|
|
1592
|
+
else:
|
|
1593
|
+
# Fallback based on even/odd
|
|
1594
|
+
direction = np.array(['down' if i % 2 == 0 else 'up' for i in range(n_prof)], dtype=object)
|
|
1595
|
+
|
|
1596
|
+
# Per-sample time as seconds since reference origin
|
|
1597
|
+
# TIME_mvp is in days relative to self.date_ref
|
|
1598
|
+
time_seconds = None
|
|
1599
|
+
if hasattr(self, 'TIME_mvp'):
|
|
1600
|
+
time_seconds = self.TIME_mvp * 24.0 * 3600.0
|
|
1601
|
+
else:
|
|
1602
|
+
time_seconds = np.full((n_prof, n_samp), np.nan)
|
|
1603
|
+
|
|
1604
|
+
# Per-profile datetime (one timestamp per cast pair); map using i//2
|
|
1605
|
+
profile_time = None
|
|
1606
|
+
if hasattr(self, 'DATETIME_mvp') and len(getattr(self, 'DATETIME_mvp', [])) > 0:
|
|
1607
|
+
prof_times = []
|
|
1608
|
+
for i in range(n_prof):
|
|
1609
|
+
j = i // 2
|
|
1610
|
+
if j < len(self.DATETIME_mvp) and self.DATETIME_mvp[j] is not None:
|
|
1611
|
+
prof_times.append(np.datetime64(self.DATETIME_mvp[j]))
|
|
1612
|
+
else:
|
|
1613
|
+
prof_times.append(np.datetime64('NaT'))
|
|
1614
|
+
profile_time = np.array(prof_times, dtype='datetime64[ns]')
|
|
1615
|
+
else:
|
|
1616
|
+
profile_time = np.array([np.datetime64('NaT')] * n_prof, dtype='datetime64[ns]')
|
|
1610
1617
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1618
|
+
# Build dataset variables safely
|
|
1619
|
+
data_vars = {}
|
|
1613
1620
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
)
|
|
1621
|
-
|
|
1622
|
-
add_var('PRES', getattr(self, 'PRES_mvp', None), units='dbar', long_name='Sea water pressure')
|
|
1623
|
-
add_var('TEMP', getattr(self, 'TEMP_mvp', None), units='degC', long_name='In-situ temperature')
|
|
1624
|
-
add_var('COND', getattr(self, 'COND_mvp', None), units='mS/cm', long_name='Conductivity')
|
|
1625
|
-
add_var('SAL', getattr(self, 'SALT_mvp', None), units='psu', long_name='Practical salinity')
|
|
1626
|
-
add_var('SOUNDVEL', getattr(self, 'SOUNDVEL_mvp', None), units='m s-1', long_name='Sound speed')
|
|
1627
|
-
add_var('DO', getattr(self, 'DO_mvp', None), units='ml/L', long_name='Dissolved oxygen')
|
|
1628
|
-
add_var('TEMP2', getattr(self, 'TEMP2_mvp', None), units='degC', long_name='Oxygen sensor temperature')
|
|
1629
|
-
add_var('SUNA', getattr(self, 'SUNA_mvp', None), long_name='SUNA raw/derived')
|
|
1630
|
-
add_var('FLUO', getattr(self, 'FLUO_mvp', None), units='ug/L', long_name='Chl fluorescence')
|
|
1631
|
-
add_var('TURB', getattr(self, 'TURB_mvp', None), units='NTU', long_name='Turbidity')
|
|
1632
|
-
add_var('PH', getattr(self, 'PH_mvp', None), units='1', long_name='pH')
|
|
1633
|
-
|
|
1634
|
-
# Position and time arrays (2D)
|
|
1635
|
-
if hasattr(self, 'LAT_mvp'):
|
|
1636
|
-
add_var('LATITUDE', self.LAT_mvp, units='degrees_north', long_name='Latitude at sample')
|
|
1637
|
-
if hasattr(self, 'LON_mvp'):
|
|
1638
|
-
add_var('LONGITUDE', self.LON_mvp, units='degrees_east', long_name='Longitude at sample')
|
|
1639
|
-
# Time seconds since reference
|
|
1640
|
-
data_vars['TIME'] = (
|
|
1641
|
-
('profile', 'sample'), time_seconds,
|
|
1642
|
-
{
|
|
1643
|
-
'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
|
|
1644
|
-
'long_name': 'Time at sample'
|
|
1645
|
-
}
|
|
1621
|
+
def add_var(var_name, arr, units=None, long_name=None):
|
|
1622
|
+
if arr is None:
|
|
1623
|
+
return
|
|
1624
|
+
data_vars[var_name] = (
|
|
1625
|
+
('profile', 'sample'), arr,
|
|
1626
|
+
{k: v for k, v in [('units', units), ('long_name', long_name)] if v is not None}
|
|
1646
1627
|
)
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
add_corr('longitude_corrected', 'LON_mvp_corr', units='degrees_east', long_name='Corrected longitude at sample')
|
|
1672
|
-
|
|
1673
|
-
# Coordinates and auxiliary per-profile variables
|
|
1674
|
-
coords = {
|
|
1675
|
-
'profile': ('profile', profile_idx),
|
|
1676
|
-
'sample': ('sample', sample_idx)
|
|
1628
|
+
|
|
1629
|
+
add_var('PRES', getattr(self, 'PRES_mvp', None), units='dbar', long_name='Sea water pressure')
|
|
1630
|
+
add_var('TEMP', getattr(self, 'TEMP_mvp', None), units='degC', long_name='In-situ temperature')
|
|
1631
|
+
add_var('COND', getattr(self, 'COND_mvp', None), units='mS/cm', long_name='Conductivity')
|
|
1632
|
+
add_var('SAL', getattr(self, 'SALT_mvp', None), units='psu', long_name='Practical salinity')
|
|
1633
|
+
add_var('SOUNDVEL', getattr(self, 'SOUNDVEL_mvp', None), units='m s-1', long_name='Sound speed')
|
|
1634
|
+
add_var('DO', getattr(self, 'DO_mvp', None), units='ml/L', long_name='Dissolved oxygen')
|
|
1635
|
+
add_var('TEMP2', getattr(self, 'TEMP2_mvp', None), units='degC', long_name='Oxygen sensor temperature')
|
|
1636
|
+
add_var('SUNA', getattr(self, 'SUNA_mvp', None), long_name='SUNA raw/derived')
|
|
1637
|
+
add_var('FLUO', getattr(self, 'FLUO_mvp', None), units='ug/L', long_name='Chl fluorescence')
|
|
1638
|
+
add_var('TURB', getattr(self, 'TURB_mvp', None), units='NTU', long_name='Turbidity')
|
|
1639
|
+
add_var('PH', getattr(self, 'PH_mvp', None), units='1', long_name='pH')
|
|
1640
|
+
|
|
1641
|
+
# Position and time arrays (2D)
|
|
1642
|
+
if hasattr(self, 'LAT_mvp'):
|
|
1643
|
+
add_var('LATITUDE', self.Lat_mvp, units='degrees_north', long_name='Latitude at sample')
|
|
1644
|
+
if hasattr(self, 'LON_mvp'):
|
|
1645
|
+
add_var('LONGITUDE', self.Lon_mvp, units='degrees_east', long_name='Longitude at sample')
|
|
1646
|
+
# Time seconds since reference
|
|
1647
|
+
data_vars['TIME'] = (
|
|
1648
|
+
('profile', 'sample'), time_seconds,
|
|
1649
|
+
{
|
|
1650
|
+
'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
|
|
1651
|
+
'long_name': 'Time at sample'
|
|
1677
1652
|
}
|
|
1653
|
+
)
|
|
1678
1654
|
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
)
|
|
1699
|
-
|
|
1700
|
-
# Optional per-profile lat/lon (first valid sample)
|
|
1701
|
-
def first_valid(vec):
|
|
1702
|
-
# vec shape (n_prof, n_samp)
|
|
1703
|
-
out = np.full((vec.shape[0],), np.nan)
|
|
1704
|
-
for i in range(vec.shape[0]):
|
|
1705
|
-
row = vec[i]
|
|
1706
|
-
j = np.where(~np.isnan(row))[0]
|
|
1707
|
-
if j.size:
|
|
1708
|
-
out[i] = row[j[0]]
|
|
1709
|
-
return out
|
|
1710
|
-
|
|
1711
|
-
if hasattr(self, 'LAT_mvp'):
|
|
1712
|
-
coords['profile_lat'] = (
|
|
1713
|
-
'profile', first_valid(self.LAT_mvp), {'units': 'degrees_north', 'long_name': 'Profile latitude'}
|
|
1714
|
-
)
|
|
1715
|
-
if hasattr(self, 'LON_mvp'):
|
|
1716
|
-
coords['profile_lon'] = (
|
|
1717
|
-
'profile', first_valid(self.LON_mvp), {'units': 'degrees_east', 'long_name': 'Profile longitude'}
|
|
1655
|
+
# Include corrected arrays if requested and present
|
|
1656
|
+
if corrected:
|
|
1657
|
+
def add_corr(name, attr, units=None, long_name=None):
|
|
1658
|
+
if hasattr(self, attr):
|
|
1659
|
+
data_vars[name] = (
|
|
1660
|
+
('profile', 'sample'), getattr(self, attr),
|
|
1661
|
+
{k: v for k, v in [('units', units), ('long_name', long_name)] if v is not None}
|
|
1662
|
+
)
|
|
1663
|
+
add_corr('pressure_corrected', 'PRES_mvp_corr', units='dbar', long_name='Corrected pressure')
|
|
1664
|
+
add_corr('temperature_corrected', 'TEMP_mvp_corr', units='degC', long_name='Corrected temperature')
|
|
1665
|
+
add_corr('conductivity_corrected', 'COND_mvp_corr', units='mS/cm', long_name='Corrected conductivity')
|
|
1666
|
+
add_corr('salinity_corrected', 'SALT_mvp_corr', units='psu', long_name='Corrected salinity')
|
|
1667
|
+
if hasattr(self, 'TIME_mvp_corr'):
|
|
1668
|
+
data_vars['time_corrected'] = (
|
|
1669
|
+
('profile', 'sample'), self.TIME_mvp_corr * 24.0 * 3600.0,
|
|
1670
|
+
{
|
|
1671
|
+
'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
|
|
1672
|
+
'long_name': 'Corrected time at sample'
|
|
1673
|
+
}
|
|
1718
1674
|
)
|
|
1675
|
+
if hasattr(self, 'LAT_mvp_corr'):
|
|
1676
|
+
add_corr('latitude_corrected', 'LAT_mvp_corr', units='degrees_north', long_name='Corrected latitude at sample')
|
|
1677
|
+
if hasattr(self, 'LON_mvp_corr'):
|
|
1678
|
+
add_corr('longitude_corrected', 'LON_mvp_corr', units='degrees_east', long_name='Corrected longitude at sample')
|
|
1679
|
+
|
|
1680
|
+
# Coordinates and auxiliary per-profile variables
|
|
1681
|
+
coords = {
|
|
1682
|
+
'profile': ('profile', profile_idx),
|
|
1683
|
+
'sample': ('sample', sample_idx)
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
# Encode direction/time according to engine capabilities
|
|
1687
|
+
if engine in ('netcdf4', 'h5netcdf'):
|
|
1688
|
+
coords['direction'] = ('profile', direction.astype('U'), {'long_name': 'Profile direction'})
|
|
1689
|
+
coords['profile_time'] = ('profile', profile_time, {'long_name': 'Profile nominal time'})
|
|
1690
|
+
else:
|
|
1691
|
+
# scipy backend: avoid object strings and datetime; use numeric fallbacks
|
|
1692
|
+
dir_flag = np.where(direction.astype('U') == 'down', 0, 1).astype('int8')
|
|
1693
|
+
coords['direction_flag'] = (
|
|
1694
|
+
'profile', dir_flag, {'long_name': 'Profile direction (0=down,1=up)'}
|
|
1695
|
+
)
|
|
1696
|
+
ref = np.datetime64(self.date_ref)
|
|
1697
|
+
pt = profile_time.astype('datetime64[s]')
|
|
1698
|
+
mask = (pt == np.datetime64('NaT'))
|
|
1699
|
+
secs = (pt - ref).astype('timedelta64[s]').astype('float64')
|
|
1700
|
+
secs[mask] = np.nan
|
|
1701
|
+
coords['profile_time_sec'] = (
|
|
1702
|
+
'profile', secs,
|
|
1703
|
+
{'units': f'seconds since {self.date_ref.strftime("%Y-%m-%d %H:%M:%S")}',
|
|
1704
|
+
'long_name': 'Profile nominal time'}
|
|
1705
|
+
)
|
|
1719
1706
|
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1707
|
+
# Optional per-profile lat/lon (first valid sample)
|
|
1708
|
+
def first_valid(vec):
|
|
1709
|
+
# vec shape (n_prof, n_samp)
|
|
1710
|
+
out = np.full((vec.shape[0],), np.nan)
|
|
1711
|
+
for i in range(vec.shape[0]):
|
|
1712
|
+
row = vec[i]
|
|
1713
|
+
j = np.where(~np.isnan(row))[0]
|
|
1714
|
+
if j.size:
|
|
1715
|
+
out[i] = row[j[0]]
|
|
1716
|
+
return out
|
|
1717
|
+
|
|
1718
|
+
if hasattr(self, 'LAT_mvp'):
|
|
1719
|
+
coords['profile_lat'] = (
|
|
1720
|
+
'profile', first_valid(self.Lat_mvp), {'units': 'degrees_north', 'long_name': 'Profile latitude'}
|
|
1721
|
+
)
|
|
1722
|
+
if hasattr(self, 'LON_mvp'):
|
|
1723
|
+
coords['profile_lon'] = (
|
|
1724
|
+
'profile', first_valid(self.Lon_mvp), {'units': 'degrees_east', 'long_name': 'Profile longitude'}
|
|
1725
|
+
)
|
|
1740
1726
|
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1727
|
+
# Global attributes
|
|
1728
|
+
attrs = {
|
|
1729
|
+
'title': 'MVP profile data',
|
|
1730
|
+
'Conventions': 'CF-1.8',
|
|
1731
|
+
'institution': 'LMD/CNRS',
|
|
1732
|
+
'source': 'PyMVP',
|
|
1733
|
+
'history': f"Created on {datetime.now().isoformat()}",
|
|
1734
|
+
'mvp_Yorig': int(self.Yorig)
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
ds = xr.Dataset(data_vars=data_vars, coords=coords, attrs=attrs)
|
|
1738
|
+
|
|
1739
|
+
# Compression encoding per engine
|
|
1740
|
+
encoding = None
|
|
1741
|
+
if compression:
|
|
1742
|
+
if engine == 'netcdf4':
|
|
1743
|
+
encoding = {name: {'zlib': True, 'complevel': 4} for name in data_vars.keys()}
|
|
1744
|
+
elif engine == 'h5netcdf':
|
|
1745
|
+
encoding = {name: {'compression': 'gzip', 'compression_opts': 4} for name in data_vars.keys()}
|
|
1746
|
+
|
|
1747
|
+
|
|
1748
|
+
if (not per_profile_files) and filepath.lower().endswith('.nc'):
|
|
1749
|
+
out_path = filepath
|
|
1750
|
+
ds.to_netcdf(out_path, encoding=encoding, engine=engine)
|
|
1751
|
+
print(f"NetCDF written: {out_path} using engine={engine}")
|
|
1752
|
+
return
|
|
1753
|
+
base_dir = filepath
|
|
1754
|
+
|
|
1755
|
+
if not base_dir.endswith(os.sep):
|
|
1756
|
+
base_dir = base_dir + os.sep
|
|
1757
|
+
|
|
1758
|
+
base_name = "MVP_" + os.path.basename(self.data_path).rstrip(os.sep)
|
|
1759
|
+
if per_profile_files:
|
|
1760
|
+
# Write one file per pair (down/up)
|
|
1761
|
+
total_pairs = (n_prof + 1) // 2
|
|
1762
|
+
for i in range(total_pairs):
|
|
1763
|
+
idxs = [k for k in (2*i, 2*i+1) if k < n_prof]
|
|
1764
|
+
if not idxs:
|
|
1765
|
+
continue
|
|
1766
|
+
ds_i = ds.isel(profile=idxs)
|
|
1767
|
+
|
|
1768
|
+
#add i to filename
|
|
1769
|
+
fname = f"{base_name}_profile_{i:03d}.nc"
|
|
1770
|
+
out_path = os.path.join(base_dir, fname)
|
|
1771
|
+
ds_i.to_netcdf(out_path, encoding=encoding, engine=engine)
|
|
1772
|
+
print(f"NetCDF written per profile into: {base_dir} using engine={engine}")
|
|
1773
|
+
else:
|
|
1774
|
+
file_name = f"{base_name}.nc"
|
|
1775
|
+
out_path = os.path.join(base_dir, file_name)
|
|
1776
|
+
ds.to_netcdf(out_path, encoding=encoding, engine=engine)
|
|
1777
|
+
print(f"NetCDF written: {out_path} using engine={engine}")
|
|
1771
1778
|
|
|
1772
1779
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1780
|
+
def help(self):
|
|
1781
|
+
"""
|
|
1782
|
+
Print all methods of the class with their docstring (header).
|
|
1783
|
+
"""
|
|
1784
|
+
for attr in dir(self):
|
|
1785
|
+
if callable(getattr(self, attr)) and not attr.startswith("__"):
|
|
1786
|
+
method = getattr(self, attr)
|
|
1787
|
+
doc = method.__doc__
|
|
1788
|
+
print(f"{attr}:\n{doc}\n{'-'*40}")
|
|
1782
1789
|
|
|
1783
1790
|
|
|
1784
1791
|
def plot_MVP_transect(self,var='TEMP',l_id=None,depth_max=None,depth_min=None,vmax=None,vmin=None,cmap=None):
|
|
@@ -1796,7 +1803,7 @@ class Analyzer:
|
|
|
1796
1803
|
|
|
1797
1804
|
"""
|
|
1798
1805
|
|
|
1799
|
-
if
|
|
1806
|
+
if hasattr(self, 'PRES_mvp_corr_interp') == False:
|
|
1800
1807
|
raise ValueError("Corrected and interpolated MVP data not available. Apply corrections and interpolation first.")
|
|
1801
1808
|
|
|
1802
1809
|
if l_id is None:
|
|
@@ -1908,9 +1915,9 @@ class Analyzer:
|
|
|
1908
1915
|
ax.invert_yaxis()
|
|
1909
1916
|
ax.set_xlabel("Distance le long du transect [km]")
|
|
1910
1917
|
ax.set_ylabel("Profondeur [m]")
|
|
1911
|
-
ax.set_title("
|
|
1918
|
+
ax.set_title(f"{var} transect (interpolated)")
|
|
1912
1919
|
cbar = plt.colorbar(pcm, ax=ax)
|
|
1913
|
-
cbar.set_label("
|
|
1920
|
+
cbar.set_label(f"{var} (units)")
|
|
1914
1921
|
plt.show()
|
|
1915
1922
|
|
|
1916
1923
|
|
|
@@ -1074,3 +1074,55 @@ def align_profiles(P, T_ref, T_to_align_raw, min_depth=0,max_shift=20):
|
|
|
1074
1074
|
T_out[T_out_indices[mask_corrected]] = T_corrected[mask_corrected]
|
|
1075
1075
|
|
|
1076
1076
|
return T_out, deltaP, deltaT
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
def find_nearest_profile(time_mvp,Lat_mvp,Lon_mvp,time_ctd,Lat_ctd,Lon_ctd,mode):
|
|
1082
|
+
|
|
1083
|
+
if mode=='Dist':
|
|
1084
|
+
idx = len(Lat_mvp)//2
|
|
1085
|
+
Lat_mvp = np.radians(Lat_mvp[idx])
|
|
1086
|
+
Lon_mvp = np.radians(Lon_mvp[idx])
|
|
1087
|
+
|
|
1088
|
+
R = 6371.0
|
|
1089
|
+
|
|
1090
|
+
min_dist = np.inf
|
|
1091
|
+
nearest_index = -1
|
|
1092
|
+
|
|
1093
|
+
for i in range(len(Lat_ctd)):
|
|
1094
|
+
|
|
1095
|
+
lat,lon = np.radians(Lat_ctd[i]), np.radians(Lon_ctd[i])
|
|
1096
|
+
mask = np.isfinite(lat) & np.isfinite(lon)
|
|
1097
|
+
lat,lon = lat[mask], lon[mask]
|
|
1098
|
+
lat,lon = lat[0],lon[0]
|
|
1099
|
+
|
|
1100
|
+
dlon = lon - Lon_mvp
|
|
1101
|
+
dlat = lat - Lat_mvp
|
|
1102
|
+
a = np.sin(dlat / 2)**2 + np.cos(Lat_mvp) * np.cos(lat) * np.sin(dlon / 2)**2
|
|
1103
|
+
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
|
|
1104
|
+
dist = R * c * 1e3 # Convert to meters
|
|
1105
|
+
if dist < min_dist:
|
|
1106
|
+
min_dist = dist
|
|
1107
|
+
nearest_index = i
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
return nearest_index, min_dist
|
|
1111
|
+
|
|
1112
|
+
elif mode=='Time':
|
|
1113
|
+
time_mvp = time_mvp[len(time_mvp)//2] # Take the middle time of the MVP cycle as reference
|
|
1114
|
+
|
|
1115
|
+
min_time_diff = np.inf
|
|
1116
|
+
nearest_index = -1
|
|
1117
|
+
for i in range(len(time_ctd)):
|
|
1118
|
+
time_diff = np.abs(time_ctd[i,-1] - time_mvp)
|
|
1119
|
+
if time_diff < min_time_diff:
|
|
1120
|
+
min_time_diff = time_diff
|
|
1121
|
+
nearest_index = i
|
|
1122
|
+
return nearest_index, min_time_diff
|
|
1123
|
+
|
|
1124
|
+
else:
|
|
1125
|
+
raise ValueError("Mode should be 'Dist' or 'Time'")
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|