From 7bf72a3410ca6c0cc560c36e64fdef7ccbc067aa Mon Sep 17 00:00:00 2001 From: Oliver King Date: Mon, 23 Jan 2023 13:53:28 +0000 Subject: [PATCH 1/2] Return a copy of mapped data to protect cached data --- planetmapper/observation.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/planetmapper/observation.py b/planetmapper/observation.py index 85cfaae..49a2cc1 100644 --- a/planetmapper/observation.py +++ b/planetmapper/observation.py @@ -488,8 +488,6 @@ def fit_disc_radius(self) -> None: self.set_disc_method('fit_r0') # Mapping - @_cache_clearable_result_with_args - @progress_decorator def get_mapped_data( self, degree_interval: float = 1, @@ -499,6 +497,13 @@ def get_mapped_data( Projects the observed :attr:`data` onto a lon/lat grid using :func:`BodyXY.map_img`. + For larger datasets, it can take some time to map every wavelength. Therefore, + the mapped data is automatically cached (in a similar way to backplanes - see + :class:`BodyXY`) so that subsequent calls to this function do not have to + recompute the mapped data. As with cached backplanes, the cached mapped data is + automatically cleared if any disc parameters are changed (i.e. you shouldn't + need to worry about the cache, it all happens 'magically' behind the scenes). + Args: degree_interval: Interval in degrees between the longitude/latitude points. Passed to :func:`BodyXY.map_img`. @@ -511,6 +516,18 @@ def get_mapped_data( each location on the surface of the target body. Locations which are not visible have a value of NaN. """ + # Return a copy so that the cached value isn't tainted by any modifications + return self._get_mapped_data( + degree_interval=degree_interval, interpolation=interpolation + ).copy() + + @_cache_clearable_result_with_args + @progress_decorator + def _get_mapped_data( + self, + degree_interval: float = 1, + interpolation: Literal['nearest', 'linear', 'quadratic', 'cubic'] = 'linear', + ): projected = [] if interpolation == 'linear' and np.any(np.isnan(self.data)): data = np.nan_to_num(self.data) From bfbf77f9f32b18f9809cb5a2d3fc19e4c76133ab Mon Sep 17 00:00:00 2001 From: Oliver King Date: Mon, 23 Jan 2023 13:53:57 +0000 Subject: [PATCH 2/2] Improved documentation of cache behaviour --- docs/general_python_api.rst | 34 ++++++++++++++++++++++++++++++++++ docs/installation.rst | 1 + planetmapper/progress.py | 2 +- scratchpad.py | 26 +++++++++++++------------- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/docs/general_python_api.rst b/docs/general_python_api.rst index 2f9a574..bca4a67 100644 --- a/docs/general_python_api.rst +++ b/docs/general_python_api.rst @@ -236,5 +236,39 @@ Backplanes can also be generated for observations which do not exist using :clas :alt: Plot of Jupiter's rotation +Cache behaviour +=============== +The generation of backplanes and projected mapped data can be slow for larger datasets. Therefore, :class:`planetmapper.BodyXY` and :class:`planetmapper.Observation` objects automatically cache the results of various expensive function calls so that they do not have to be recalculated. This cache management happens automatically behind the scenes, so you should never have to worry about dealing with it directly. For example, when any disc parameters are changed, the cache is automatically cleared as the cached results will no longer be valid. + +:: + + # Create a new object + body = planetmapper.BodyXY('Jupiter', '2000-01-01', sz=500) + body.set_disc_params(x0=250, y0=250, r0=200) + # At this point, the cache is completely empty + + # The intermediate results used in generating the incidence angle backplane + # are cached, speeding up any future calculations which use these + # intermediate results: + body.get_backplane_img('INCIDENCE') # Takes ~10s to execute + body.get_backplane_img('INCIDENCE') # Executes instantly + body.get_backplane_img('EMISSION') # Executes instantly + + # When any of the disc parameters are changed, the xy <-> radec conversion + # changes so the cache is automatically cleared (as the cached intermediate + # results are no longer valid): + body.set_r0(190) # This automatically clears the cache + body.get_backplane_img('EMISSION') # Takes ~10s to execute + body.get_backplane_img('INCIDENCE') # Executes instantly + +The methods which cache their results include... + +- :func:`planetmapper.BodyXY.get_backplane_img` +- :func:`planetmapper.BodyXY.get_backplane_map` +- :func:`planetmapper.Observation.get_mapped_data` +- :func:`planetmapper.Observation.save_observation` and equivalent option in the GUI +- :func:`planetmapper.Observation.save_mapped_observation` and equivalent option in the GUI + + .. note:: The Python script used to generate all the figures shown on this page can be found `here `_ diff --git a/docs/installation.rst b/docs/installation.rst index 4ba73d3..00e0322 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -17,6 +17,7 @@ To upgrade an existing PlanetMapper installation to the latest version, run: :: pip install planetmapper --upgrade +You can view the release notes for each version `on GitHub `__. First steps =========== diff --git a/planetmapper/progress.py b/planetmapper/progress.py index ff7df88..56e2c32 100644 --- a/planetmapper/progress.py +++ b/planetmapper/progress.py @@ -144,7 +144,7 @@ def __init__(self, n_wavelengths: int, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.n_wavelengths = n_wavelengths self.weights: dict[str, float] = { - 'Observation.get_mapped_data': n_wavelengths * 5, + 'Observation._get_mapped_data': n_wavelengths * 5, 'BodyXY._get_targvec_map': 1, 'BodyXY._get_illumf_map': 5, 'BodyXY._get_radec_map': 10, diff --git a/scratchpad.py b/scratchpad.py index 8574c84..f668500 100644 --- a/scratchpad.py +++ b/scratchpad.py @@ -13,19 +13,19 @@ # body.run_gui() # body.save_mapped_observation('~/Desktop/map_nearest.fits', interpolation='nearest') -# ax = body.plot_wireframe_xy() -# img = np.nanmedian(body.data, axis=0) -# ax.imshow(img, origin='lower') -# plt.show() -# img = np.nan_to_num(img) +ax = body.plot_wireframe_xy() +img = np.nanmedian(body.data, axis=0) +ax.imshow(img, origin='lower') +plt.show() +img = np.nan_to_num(img) -# for interpolation in ('nearest', 'linear', 'quadratic', 'cubic'): -# map_img = body.map_img(img, interpolation=interpolation) -# fig, ax = plt.subplots(figsize=(5,5)) -# body.imshow_map(map_img, ax=ax) -# ax.set_title(interpolation) -# plt.show() +for interpolation in ('nearest', 'linear', 'quadratic', 'cubic'): + map_img = body.map_img(img, interpolation=interpolation) + fig, ax = plt.subplots(figsize=(5,5)) + body.imshow_map(map_img, ax=ax) + ax.set_title(interpolation) + plt.show() -gui = planetmapper.gui.GUI() -gui.run() \ No newline at end of file +# gui = planetmapper.gui.GUI() +# gui.run() \ No newline at end of file