              update["timestamp"] = carry_over["timestamp"] if timestamp is None else timestamp
              update.setdefault("loss", []).extend(carry_over.get("loss", []))
              logger.debug("Merged carry over data: %s", update)
  
      def _process_data(self, data, timestamps, loss, is_live):
          """ Process live update data.
  
          Live data requires different processing as often we will only have partial data for the
          current step, so we need to cache carried over partial data to be picked up at the next
          query. In addition to this, if training is unexpectedly interrupted, there may also be
 
</s> remove             timestamp = update.get("timestamp")
            update["timestamp"] = carry_over["timestamp"] if timestamp is None else timestamp
            update.setdefault("loss", []).extend(carry_over.get("loss", []))
 </s> add             timestamp = update.timestamp
            update.timestamp = carry_over.timestamp if not timestamp else timestamp
            update.loss = carry_over.loss + update.loss </s> remove                          timestamps[-1], loss[-1])
            del loss[-1]
            del timestamps[-1]
 </s> add                          l_timestamps[-1], loss[-1])
            del l_loss[-1]
            del l_timestamps[-1] </s> remove         timestamps: tuple
            The raw timestamps for for the latest live query, including any partial reads
        loss: tuple
            The raw loss for for the latest live query, including any partial reads
 </s> add  </s> remove         times, loss = zip(*[(data[idx].get("timestamp"), data[idx].get("loss", []))
                            for idx in sorted(data)])
        times, loss = self._process_data(data, times, loss, is_live)
 </s> add         times, loss = self._process_data(data, is_live) </s> remove     def _collect_carry_over(self, data):
 </s> add     def _collect_carry_over(self, data: Dict[int, EventData]) -> None:
    def _process_data(self,
                      data: Dict[int, EventData],
                      is_live: bool) -> Tuple[List[float], List[List[float]]]:
              update["timestamp"] = carry_over["timestamp"] if timestamp is None else timestamp
              update.setdefault("loss", []).extend(carry_over.get("loss", []))
              logger.debug("Merged carry over data: %s", update)
  
    def _process_data(self,
                      data: Dict[int, EventData],
                      is_live: bool) -> Tuple[List[float], List[List[float]]]:
          """ Process live update data.
  
          Live data requires different processing as often we will only have partial data for the
          current step, so we need to cache carried over partial data to be picked up at the next
          query. In addition to this, if training is unexpectedly interrupted, there may also be
keep keep keep keep replace keep keep keep keep keep
 Bugfixes
  - plugin.train.unbalanced - decoder b
  - gui.stats - crash when log folder deleted
  - setup.py - install gcc=12.1.0 on linux (scipy dep)
  - setup.py - don't test ROCm unless valid
  - typofix - cli_args extract singleprocess help
typing
  - lib.gui.analysis.event_reader
  - lib.gpu_stats.directml - make platform specific
unit tests
  - lib.gui.analysis.event_reader 
lib/gui/analysis/event_reader.py