class_docstr: Framewise evaluator.
  
  Evaluates pianorolls one frame at a time. That is, the model is judged for its
  prediction of entire frames at a time, conditioning on its own samples rather
  than the ground truth of other instruments/pitches in the same frame.
  
  The frames are evaluated in random order, and within each frame the
  instruments/pitches are evaluated in random order.
code: def __call__(self, pianoroll):
      tt, pp, ii = pianoroll.shape
      assert self.separate_instruments or ii == 1
      dd = ii if self.separate_instruments else pp
  
      # Compile a batch with each frame being an example.
      bb = tt
      xs = np.tile(pianoroll[None], [bb, 1, 1, 1])
  
      ts, ds = self.draw_ordering(tt, dd)
  
      # Set up sequence of masks to predict the first (according to ordering)
      # instrument for each frame
      mask = []
      mask_scratch = np.ones([tt, pp, ii], dtype=np.float32)
      for j, (t, d) in enumerate(zip(ts, ds)):
          # When time rolls over, reveal the entire current frame for purposes of
          # predicting the next one.
          if j % dd != 0:
              continue
          mask.append(mask_scratch.copy())
          mask_scratch[t, :, :] = 0
      assert np.allclose(mask_scratch, 0)
      del mask_scratch
      mask = np.array(mask)
  
      lls = np.zeros([tt, dd], dtype=np.float32)
  
      # We can't parallelize within the frame, as we need the predictions of
      # some of the other instruments.
      # Hence we outer loop over the instruments and parallelize across frames.
      xs_scratch = xs.copy()
      for d_idx in range(dd):
          # Call out to the model to get predictions for the first instrument
          # at each time step.
          pxhats = self.predictor(xs_scratch, mask)
  
          t, d = ts[d_idx::dd], ds[d_idx::dd]
          assert len(t) == bb and len(d) == bb
  
          # Write in predictions and update mask.
          if self.separate_instruments:
              xs_scratch[np.arange(bb), t, :, d] = np.eye(pp)[
                  np.argmax(pxhats[np.arange(bb), t, :, d], axis=1)
              ]
              mask[np.arange(bb), t, :, d] = 0
              # Every example in the batch sees one frame more than the previous.
              assert np.allclose(
                  (1 - mask).sum(axis=(1, 2, 3)),
                  [
                      (k * dd + d_idx + 1) * pp
                      for k in range(mask.shape[0])
                  ],
              )
          else:
              xs_scratch[np.arange(bb), t, d, :] = (
                  pxhats[np.arange(bb), t, d, :] > 0.5
              )
              mask[np.arange(bb), t, d, :] = 0
              # Every example in the batch sees one frame more than the previous.
              assert np.allclose(
                  (1 - mask).sum(axis=(1, 2, 3)),
                  [
                      (k * dd + d_idx + 1) * ii
                      for k in range(mask.shape[0])
                  ],
              )
  
          self._update_lls(lls, xs, pxhats, t, d)
  
      # conjunction over notes within frames; frame is the unit of prediction
      return lls.sum(axis=1)
  
imports: import time
  from magenta.models.coconet import lib_tfutil
  from magenta.models.coconet import lib_util
  import numpy
  from scipy.misc import logsumexp
  import tensorflow.compat.v1
label_desc: Use image node_id 1 for calling the FrameEvaluator obj's underlying member method code with example usage: obj.__call__(pianoroll) and returns: lls