Source code for wolfhece._drowning_manager

"""Drowning-victim companion object for WolfMapViewer.

All drowning-simulation management logic lives here.
``WolfMapViewer`` holds a single instance as ``self._drowning`` and exposes
one-line delegators so that external callers remain unaffected.

Design notes
------------
* ``DrowningManager`` owns the ``active_drowning`` reference, the
  ``mydrownings`` list, and all menu state (``menudrowning`` hierarchy +
  check-item references).
* The wx parent for dialogs and menus is always ``self._viewer``.
* ``menu_build()`` is idempotent — the first call creates and appends the
  "Drowning" menu to the menubar; subsequent calls are no-ops.
* ``on_menu()`` is bound to the wx menu and dispatches all drowning commands.
* ``new_drowning()`` handles both "Create" and "Add" from the object toolbar.
* ``register()`` is called from ``WolfMapViewer.add_object`` to record a
  newly created ``Drowning_victim_Viewer`` instance and update the active
  reference.
"""
from __future__ import annotations

import logging
import os
from typing import TYPE_CHECKING

import wx

from .drowning_victims.drowning_class import Drowning_victim_Viewer
from .PyTranslate import _

if TYPE_CHECKING:
    from .PyDraw import WolfMapViewer

__all__ = ['DrowningManager']


