# Path: deluca/lung/core.py
# class BreathWaveform(deluca.Obj):
#   r"""Waveform generator with shape |‾\_."""
#   peep: float = deluca.field(5., jaxed=False)
#   pip: float = deluca.field(35., jaxed=False)
#   bpm: int = deluca.field(20, jaxed=False)
#   fp: jnp.array = deluca.field(jaxed=False)
#   xp: jnp.array = deluca.field(jaxed=False)
#   in_bounds: Tuple[int, int] = deluca.field((0, 1), jaxed=False)
#   ex_bounds: Tuple[int, int] = deluca.field((2, 4), jaxed=False)
#   period: float = deluca.field(jaxed=False)
#   dt: float = deluca.field(DEFAULT_DT, jaxed=False)
#   dtype: jax._src.numpy.lax_numpy._ScalarMeta = deluca.field(
#       jnp.float32, jaxed=False)
# 
#   def setup(self):
#     if self.fp is None:
#       self.fp = jnp.array([self.pip, self.pip, self.peep, self.peep, self.pip])
#     if self.xp is None:
#       self.xp = jnp.array(DEFAULT_XP)
#     self.period = 60 / self.bpm
# 
#   def at(self, t):
# 
#     @functools.partial(jax.jit, static_argnums=(3,))
#     def static_interp(t, xp, fp, period):
#       return jnp.interp(t, xp, fp, period=period)
# 
#     return static_interp(t, self.xp, self.fp, self.period).astype(self.dtype)
# 
#   def elapsed(self, t):
#     return t % self.period
# 
#   def is_in(self, t):
#     return self.elapsed(t) <= self.xp[self.in_bounds[1]]
# 
#   def is_ex(self, t):
#     return not self.is_in(t)


import pickle
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from deluca.lung.core import BreathWaveform
      use_cached: whether or not to use self.cached_breaths
      breath_length: length of a breath
    Returns:
      list of endpoints so that u_out[lo:hi] == 1
    """

    if not use_cached or not hasattr(self, "cached_breaths"):
      d_u_out = np.diff(self.u_out, prepend=1)

      starts = np.where(d_u_out == -1)[0]
      ends = np.where(d_u_out == 1)[0]

      self.cached_breaths = list(zip(starts, ends))

    # Return a single breath of the entire length
    return self.cached_breaths or [(0, breath_length)]

  ###########################################################################
  # Metric methods
  ###########################################################################
  def losses_per_breath(self,
                        target,
                        clip=(0, None),
                        loss_fn=None,
                        breath_length=29):
    """compute losses per breath."""
    # computes trapezoidally integrated loss per inferred breath
    loss_fn = loss_fn or np.square

    # handle polymorphic targets

# Path: deluca/lung/core.py
# class BreathWaveform(deluca.Obj):
#   r"""Waveform generator with shape |‾\_."""
#   peep: float = deluca.field(5., jaxed=False)
#   pip: float = deluca.field(35., jaxed=False)
#   bpm: int = deluca.field(20, jaxed=False)
#   fp: jnp.array = deluca.field(jaxed=False)
#   xp: jnp.array = deluca.field(jaxed=False)
#   in_bounds: Tuple[int, int] = deluca.field((0, 1), jaxed=False)
#   ex_bounds: Tuple[int, int] = deluca.field((2, 4), jaxed=False)
#   period: float = deluca.field(jaxed=False)
#   dt: float = deluca.field(DEFAULT_DT, jaxed=False)
#   dtype: jax._src.numpy.lax_numpy._ScalarMeta = deluca.field(
#       jnp.float32, jaxed=False)
# 
#   def setup(self):
#     if self.fp is None:
#       self.fp = jnp.array([self.pip, self.pip, self.peep, self.peep, self.pip])
#     if self.xp is None:
#       self.xp = jnp.array(DEFAULT_XP)
#     self.period = 60 / self.bpm
# 
#   def at(self, t):
# 
#     @functools.partial(jax.jit, static_argnums=(3,))
#     def static_interp(t, xp, fp, period):
#       return jnp.interp(t, xp, fp, period=period)
# 
#     return static_interp(t, self.xp, self.fp, self.period).astype(self.dtype)
# 
#   def elapsed(self, t):
#     return t % self.period
# 
#   def is_in(self, t):
#     return self.elapsed(t) <= self.xp[self.in_bounds[1]]
# 
#   def is_ex(self, t):
#     return not self.is_in(t)


# Path: deluca/lung/utils/data/analyzer.py
import pickle
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from deluca.lung.core import BreathWaveform

      use_cached: whether or not to use self.cached_breaths
      breath_length: length of a breath
    Returns:
      list of endpoints so that u_out[lo:hi] == 1
    """

    if not use_cached or not hasattr(self, "cached_breaths"):
      d_u_out = np.diff(self.u_out, prepend=1)

      starts = np.where(d_u_out == -1)[0]
      ends = np.where(d_u_out == 1)[0]

      self.cached_breaths = list(zip(starts, ends))

    # Return a single breath of the entire length
    return self.cached_breaths or [(0, breath_length)]

  ###########################################################################
  # Metric methods
  ###########################################################################
  def losses_per_breath(self,
                        target,
                        clip=(0, None),
                        loss_fn=None,
                        breath_length=29):
    """compute losses per breath."""
    # computes trapezoidally integrated loss per inferred breath
    loss_fn = loss_fn or np.square

    # handle polymorphic targets

    if isinstance(target, BreathWaveform):