def apply()

in moonlight/structure/section_barlines.py [0:0]


  def apply(self, page):
    """Detects thick section barlines from the connected components.

    These should be tall components that start and end near the start and end
    of two (possibly different) staves. We use the standard barlines logic to
    assign components to the nearest start and end staff. We filter for
    candidate barlines, whose start and end are sufficiently close to the
    expected values. We then filter again by whether the component width is
    within the expected values for section barlines.

    For each staff system, we take the section barlines that match exactly that
    system's staves. Any standard barlines that are too close to a new section
    barline are removed, and we merge the existing standard barlines with the
    new section barlines.

    Args:
      page: A Page message.

    Returns:
      The same Page message, with new section barlines added.
    """
    component_center_x = np.mean(
        self.components[:, [COLUMNS.X0, COLUMNS.X1]], axis=1).astype(int)
    # Take section barline candidates, whose start and end y values are close
    # enough to the staff start and end ys.
    component_is_candidate, candidate_start_staff, candidate_end_staff = (
        barlines.assign_barlines_to_staves(
            barline_x=component_center_x,
            barline_y0=self.components[:, COLUMNS.Y0],
            barline_y1=self.components[:, COLUMNS.Y1],
            staff_detector=self.staff_detector))
    candidates = self.components[component_is_candidate]
    candidate_center_x = component_center_x[component_is_candidate]
    del component_center_x

    # Filter again by the expected section barline width.
    component_width = candidates[:, COLUMNS.X1] - candidates[:, COLUMNS.X0]
    component_width_ok = np.logical_and(
        self._section_min_width() <= component_width,
        component_width <= self._section_max_width(candidate_start_staff))
    candidates = candidates[component_width_ok]
    candidate_center_x = candidate_center_x[component_width_ok]
    candidate_start_staff = candidate_start_staff[component_width_ok]
    candidate_end_staff = candidate_end_staff[component_width_ok]

    # For each existing staff system, consider only the candidates that match
    # exactly the system's start and end staves.
    start_staff = 0
    for system in page.system:
      staffline_distance = np.median(
          [staff.staffline_distance for staff in system.staff]).astype(int)
      candidate_covers_staff_system = np.logical_and(
          candidate_start_staff == start_staff,
          candidate_end_staff + 1 == start_staff + len(system.staff))
      # Calculate the x coordinates of all section barlines to keep.
      section_bar_x = candidate_center_x[candidate_covers_staff_system]
      # Extract the existing bar x coordinates and types for merging.
      existing_bar_type = {bar.x: bar.type for bar in system.bar}
      existing_bars = np.asarray([bar.x for bar in system.bar])
      # Merge the existing barlines and section barlines.
      if existing_bars.size and section_bar_x.size:
        # Filter the existing bars by whether they are far enough from a new
        # section barline. Section barlines override the existing standard
        # barlines.
        existing_bars_ok = np.greater(
            np.min(
                np.abs(existing_bars[:, None] - section_bar_x[None, :]),
                axis=1), staffline_distance * 4)
        existing_bars = existing_bars[existing_bars_ok]

      # Merge the existing barlines which we kept, and the new section barlines
      # (which are assumed to be type END_BAR), in sorted order.
      bars = sorted(
          [Bar(x=x, type=existing_bar_type[x]) for x in existing_bars] +
          [Bar(x=x, type=Bar.END_BAR) for x in section_bar_x],
          key=lambda bar: bar.x)
      # Update the staff system.
      system.ClearField('bar')
      system.bar.extend(bars)

      start_staff += len(system.staff)
    return page