[docs] class DrowningManager: """Companion object that owns drowning-victim simulation state. Instantiated once as ``viewer._drowning = DrowningManager(viewer)`` inside ``WolfMapViewer.__init__``. """ def __init__(self, viewer: 'WolfMapViewer') -> None:
[docs] self._viewer = viewer
# ── Object registry ──────────────────────────────────────────────────
[docs] self.mydrownings: list['Drowning_victim_Viewer'] = []
[docs] self.active: 'Drowning_victim_Viewer | None' = None
# ── Menu state ───────────────────────────────────────────────────────
[docs] self._menu: wx.Menu | None = None
[docs] self._menu_plot: wx.Menu | None = None
# Check-items (created once in menu_build)
[docs] self._item_plot_runs: wx.MenuItem | None = None
[docs] self._item_plot_cells: wx.MenuItem | None = None
[docs] self._item_plot_kde: wx.MenuItem | None = None
# ------------------------------------------------------------------ # Object registration (called by WolfMapViewer.add_object) # ------------------------------------------------------------------
[docs] def register(self, newobj: 'Drowning_victim_Viewer') -> None: """Add *newobj* to the registry and make it the active drowning.""" self.mydrownings.append(newobj) self.active = newobj
# ------------------------------------------------------------------ # Menu construction (idempotent) # ------------------------------------------------------------------
[docs] def menu_build(self) -> None: """Create and append the 'Drowning' menu to the viewer menubar. Safe to call multiple times — only the first call has any effect. """ if self._menu is not None: return viewer = self._viewer self._menu = wx.Menu() self._menu_plot = wx.Menu() item_explore = self._menu.Append( wx.ID_ANY, _("Explore time/index results"), _("Open a dialog to explore time/index results"), ) self._menu.AppendSeparator() self._menu.Append(wx.ID_ANY, _("Plot..."), self._menu_plot) self._item_plot_runs = self._menu_plot.Append(wx.ID_ANY, _("Plot runs positions"), _("Plot runs positions"), kind=wx.ITEM_CHECK) self._item_plot_cells = self._menu_plot.Append(wx.ID_ANY, _("Plot cells positions"), _("Plot the cells where bodies colored as a function of bodies in it"), kind=wx.ITEM_CHECK) self._item_plot_kde = self._menu_plot.Append(wx.ID_ANY, _("Plot KDE"), _("Plot Kernel Density Estimation - Map of probability of presence"), kind=wx.ITEM_CHECK) item_last = self._menu.Append(wx.ID_ANY, _("Read last result"), _("Current view")) item_hotspots = self._menu.Append(wx.ID_ANY, _("Zoom on hotspots"), _("Zoom on areas where you have the highest probability of presence")) self._menu.AppendSeparator() item_bodies = self._menu.Append(wx.ID_ANY, _("Get bodies characteristics"), _("Get a table of the characteristics of all simulated bodies")) item_vpos = self._menu.Append(wx.ID_ANY, _("Vertical position proportion"), _("Get proportion of runs at the surface and at the bottom at selected time")) self._menu.AppendSeparator() item_video = self._menu.Append(wx.ID_ANY, _("Create video..."), _("Video/Movie")) viewer.menubar.Append(self._menu, _('Drowning')) self._menu.Bind(wx.EVT_MENU, self._on_explore, item_explore) self._menu_plot.Bind(wx.EVT_MENU, self._on_plot_runs, self._item_plot_runs) self._menu_plot.Bind(wx.EVT_MENU, self._on_plot_cells, self._item_plot_cells) self._menu_plot.Bind(wx.EVT_MENU, self._on_plot_kde, self._item_plot_kde) self._menu.Bind(wx.EVT_MENU, self._on_last_result, item_last) self._menu.Bind(wx.EVT_MENU, self._on_zoom_hotspots, item_hotspots) self._menu.Bind(wx.EVT_MENU, self._on_bodies, item_bodies) self._menu.Bind(wx.EVT_MENU, self._on_vpos, item_vpos) self._menu.Bind(wx.EVT_MENU, self._on_video, item_video)
# ------------------------------------------------------------------ # Menu event handler # ------------------------------------------------------------------
[docs] def _on_last_result(self, event: wx.MenuEvent) -> None: viewer = self._viewer if self.active is None: logging.warning(_('No active drowning ! -- Please activate a drowning first')) return self.active.read_last_result() viewer.Refresh() viewer._update_tooltip()
[docs] def _on_explore(self, event: wx.MenuEvent) -> None: viewer = self._viewer if self.active is None: logging.warning(_('No active drowning ! -- Please activate a drowning first')) return from ._sim_panels import Drowning_Explorer which = self.active viewer.sim_explorers[which] = Drowning_Explorer( viewer, title=f'Explore drowning results: {self.active.idx}', mapviewer=viewer, sim=which, )
[docs] def _on_plot_runs(self, event: wx.MenuEvent) -> None: viewer = self._viewer if self._item_plot_runs.IsChecked(): self.active.prepare_plot_runs_positions() else: self.active.reset_plot_runs_positions() viewer._update_tooltip() viewer.Refresh()
[docs] def _on_plot_cells(self, event: wx.MenuEvent) -> None: viewer = self._viewer if self._item_plot_cells.IsChecked(): self.active.prepare_plot_cells_positions() else: self.active.reset_plot_cells_positions() viewer._update_tooltip() viewer.Refresh()
[docs] def _on_plot_kde(self, event: wx.MenuEvent) -> None: viewer = self._viewer if self._item_plot_kde.IsChecked(): self.active.prepare_plot_kde() else: self.active.reset_plot_kde() viewer._update_tooltip() viewer.Refresh()
[docs] def _on_zoom_hotspots(self, event: wx.MenuEvent) -> None: from ._sim_panels import Memory_Views, Memory_Views_GUI viewer = self._viewer viewer.memory_views = Memory_Views() self.active.zoom_on_hotspots(viewer.memory_views) viewer._memory_views_gui = Memory_Views_GUI( viewer, _('Memory view manager'), viewer.memory_views, mapviewer=viewer, )
[docs] def _on_bodies(self, event: wx.MenuEvent) -> None: self.active.get_bodies_characteristics()
[docs] def _on_vpos(self, event: wx.MenuEvent) -> None: self.active.get_vertical_position_proportion()
[docs] def _on_video(self, event: wx.MenuEvent) -> None: logging.info(_('Not yet implemented'))
# ------------------------------------------------------------------ # Object creation (called by WolfMapViewer menu handlers) # ------------------------------------------------------------------
[docs] def new_drowning(self, itemlabel: str) -> None: """Handle 'Create a drowning...' and 'Add a drowning result...' items.""" from .drowning_victims.drowning_class import Drowning_victim_Viewer viewer = self._viewer if itemlabel == _('Create a drowning...'): new = Drowning_victim_Viewer(mapviewer=viewer) viewer.add_object(which='drowning', newobj=new, ToCheck=True) elif itemlabel == _('Add a drowning result...'): dialog = wx.DirDialog( None, _("Select the folder containing your drowning"), style=wx.DD_DEFAULT_STYLE, ) if dialog.ShowModal() != wx.ID_OK: dialog.Destroy() return filedir = dialog.GetPath() dialog.Destroy() if not os.path.exists(os.path.join(filedir, "Results.npz")): logging.error(_("The selected folder does not contain any Results.npz")) return new = Drowning_victim_Viewer(mapviewer=viewer, filedir=filedir) new.file_drowning = 1 viewer.add_object(which='drowning', newobj=new, ToCheck=True) new.load_results() new.time_id = len(new.wanted_time) - 2 new.init_plot() self.menu_build()