class_docstr: Module stitches histograms by date
docstr: Stitching histograms by first axis.
  
  Histograms in hists_delta are added to those in hists_basis. Bins are summed or replaced, set this with 'mode'.
  'time_axis' specifies the name of the first axis. If the time_axis is not found, it is created, and
  histograms get inserted the time_bin_idx values.
  
  :param str mode: options for histogram stitching: "add" or "replace" bins. default is "add".
  :param dict hists_basis: input dict of basis histograms.
  :param dict hists_delta: delta dict of histograms to add to hists_basis.
  :param list hists_list: alternative for [hists_basis, hists_delta, etc]. can have multiple deltas.
      first item in list is taken as hists_basis (optional)
  :param str time_axis: time-axis used for stitching (optional).
  :param time_bin_idx: (list of) time-value(s) at which to insert hist-deltas into hist-basis.
  :return: dict with stitched histograms. If stitching is not possible, returns hists_basis.
docstr_tok: ["Stitching","histograms","by","first","axis",".","Histograms","in","hists_delta","are","added","to","those","in","hists_basis",".","Bins","are","summed","or","replaced",",","set","this","with","'mode","'",".","'time_axis","'","specifies","the","name","of","the","first","axis",".","If","the","time_axis","is","not","found",",","it","is","created",",","and","histograms","get","inserted","the","time_bin_idx","values",".",":","param","str","mode",":","options","for","histogram","stitching",":","``","add","''","or","``","replace","''","bins",".","default","is","``","add","''",".",":","param","dict","hists_basis",":","input","dict","of","basis","histograms",".",":","param","dict","hists_delta",":","delta","dict","of","histograms","to","add","to","hists_basis",".",":","param","list","hists_list",":","alternative","for","[","hists_basis",",","hists_delta",",","etc","]",".","can","have","multiple","deltas",".","first","item","in","list","is","taken","as","hists_basis","(","optional",")",":","param","str","time_axis",":","time-axis","used","for","stitching","(","optional",")",".",":","param","time_bin_idx",":","(","list","of",")","time-value","(","s",")","at","which","to","insert","hist-deltas","into","hist-basis",".",":","return",":","dict","with","stitched","histograms",".","If","stitching","is","not","possible",",","returns","hists_basis","."]
code: def stitch_histograms(
      self,
      mode=None,
      hists_basis=None,
      hists_delta=None,
      hists_list=None,
      time_axis: str = "",
      time_bin_idx=None,
  ):
      """Stitching histograms by first axis.
  
      Histograms in hists_delta are added to those in hists_basis. Bins are summed or replaced, set this with 'mode'.
      'time_axis' specifies the name of the first axis. If the time_axis is not found, it is created, and
      histograms get inserted the time_bin_idx values.
  
      :param str mode: options for histogram stitching: "add" or "replace" bins. default is "add".
      :param dict hists_basis: input dict of basis histograms.
      :param dict hists_delta: delta dict of histograms to add to hists_basis.
      :param list hists_list: alternative for [hists_basis, hists_delta, etc]. can have multiple deltas.
          first item in list is taken as hists_basis (optional)
      :param str time_axis: time-axis used for stitching (optional).
      :param time_bin_idx: (list of) time-value(s) at which to insert hist-deltas into hist-basis.
      :return: dict with stitched histograms. If stitching is not possible, returns hists_basis.
      """
      # set stitching mode
      mode = (
          mode
          if isinstance(mode, str) and mode in self.allowed_modes
          else self.mode
      )
      time_axis = (
          time_axis
          if isinstance(time_axis, str) and len(time_axis) > 0
          else self.time_axis
      )
      time_bin_idx = (
          time_bin_idx
          if isinstance(time_bin_idx, (str, int, list, tuple))
          else self.time_bin_idx
      )
      hists_list = hists_list or []
  
      if time_bin_idx is not None:
          if isinstance(time_bin_idx, (str, int)):
              time_bin_idx = [time_bin_idx]
          if not isinstance(time_bin_idx, (list, tuple)):
              raise TypeError(
                  "time_bin_idx should be a (list of) ordered integer(s) or string(s)"
              )
          dts = [type(tv) for tv in time_bin_idx]
          if not dts.count(dts[0]) == len(dts):
              raise TypeError(
                  f"time_bin_idxs have inconsistent datatypes: {dts}"
              )
  
      # basic checks and conversions
      if isinstance(hists_basis, dict) and len(hists_basis) > 0:
          hists_list.insert(0, hists_basis)
      if isinstance(hists_delta, dict) and len(hists_delta) > 0:
          hists_list.append(hists_delta)
      elif isinstance(hists_delta, list):
          for hd in hists_delta:
              if isinstance(hd, dict) and len(hd) > 0:
                  hists_list.append(hd)
      if not isinstance(hists_list, list) or len(hists_list) == 0:
          raise TypeError(
              "hists_list should be a filled list of histogram dictionaries."
          )
      for hd in hists_list:
          if not isinstance(hd, dict) or len(hd) == 0:
              raise TypeError(
                  "hists_list should be a list of filled histogram dictionaries."
              )
      hists_basis = hists_list[0]
      hists_delta = hists_list[1:]
  
      # determine possible features, used for comparisons below
      if isinstance(time_axis, str) and len(time_axis) > 0:
          self.feature_begins_with = f"{time_axis}:"
  
      # Three possibilities
      # 1. if there are no basis hists starting with "time_axis:", assume that this the very first batch.
      # 2. if delta(s) do not start with "time_axis:", assume that this is a set of batches without time_axis
      # 3. deltas already have a time-axis. start stitching of histograms by using bins.update()
  
      features_basis = self.get_features(
          list(hists_basis.keys())
      )  # basis keys that start with time_axis
      if len(features_basis) > 0:
          # pruning of keys not starting with time_axis
          hists_list[0] = hists_basis = {
              k: h
              for k, h in hists_basis.items()
              if k in features_basis
          }
  
      # 1. if there are no basis hists starting with "time_axis:", assume that this the very first batch.
      if (
          len(features_basis) == 0
          and time_axis
          and len(hists_basis) > 0
          and time_axis
      ):
          if time_bin_idx is None:
              self.logger.info(
                  f'Inserting basis histograms in axis "{time_axis}" at bin index 0.'
              )
              time_bin_idx = [0]
          hists_basis_new = {}
          for k, hist in hists_basis.items():
              feature = f"{time_axis}:{k}"
              self.logger.debug(f'Now creating histogram "{feature}"')
              hists_basis_new[
                  feature
              ] = self._create_hist_with_time_axis(
                  hist, time_bin_idx[0]
              )
          # reset hists_basis
          features_basis = self.get_features(
              list(hists_basis_new.keys())
          )
          hists_list[0] = hists_basis = hists_basis_new
          time_bin_idx = time_bin_idx[1:]
  
      # -- is there a need to continue? There need to be overlapping hists.
      delta_keys = set()
      if len(hists_delta) > 0:
          delta_keys = set(hists_delta[0].keys())
          for hd in hists_delta[1:]:
              delta_keys &= set(hd.keys())
      if (
          len(hists_delta) == 0
          or len(delta_keys) == 0
          or len(hists_basis) == 0
      ):
          self.logger.debug(
              "No overlapping delta features. returning pruned hists_basis."
          )
          return hists_basis
  
      stitched = {}
  
      # 2. if delta(s) do not start with "time_axis:", assume that this is a set of batches without time_axis
      features_delta = self.get_features(
          list(delta_keys)
      )  # delta keys that start with time_axis
      if (
          len(features_basis) > 0
          and len(features_delta) == 0
          and len(delta_keys) > 0
          and time_axis
      ):
          if time_bin_idx is None or len(time_bin_idx) == 0:
              time_bin_idx = self._generate_time_bin_idx(
                  hists_basis,
                  features_basis,
                  time_axis,
                  len(hists_delta),
              )
              if time_bin_idx is None:
                  raise ValueError(
                      "Request to insert delta hists but time_bin_idx not set or deductable. Please set manually."
                  )
              self.logger.info(
                  f'Inserting delta histograms in axis "{time_axis}" at bin indices {time_bin_idx}.'
              )
          if len(hists_delta) != len(time_bin_idx):
              raise ValueError(
                  "Not enough time_bin_idxs set to insert delta histograms."
              )
          for key in list(delta_keys):
              feature = f"{time_axis}:{key}"
              if feature not in features_basis:
                  continue
              self.logger.debug(
                  f'Now inserting into histogram "{feature}"'
              )
              hist_list = [hd[key] for hd in hists_delta]
              stitched[feature] = self._insert_hists(
                  hists_basis[feature], hist_list, time_bin_idx, mode
              )
          # add basis hists without any overlap
          for feature in features_basis:
              if feature not in stitched:
                  stitched[feature] = hists_basis[feature]
          return stitched
  
      # 3. deltas already have a time-axis. start stitching of histograms by using bins.update()
      overlapping_keys = set(hists_list[0].keys())
      for hd in hists_list[1:]:
          overlapping_keys &= set(hd.keys())
      features_overlap = self.get_features(
          list(overlapping_keys)
      )  # all overlapping keys that start with time_axis
      if len(features_overlap) == 0:
          # no overlap, then return basis histograms
          self.logger.warning(
              "No overlapping basis-delta features. returning pruned hists_basis."
          )
          return hists_basis
      for feature in features_overlap:
          self.logger.debug(f'Now stitching histograms "{feature}"')
          hist_list = [hd[feature] for hd in hists_list]
          stitched[feature] = self._stitch_by_update(mode, hist_list)
      # add basis hists without any overlap
      for feature in features_basis:
          if feature not in stitched:
              stitched[feature] = hists_basis[feature]
      return stitched
  
file_path: popmon/popmon/stitching/hist_stitcher.py
imports: import histogrammar
  import numpy
  from popmon.analysis.hist_numpy import assert_similar_hists
  from popmon.base import Module
label_desc: Use image node_id 4 for calling the HistStitcher obj's underlying member method code with example usage: obj.stitch_histograms(mode, hists_basis, hists_delta, hists_list, time_axis, time_bin_idx) and returns: stitched, hists_basis, stitched, hists_basis