diff --git a/docs/examples/streaks.ipynb b/docs/examples/streaks.ipynb index 41e118f..62cdd0b 100644 --- a/docs/examples/streaks.ipynb +++ b/docs/examples/streaks.ipynb @@ -55,6 +55,7 @@ "metadata": {}, "outputs": [], "source": [ + "xsarsea.windspeed.gmfs.GmfModel.activate_gmfs_impl()\n", "xsarsea.windspeed.available_models()" ] }, @@ -138,7 +139,7 @@ "streaks_geo['weight'] = streaks['weight']\n", "\n", "# convert directions from image convention to geographic convention\n", - "streaks_geo['streaks_dir'] = xsarsea.dir_sample_to_geo(np.rad2deg(streaks['angle']), streaks_geo['ground_heading']) \n", + "streaks_geo['streaks_dir'] = xsarsea.dir_sample_to_meteo(np.rad2deg(streaks['angle']), streaks_geo['ground_heading']) \n", "\n", "streaks_geo = streaks_geo.compute()\n", "\n", @@ -228,7 +229,7 @@ "source": [ "#### Convertion between image convention and geographic convention\n", "\n", - "see [xsarsea.dir_geo_to_sample](../basic_api.rst#xsarsea.dir_geo_to_sample) and [xsarsea.dir_sample_to_geo](../basic_api.rst#xsarsea.dir_sample_to_geo)\n", + "see [xsarsea.dir_meteo_to_sample](../basic_api.rst#xsarsea.dir_meteo_to_sample) and [xsarsea.dir_sample_to_meteo](../basic_api.rst#xsarsea.dir_sample_to_meteo)\n", "\n" ] }, diff --git a/docs/examples/windspeed_inversion.ipynb b/docs/examples/windspeed_inversion.ipynb index 683b830..f3c63b6 100644 --- a/docs/examples/windspeed_inversion.ipynb +++ b/docs/examples/windspeed_inversion.ipynb @@ -18,7 +18,7 @@ "> .. warning::\n", " **Use of ancillary wind**\n", "\n", - "> We suggest to go from `ancillary_wind = -np.conj(sarwing_ds.owi_ancillary_wind)` to `sarwing_ds.owi_ancillary_wind`` ; then it won't match sarwing results anymore\n", + "> We suggest to go from `ancillary_wind = -np.conj(sarwing_ds.owi_ancillary_wind)` to `ancillary_wind = sarwing_ds.owi_ancillary_wind` ; then it won't match sarwing results anymore\n", "\n" ] }, @@ -82,7 +82,7 @@ "\n", "Ecmwf wind is stored in owi file in *geographical* (deg N/S) convention. `xsarsea.windspeed` need it relative to *sample* (ie antenna), as a complex number.\n", "\n", - "We use [xsarsea.dir_geo_to_sample](../basic_api.rst#xsarsea.dir_geo_to_sample) to convert `sarwing_ds.owiEcmwfWindDirection` (deg) to radians, relative to sample, using `sarwing_ds.owiHeading`\n" + "We use [xsarsea.dir_meteo_to_sample](../basic_api.rst#xsarsea.dir_meteo_to_sample) to convert `sarwing_ds.owiEcmwfWindDirection` (deg) to radians, relative to sample, using `sarwing_ds.owiHeading`\n" ] }, { @@ -92,7 +92,7 @@ "metadata": {}, "outputs": [], "source": [ - "owi_ecmwf_wind = sarwing_ds.owiEcmwfWindSpeed * np.exp(1j* xsarsea.dir_geo_to_sample(sarwing_ds.owiEcmwfWindDirection, sarwing_ds.owiHeading))\n", + "owi_ecmwf_wind = sarwing_ds.owiEcmwfWindSpeed * np.exp(1j* xsarsea.dir_meteo_to_sample(sarwing_ds.owiEcmwfWindDirection, sarwing_ds.owiHeading))\n", "sarwing_ds = xr.merge([\n", " sarwing_ds,\n", " owi_ecmwf_wind.to_dataset(name='owi_ancillary_wind'),\n", @@ -127,7 +127,7 @@ "vectorfield = hv.VectorField(\n", " (\n", " sub_sarwing_ds.sample, sub_sarwing_ds.line,\n", - " xsarsea.dir_geo_to_sample(sub_sarwing_ds.owiEcmwfWindDirection, sub_sarwing_ds.owiHeading),\n", + " xsarsea.dir_meteo_to_sample(sub_sarwing_ds.owiEcmwfWindDirection, sub_sarwing_ds.owiHeading),\n", " sub_sarwing_ds.owiEcmwfWindSpeed\n", " )\n", ")\n", @@ -478,7 +478,7 @@ "vectorfield = hv.VectorField(\n", " (\n", " sub_sarwing_ds.sample, sub_sarwing_ds.line,\n", - " xsarsea.dir_geo_to_sample(sarwing_ds[\"winddir_dual\"] ,sub_sarwing_ds.owiHeading),\n", + " xsarsea.dir_meteo_to_sample(sarwing_ds[\"winddir_dual\"] ,sub_sarwing_ds.owiHeading),\n", " np.abs(wind_dual).isel(line=slice(None, None, 10), sample=slice(None, None, 10))\n", " )\n", ")\n", @@ -506,7 +506,7 @@ "vectorfield = hv.VectorField(\n", " (\n", " sub_sarwing_ds.sample, sub_sarwing_ds.line,\n", - " xsarsea.dir_geo_to_sample(sub_sarwing_ds.owiWindDirection ,sub_sarwing_ds.owiHeading),\n", + " xsarsea.dir_meteo_to_sample(sub_sarwing_ds.owiWindDirection ,sub_sarwing_ds.owiHeading),\n", " sub_sarwing_ds.owiWindSpeed\n", " )\n", ")\n", diff --git a/docs/examples/windspeed_retrieval_L1.ipynb b/docs/examples/windspeed_retrieval_L1.ipynb index 18b4ad2..2a514b6 100644 --- a/docs/examples/windspeed_retrieval_L1.ipynb +++ b/docs/examples/windspeed_retrieval_L1.ipynb @@ -249,7 +249,7 @@ "dataset['sigma0_ocean'] = xr.where(dataset['sigma0'] <= 0, 1e-15, xsar_obj.dataset['sigma0'])\n", "dataset['ancillary_wind_direction'] = (90. - np.rad2deg(np.arctan2(dataset.model_V10, dataset.model_U10)) + 180) % 360\n", "dataset['ancillary_wind_speed'] = np.sqrt(dataset['model_U10']**2+dataset['model_V10']**2)\n", - "dataset['ancillary_wind'] = dataset.ancillary_wind_speed * np.exp(1j * xsarsea.dir_geo_to_sample(dataset.ancillary_wind_direction, dataset.ground_heading)) # ref antenna" + "dataset['ancillary_wind'] = dataset.ancillary_wind_speed * np.exp(1j * xsarsea.dir_meteo_to_sample(dataset.ancillary_wind_direction, dataset.ground_heading)) # ref antenna" ] }, { @@ -471,7 +471,7 @@ "vectorfield = hv.VectorField(\n", " (\n", " sub_ds.sample, sub_ds.line,\n", - " xsarsea.dir_geo_to_sample(sub_ds.winddir_dual,sub_ds.ground_heading),\n", + " xsarsea.dir_meteo_to_sample(sub_ds.winddir_dual,sub_ds.ground_heading),\n", " sub_ds.windspeed_dual\n", " )\n", ")\n", @@ -491,7 +491,7 @@ "vectorfield = hv.VectorField(\n", " (\n", " sub_ds.sample, sub_ds.line,\n", - " xsarsea.dir_geo_to_sample(sub_ds.ancillary_wind_direction,sub_ds.ground_heading),\n", + " xsarsea.dir_meteo_to_sample(sub_ds.ancillary_wind_direction,sub_ds.ground_heading),\n", " sub_ds.ancillary_wind_speed\n", " )\n", ")\n", diff --git a/src/xsarsea/__init__.py b/src/xsarsea/__init__.py index b8e73dc..a78a1b0 100644 --- a/src/xsarsea/__init__.py +++ b/src/xsarsea/__init__.py @@ -1,10 +1,10 @@ -__all__ = ['sigma0_detrend', 'dir_geo_to_sample', 'dir_sample_to_geo', 'get_test_file'] +__all__ = ['sigma0_detrend', 'dir_meteo_to_sample', + 'dir_sample_to_meteo', 'dir_meteo_to_oceano', 'dir_oceano_to_meteo', 'dir_to_180', 'dir_to_360', 'get_test_file'] from .utils import get_test_file -from .xsarsea import dir_geo_to_sample, dir_sample_to_geo, sigma0_detrend, read_sarwing_owi +from .xsarsea import dir_meteo_to_sample, dir_sample_to_meteo, sigma0_detrend, dir_meteo_to_oceano, dir_oceano_to_meteo, dir_to_180, dir_to_360, read_sarwing_owi try: from importlib import metadata except ImportError: # for Python<3.8 import importlib_metadata as metadata __version__ = metadata.version('xsarsea') - diff --git a/src/xsarsea/gradients.py b/src/xsarsea/gradients.py index 34bce4a..0db0fc4 100644 --- a/src/xsarsea/gradients.py +++ b/src/xsarsea/gradients.py @@ -12,7 +12,11 @@ __all__ = ['Gradients', 'Gradients2D', 'circ_smooth', 'PlotGradients', 'circ_hist'] -import cv2 +try: + import cv2 +except: + import cv2 + import numpy as np from scipy import signal, ndimage import xarray as xr diff --git a/src/xsarsea/windspeed/__init__.py b/src/xsarsea/windspeed/__init__.py index 5754e66..98f808d 100644 --- a/src/xsarsea/windspeed/__init__.py +++ b/src/xsarsea/windspeed/__init__.py @@ -2,9 +2,9 @@ windspeed module, for retrieving wind speed from sigma0 and models. """ __all__ = ['invert_from_model', 'available_models', 'get_model', 'register_cmod7', - 'register_sarwing_luts', 'register_nc_luts', 'nesz_flattening', 'GmfModel'] + 'register_sarwing_luts', 'register_nc_luts', 'register_luts', 'nesz_flattening', 'GmfModel'] from .windspeed import invert_from_model -from .models import available_models, get_model, register_nc_luts +from .models import available_models, get_model, register_nc_luts, register_luts from .sarwing_luts import register_sarwing_luts from .cmod7 import register_cmod7 from .utils import nesz_flattening, get_dsig diff --git a/src/xsarsea/windspeed/models.py b/src/xsarsea/windspeed/models.py index 20dab2d..be41ea5 100644 --- a/src/xsarsea/windspeed/models.py +++ b/src/xsarsea/windspeed/models.py @@ -273,7 +273,7 @@ def __call__(self, inc, wspd, phi=None, broadcast=False): wspd: array-like windspeed phi: array-like or None - wind direction, in **gmf convention** + wind direction relative to antenna broadcast: bool | If True, input arrays will be broadcasted to the same dimensions, and output will have the same dimension. | This option is only available for :func:`~xsarsea.windspeed.gmfs.GmfModel` @@ -397,7 +397,7 @@ def _raw_lut(self, **kwargs): return lut -def register_nc_luts(topdir, gmf_names=None, **kwargs): +def register_nc_luts(topdir, gmf_names=None): """ Register all netcdf luts found under `topdir`. @@ -518,3 +518,32 @@ def get_model(name): raise KeyError('model %s not found' % name) return model + + +def register_luts(topdir=None, topdir_cmod7=None): + """ + Register gmfModel luts and ncLutModel luts + + Parameters + ---------- + topdir: str + top dir path to nc luts. + + topdir_cmod7: str + top dir path to cmod7 luts. + + kwargs: dict + kwargs to pass to register_nc_luts + """ + + # register gmf luts + import xsarsea.windspeed as windspeed + windspeed.GmfModel.activate_gmfs_impl() + + # register nc luts + if topdir != None: + register_nc_luts(topdir) + + # register cmod7 + if topdir_cmod7 != None: + windspeed.register_cmod7(topdir_cmod7) diff --git a/src/xsarsea/windspeed/windspeed.py b/src/xsarsea/windspeed/windspeed.py index b5d69b9..32de90b 100644 --- a/src/xsarsea/windspeed/windspeed.py +++ b/src/xsarsea/windspeed/windspeed.py @@ -23,8 +23,7 @@ def invert_from_model(inc, sigma0, sigma0_dual=None, /, ancillary_wind=None, dsi sigma0 in cross pol for dualpol invertion ancillary_wind=: xarray.DataArray (numpy.complex28) ancillary wind - - | (for example ecmwf winds), in **model convention** + | (for example ecmwf winds), in **antenna convention** model=: str or tuple model to use. @@ -46,8 +45,7 @@ def invert_from_model(inc, sigma0, sigma0_dual=None, /, ancillary_wind=None, dsi ------- xarray.DataArray or tuple If available (copol or dualpol), the returned array is `np.complex64`, with the angle of the returned array is - inverted direction in **gmf convention** (use `-np.conj(result))` to get it in standard convention) - + inverted direction in **antenna convention** See Also -------- xsarsea.windspeed.available_models @@ -170,12 +168,6 @@ def __invert_from_model_1d(inc_1d, sigma0_co_db_1d, sigma0_cr_db_1d, dsig_cr_1d, # this function will be vectorized with 'numba.guvectorize' or 'numpy.frompyfunc' # set debug=True below to force 'numpy.frompyfunc', so you can debug this code - # gmf and lut doesn't have the same direction convention than xsarsea in the sample direction - # for xsarsea, positive sample means in the sample increasing direction - # for gmf and lut, positive means in the sample decreasing direction - # we switch ancillary wind to the gmf convention - # ancillary_wind_1d = -np.conj(ancillary_wind_1d) - for i in range(len(inc_1d)): one_inc = inc_1d[i] one_sigma0_co_db = sigma0_co_db_1d[i] diff --git a/src/xsarsea/xsarsea.py b/src/xsarsea/xsarsea.py index f94e401..705cd65 100644 --- a/src/xsarsea/xsarsea.py +++ b/src/xsarsea/xsarsea.py @@ -89,13 +89,13 @@ def read_sarwing_owi(owi_file): return sarwing_ds -def dir_geo_to_sample(geo_dir, ground_heading): +def dir_meteo_to_sample(meteo_dir, ground_heading): """ - Convert geographical N/S direction to image convention + Convert meteorological N/S direction to image convention Parameters ---------- - geo_dir: geographical direction in degrees north + meteo_dir: meteorological direction in degrees north ground_heading: azimuth at position, in degrees north Returns @@ -104,22 +104,94 @@ def dir_geo_to_sample(geo_dir, ground_heading): same shape as input. angle in radian, relative to sample, anticlockwise """ - return np.pi / 2 - np.deg2rad(geo_dir - ground_heading) + return np.pi / 2 - np.deg2rad(meteo_dir - ground_heading) -def dir_sample_to_geo(sample_dir, ground_heading): +def dir_sample_to_meteo(sample_dir, ground_heading): """ - Convert image direction relative to antenna to geographical direction + Convert image direction relative to antenna to meteorological direction Parameters ---------- - sample_dir: geographical direction in degrees north + sample_dir: angle in degrees, relative to sample, anticlockwise ground_heading: azimuth at position, in degrees north Returns ------- np.float64 - same shape as input. angle in degrees, relative to sample, anticlockwise + same shape as input. meteorological direction in degrees north """ return 90 - sample_dir + ground_heading + + +def dir_meteo_to_oceano(meteo_dir): + """ + Convert meteorological direction to oceanographic direction + + Parameters + ---------- + meteo_dir: float + Wind direction in meteorological convention (clockwise, from), ex: 0°=from north, 90°=from east + + Returns + ------- + float + Wind direction in oceanographic convention (clockwise, to), ex: 0°=to north, 90°=to east + """ + oceano_dir = (meteo_dir + 180) % 360 + return oceano_dir + + +def dir_oceano_to_meteo(oceano_dir): + """ + Convert oceanographic direction to meteorological direction + + Parameters + ---------- + oceano_dir: float + Wind direction in oceanographic convention (clockwise, to), ex: 0°=to north, 90°=to east + + Returns + ------- + float + Wind direction in meteorological convention (clockwise, from), ex: 0°=from north, 90°=from east + """ + meteo_dir = (oceano_dir - 180) % 360 + return meteo_dir + + +def dir_to_180(angle): + """ + Convert angle to [-180;180] + + Parameters + ---------- + angle: float + angle in degrees + + Returns + ------- + float + angle in degrees + """ + angle_180 = (angle + 180) % 360 - 180 + return angle_180 + + +def dir_to_360(angle): + """ + Convert angle to [0;360] + + Parameters + ---------- + angle: float + angle in degrees + + Returns + ------- + float + angle in degrees + """ + angle_360 = (angle + 360) % 360 + return angle_360 diff --git a/test/test_xsarsea.py b/test/test_xsarsea.py index 6057e9c..dca1f30 100644 --- a/test/test_xsarsea.py +++ b/test/test_xsarsea.py @@ -84,7 +84,7 @@ def test_inversion(): line=slice(0, 50), sample=slice(0, 60)) owi_ecmwf_wind = sarwing_ds.owiEcmwfWindSpeed * np.exp( - 1j * xsarsea.dir_geo_to_sample(sarwing_ds.owiEcmwfWindDirection, sarwing_ds.owiHeading)) + 1j * xsarsea.dir_meteo_to_sample(sarwing_ds.owiEcmwfWindDirection, sarwing_ds.owiHeading)) sarwing_ds = xr.merge([ sarwing_ds, owi_ecmwf_wind.to_dataset(name='owi_ancillary_wind'),