          summary = event.summary.value[0]
          if summary.tag in ("batch_loss", "batch_total"):  # Pre tf2.3 totals were "batch_total"
              step["timestamp"] = event.wall_time
              return step
          self._process_label(summary.tag)
          step.setdefault("loss", list()).append(summary.simple_value)
          return step
  
      def _process_label(self, label):
          """ Check if the incoming loss label exists in :attr:`_loss_labels` and if not, add it.
 
</s> remove 
    def _process_label(self, label):
        """ Check if the incoming loss label exists in :attr:`_loss_labels` and if not, add it.

        Parameters
        ----------
        label: str
            The label for the currently processing loss event
        """
        # tf2.3 stopped respecting loss names in tensorboard callback so rewrite
        lbl_split = label.replace("batch_", "").replace("_loss", "").split("_")
        if lbl_split[-1] in ("a", "b"):
            lbl = f"face_{lbl_split[-1]}"
        elif "both" in lbl_split:  # Combined decoders don't get face names
            lbl = f"face_{'b' if lbl_split[-1] == '1' else 'a'}"
        else:
            lbl = f"mask_{lbl_split[-2]}"  # TODO may not work for combined decoders
        if lbl not in self._loss_labels:
            logger.debug("Adding loss label: (original: '%s', rewritten: '%s')", label, lbl)
            self._loss_labels.append(lbl)
 </s> add  </s> remove         ids = sorted(data)

        if not all(len(step) == length_loss for step in loss):
            indices = [loss.index(step) for step in loss if len(step) != length_loss]
            logger.debug("Truncated loss found. loss count: %s, truncated indices: %s",
                         len(loss), indices)
            for idx in reversed(indices):  # Backwards so we can delete indices and still traverse
                if is_live:
                    self._set_carry_over(ids[idx], data[ids[idx]])
                logger.debug("Removing truncated loss: (timestamp: %s, loss: %s)",
                             timestamps[idx], loss[idx])
                del loss[idx]
                del timestamps[idx]

        return timestamps, loss
 </s> add  </s> remove     def _process_event(self, event, step):
 </s> add     def _parse_outputs(self, event):
        """ Parse the outputs from the stored model structure for mapping loss names to
        model outputs.

        Loss names are added to :attr:`_loss_labels`

        Parameters
        ----------
        event: :class:`tensorflow.core.util.event_pb2`
            The event data containing the keras model structure to be parsed
        """
        serializer = get_serializer("json")
        struct = event.summary.value[0].tensor.string_val[0]
        outputs = np.array(serializer.unmarshal(struct)["config"]["output_layers"])
        logger.debug("Obtained model outputs: %s, shape: %s", outputs, outputs.shape)
        if outputs.ndim == 2:  # Insert extra dimension for non learn mask models
            outputs = np.expand_dims(outputs, axis=1)
            logger.debug("Expanded dimensions for non-learn_mask model. outputs: %s, shape: %s",
                         outputs, outputs.shape)
        for side_outputs, side in zip(outputs, ("a", "b")):
            logger.debug("side: '%s', outputs: '%s'", side, side_outputs)
            for idx in range(len(side_outputs)):
                # First output is always face. Subsequent outputs are masks
                loss_name = f"face_{side}" if idx == 0 else f"mask_{side}"
                loss_name = loss_name if idx < 2 else f"{loss_name}_{idx}"
                if loss_name not in self._loss_labels:
                    logger.debug("Adding loss name: '%s'", loss_name)
                    self._loss_labels.append(loss_name)
        logger.debug("Collated loss labels: %s", self._loss_labels)

    @classmethod
    def _process_event(cls, event, step): </s> remove                 data[event.step] = self._process_event(event, data.get(event.step, dict()))
 </s> add                 if event.summary.value[0].tag == "keras":
                    self._parse_outputs(event)
                if event.summary.value[0].tag.startswith("batch_"):
                    data[event.step] = self._process_event(event, data.get(event.step, dict())) </s> remove     def _set_carry_over(self, index, data):
        """ For live data, set carried over data from a partial Tensorflow Event read to
        :attr:`_carry_over`
 </s> add         if len(loss[-1]) != length_loss:
            logger.debug("Truncated loss found. loss count: %s", len(loss))
            idx = sorted(data)[-1]
            if is_live:
                logger.debug("Setting carried over data: %s", data)
                self._carry_over[idx] = data[idx]
            logger.debug("Removing truncated loss: (timestamp: %s, loss: %s)",
                         timestamps[-1], loss[-1])
            del loss[-1]
            del timestamps[-1]
          summary = event.summary.value[0]
          if summary.tag in ("batch_loss", "batch_total"):  # Pre tf2.3 totals were "batch_total"
              step["timestamp"] = event.wall_time
              return step

          step.setdefault("loss", list()).append(summary.simple_value)
          return step
  
      def _process_label(self, label):
          """ Check if the incoming loss label exists in :attr:`_loss_labels` and if not, add it.
keep keep keep keep replace keep keep keep keep keep
 GUI - Bugfixes
  - Swallow OSErrors when failing to load preview image
  - Fix event_reader mapping for model output to loss names
  - stats - Ensure that _tb_logs exists prior to calling stop training 
lib/gui/analysis/event_reader.py