text: import numpy as np
  import warnings
  import json
  import numpy as np
  from ..utils import ordinal_str
  import random
  import string
  try:
      import matplotlib.pyplot as pl
  except ImportError:
      warnings.warn("matplotlib could not be loaded!")
  try:
      from IPython.core.display import display, HTML
  except ImportError:
      warnings.warn("IPython could not be loaded!")
  from . import colors
  from ..utils._legacy import kmeans
  
  # .shape[0] messes up pylint a lot here
  # pylint: disable=unsubscriptable-object
  
  
  def image(shap_values, pixel_values=None, labels=None, width=20, aspect=0.2, hspace=0.2, labelpad=None, show=True):
      """ Plots SHAP values for image inputs.
  
      Parameters
      ----------
      shap_values : [numpy.array]
          List of arrays of SHAP values. Each array has the shap (# samples x width x height x channels), and the
          length of the list is equal to the number of model outputs that are being explained.
  
      pixel_values : numpy.array
          Matrix of pixel values (# samples x width x height x channels) for each image. It should be the same
          shape as each array in the shap_values list of arrays.
  
      labels : list
          List of names for each of the model outputs that are being explained. This list should be the same length
          as the shap_values list.
  
      width : float
          The width of the produced matplotlib plot.
  
      labelpad : float
          How much padding to use around the model output labels.
  
      show : bool
          Whether matplotlib.pyplot.show() is called before returning. Setting this to False allows the plot
          to be customized further after it has been created.
      """
  
      # support passing an explanation object
      if str(type(shap_values)).endswith("Explanation'>"):
          shap_exp = shap_values
          # feature_names = [shap_exp.feature_names]
          # ind = 0
          if len(shap_exp.output_dims) == 1:
              shap_values = [shap_exp.values[..., i] for i in range(shap_exp.values.shape[-1])]
          elif len(shap_exp.output_dims) == 0:
              shap_values = shap_exp.values
          else:
              raise Exception("Number of outputs needs to have support added!! (probably a simple fix)")
          if pixel_values is None:
              pixel_values = shap_exp.data
          if labels is None:
              labels = shap_exp.output_names
  
      multi_output = True
      if not isinstance(shap_values, list):
          multi_output = False
          shap_values = [shap_values]
  
      if len(shap_values[0].shape) == 3:
          shap_values = [v.reshape(1, *v.shape) for v in shap_values]
          pixel_values = pixel_values.reshape(1, *pixel_values.shape)
  
      # make sure labels
      if labels is not None:
          labels = np.array(labels)
          if labels.shape[0] != shap_values[0].shape[0] and labels.shape[0] == len(shap_values):
              labels = np.tile(np.array([labels]), shap_values[0].shape[0])
          assert labels.shape[0] == shap_values[0].shape[0], "Labels must have same row count as shap_values arrays!"
          if multi_output:
              assert labels.shape[1] == len(shap_values), "Labels must have a column for each output in shap_values!"
          else:
              assert len(labels.shape) == 1, "Labels must be a vector for single output shap_values."
  
      label_kwargs = {} if labelpad is None else {'pad': labelpad}
  
      # plot our explanations
      x = pixel_values
      fig_size = np.array([3 * (len(shap_values) + 1), 2.5 * (x.shape[0] + 1)])
      if fig_size[0] > width:
          fig_size *= width / fig_size[0]
      fig, axes = pl.subplots(nrows=x.shape[0], ncols=len(shap_values) + 1, figsize=fig_size)
      if len(axes.shape) == 1:
          axes = axes.reshape(1,axes.size)
      for row in range(x.shape[0]):
          x_curr = x[row].copy()
  
          # make sure we have a 2D array for grayscale
          if len(x_curr.shape) == 3 and x_curr.shape[2] == 1:
              x_curr = x_curr.reshape(x_curr.shape[:2])
          if x_curr.max() > 1:
              x_curr /= 255.
  
          # get a grayscale version of the image
          if len(x_curr.shape) == 3 and x_curr.shape[2] == 3:
              x_curr_gray = (0.2989 * x_curr[:,:,0] + 0.5870 * x_curr[:,:,1] + 0.1140 * x_curr[:,:,2]) # rgb to gray
              x_curr_disp = x_curr
          elif len(x_curr.shape) == 3:
              x_curr_gray = x_curr.mean(2)
  
              # for non-RGB multi-channel data we show an RGB image where each of the three channels is a scaled k-mean center
              flat_vals = x_curr.reshape([x_curr.shape[0]*x_curr.shape[1], x_curr.shape[2]]).T
              flat_vals = (flat_vals.T - flat_vals.mean(1)).T
              means = kmeans(flat_vals, 3, round_values=False).data.T.reshape([x_curr.shape[0], x_curr.shape[1], 3])
              x_curr_disp = (means - np.percentile(means, 0.5, (0,1))) / (np.percentile(means, 99.5, (0,1)) - np.percentile(means, 1, (0,1)))
              x_curr_disp[x_curr_disp > 1] = 1
              x_curr_disp[x_curr_disp < 0] = 0
          else:
              x_curr_gray = x_curr
              x_curr_disp = x_curr
  
          axes[row,0].imshow(x_curr_disp, cmap=pl.get_cmap('gray'))
          axes[row,0].axis('off')
          if len(shap_values[0][row].shape) == 2:
              abs_vals = np.stack([np.abs(shap_values[i]) for i in range(len(shap_values))], 0).flatten()
          else:
              abs_vals = np.stack([np.abs(shap_values[i].sum(-1)) for i in range(len(shap_values))], 0).flatten()
          max_val = np.nanpercentile(abs_vals, 99.9)
          for i in range(len(shap_values)):
              if labels is not None:
                  axes[row,i+1].set_title(labels[row,i], **label_kwargs)
              sv = shap_values[i][row] if len(shap_values[i][row].shape) == 2 else shap_values[i][row].sum(-1)
              axes[row,i+1].imshow(x_curr_gray, cmap=pl.get_cmap('gray'), alpha=0.15, extent=(-1, sv.shape[1], sv.shape[0], -1))
              im = axes[row,i+1].imshow(sv, cmap=colors.red_transparent_blue, vmin=-max_val, vmax=max_val)
              axes[row,i+1].axis('off')
      if hspace == 'auto':
          fig.tight_layout()
      else:
          fig.subplots_adjust(hspace=hspace)
      cb = fig.colorbar(im, ax=np.ravel(axes).tolist(), label="SHAP value", orientation="horizontal", aspect=fig_size[0]/aspect)
      cb.outline.set_visible(False)
      if show:
          pl.show()
  
  
  def image_to_text(shap_values):
      """ Plots SHAP values for image inputs with test outputs.
  
      Parameters
      ----------
      shap_values : [numpy.array]
          List of arrays of SHAP values. Each array has the shap (# width x height x channels x num output tokens). One array
          for each sample
  
      """
  
      if len(shap_values.values.shape) == 5:
          for i in range(shap_values.values.shape[0]):
              display(HTML(f"<br/><b>{ordinal_str(i)} instance:</b><br/>"))
              image_to_text(shap_values[i])
          
          return
      
  
      uuid = ''.join(random.choices(string.ascii_lowercase, k=20))
      
      # creating input html tokens
      
      model_output = shap_values.output_names
  
      output_text_html = ''
  
      for i in range(model_output.shape[0]):
          output_text_html += "<div style='display:inline; text-align:center;'>" \
                  + f"<div id='{uuid}_output_flat_value_label_"+ str(i) +"'" \
                  + "style='display:none;color: #999; padding-top: 0px; font-size:12px;'>" \
                  + "</div>" \
                  + f"<div id='{uuid}_output_flat_token_"+ str(i) +"'" \
                  + "style='display: inline; background:transparent; border-radius: 3px; padding: 0px;cursor: default;cursor: pointer;'" \
                  + f"onmouseover=\"onMouseHoverFlat_{uuid}(this.id)\" " \
                  + f"onmouseout=\"onMouseOutFlat_{uuid}(this.id)\" " \
                  + f"onclick=\"onMouseClickFlat_{uuid}(this.id)\" " \
                  + ">" \
                  + model_output[i].replace("<", "&lt;").replace(">", "&gt;").replace(' ##', '').replace('▁', '').replace('Ġ','') \
                  + " </div>" \
                  + "</div>"
      
      # computing gray scale images
      image_data = shap_values.data
      image_height = image_data.shape[0]
      image_width = image_data.shape[1]
      
      # computing gray scale image
      image_data_gray_scale = np.ones((image_height, image_width, 4)) * 255 * 0.5
      image_data_gray_scale[:,:,0] = np.mean(image_data,axis=2).astype(int)
      image_data_gray_scale[:,:,1] = image_data_gray_scale[:,:,0]
      image_data_gray_scale[:,:,2] = image_data_gray_scale[:,:,0]
          
      # computing shap color values for every pixel and for every output token
      
      shap_values_color_maps = shap_values.values[:,:,0,:]
      max_val = np.nanpercentile(np.abs(shap_values.values), 99.9)
      
      shap_values_color_dict = {}
      
      for index in range(model_output.shape[0]):        
          shap_values_color_dict[f'{uuid}_output_flat_token_{index}'] = (colors.red_transparent_blue(0.5 + 0.5 * shap_values_color_maps[:,:,index]/ max_val) * 255).astype(int).tolist()
  
      
      # converting to json to be read in javascript
      
      image_data_json = json.dumps(shap_values.data.astype(int).tolist())
      shap_values_color_dict_json = json.dumps(shap_values_color_dict)
      image_data_gray_scale_json = json.dumps(image_data_gray_scale.astype(int).tolist())
      
      image_viz_html = f"""
  
          <div id="{uuid}_image_viz" class="{uuid}_image_viz_content">
            <div id="{uuid}_image_viz_header" style="padding:15px;margin:5px;font-family:sans-serif;font-weight:bold;">
              <div style="display:inline">
                <span style="font-size: 20px;"> Input/Output - Heatmap </span>
              </div>
            </div>
            <div id="{uuid}_image_viz_content" style="display:flex;">
              <div id="{uuid}_image_viz_input_container" style="padding:15px;border-style:solid;margin:5px;flex:2;">
                <div id="{uuid}_image_viz_input_header" style="margin:5px;font-weight:bold;font-family:sans-serif;margin-bottom:10px">
                  Input Image
                </div>
                <div id="{uuid}_image_viz_input_content" style="margin:5px;font-family:sans-serif;">
                    <canvas id="{uuid}_image_canvas" style="cursor:grab;width:100%;max-height:500px;"></canvas>
                    <br>
                    <br>
                    <div id="{uuid}_tools">
                        <div id="{uuid}_zoom">
                          <span style="font-size:12px;margin-right:15px;"> Zoom </span>
                          <button id="{uuid}_minus_button" class="zoom-button" onclick="{uuid}_zoom(-1)" style="background-color: #555555;color: white; border:none;font-size:15px;">-</button>
                          <button id="{uuid}_plus_button" class="zoom-button" onclick="{uuid}_zoom(1)" style="background-color: #555555;color: white; border:none;font-size:15px;">+</button>
                          <button id="{uuid}_reset_button" class="zoom-button" onclick="{uuid}_reset()" style="background-color: #555555;color: white; border:none;font-size:15px;"> Reset </button>
                        </div>
                        <br>
                        <div id="{uuid}_opacity" style="display:none">
                        <span style="font-size:12px;margin-right:15px;"> Shap-Overlay Opacity </span>
                        <input type="range" min="1" max="100" value="35" style="width:100px" oninput="{uuid}_set_opacity(this.value)">
                        </div>
                    </div>
                </div>
              </div>
              <div id="{uuid}_image_viz_output_container" style="padding:15px;border-style:solid;margin:5px;flex:1;">
                <div id="{uuid}_image_viz_output_header" style="margin:5px;font-weight:bold;font-family:sans-serif;margin-bottom:10px">
                  Output Text
                </div>
                <div id="{uuid}_image_viz_output_content" style="margin:5px;font-family:sans-serif;">
                    {output_text_html}
                </div>
              </div>
            </div>
          </div>
  
      """
      
      
      
      image_viz_script = f"""
          <script>
              
              var {uuid}_heatmap_flat_state = null;
              var {uuid}_opacity = 0.35
              
              function onMouseHoverFlat_{uuid}(id) {{
                  if ({uuid}_heatmap_flat_state === null) {{
                      document.getElementById(id).style.backgroundColor  = "grey";
                      {uuid}_update_image_and_overlay(id);
                  }}            
              }}
              
              function onMouseOutFlat_{uuid}(id) {{
                  if ({uuid}_heatmap_flat_state === null) {{
                      document.getElementById(id).style.backgroundColor  = "transparent";
                      {uuid}_update_image_and_overlay(null);
                  }}                
              }}
  
              function onMouseClickFlat_{uuid}(id) {{
                  if ({uuid}_heatmap_flat_state === null) {{
                      document.getElementById(id).style.backgroundColor  = "grey";
                      document.getElementById('{uuid}_opacity').style.display  = "block";
                      {uuid}_update_image_and_overlay(id);
                      {uuid}_heatmap_flat_state = id;
                  }}
                  else {{
                      if ({uuid}_heatmap_flat_state === id) {{
                          document.getElementById(id).style.backgroundColor  = "transparent";
                          document.getElementById('{uuid}_opacity').style.display  = "none";
                          {uuid}_update_image_and_overlay(null);
                          {uuid}_heatmap_flat_state = null;
                      }}
                      else {{
                          document.getElementById({uuid}_heatmap_flat_state).style.backgroundColor  = "transparent";
                          document.getElementById(id).style.backgroundColor  = "grey";
                          {uuid}_update_image_and_overlay(id)
                          {uuid}_heatmap_flat_state = id
                      }}
                  }}
              }}         
  
              const {uuid}_image_data_matrix = {image_data_json};
              const {uuid}_image_data_gray_scale = {image_data_gray_scale_json};
              const {uuid}_image_height = {image_height};
              const {uuid}_image_width = {image_width};
              const {uuid}_shap_values_color_dict = {shap_values_color_dict_json};
              
              {uuid}_canvas = document.getElementById('{uuid}_image_canvas');
              {uuid}_context = {uuid}_canvas.getContext('2d');
              
              var {uuid}_imageData = {uuid}_convert_image_matrix_to_data({uuid}_image_data_matrix, {image_height}, {image_width}, {uuid}_context);            
              var {uuid}_currImagData = {uuid}_imageData;
              
              
              {uuid}_trackTransforms({uuid}_context);
              initial_scale_factor = Math.min({uuid}_canvas.height/{uuid}_image_height,{uuid}_canvas.width/{uuid}_image_width);
              {uuid}_context.scale(initial_scale_factor, initial_scale_factor);
              
              function {uuid}_update_image_and_overlay(selected_id) {{
                  if (selected_id == null) {{
                      {uuid}_currImagData = {uuid}_imageData;
                      {uuid}_redraw();
                  }}
                  else {{
                      {uuid}_currImagData = {uuid}_blend_image_shap_map({uuid}_image_data_gray_scale, {uuid}_shap_values_color_dict[selected_id], {image_height}, {image_width}, {uuid}_opacity, {uuid}_context);
                      {uuid}_redraw();
                  }}
              }}
              
              function {uuid}_set_opacity(value) {{
                  {uuid}_opacity = value/100;
                  
                  if ({uuid}_heatmap_flat_state !== null ) {{
                      {uuid}_currImagData = {uuid}_blend_image_shap_map({uuid}_image_data_gray_scale, {uuid}_shap_values_color_dict[{uuid}_heatmap_flat_state], {image_height}, {image_width}, {uuid}_opacity, {uuid}_context);                    
                      {uuid}_redraw();
                  }}
              }}
              
              function {uuid}_redraw() {{
  
                  // Clear the entire canvas
                  var p1 = {uuid}_context.transformedPoint(0, 0);
                  var p2 = {uuid}_context.transformedPoint({uuid}_canvas.width, {uuid}_canvas.height);
                  {uuid}_context.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
  
                  {uuid}_context.save();
                  {uuid}_context.setTransform(1, 0, 0, 1, 0, 0);
                  {uuid}_context.clearRect(0, 0, {uuid}_canvas.width, {uuid}_canvas.height);
                  {uuid}_context.restore();
  
                  createImageBitmap({uuid}_currImagData, {{ premultiplyAlpha: 'premultiply' }}).then(function(imgBitmap) {{
                      {uuid}_context.drawImage(imgBitmap, 0, 0);
                  }});
              }}
              {uuid}_redraw();
              {uuid}_context.save();
  
              var lastX = {uuid}_canvas.width / 2,
                  lastY = {uuid}_canvas.height / 2;
  
              var dragStart, dragged;
  
              {uuid}_canvas.addEventListener('mousedown', function(evt) {{
                  document.body.style.mozUserSelect = document.body.style.webkitUserSelect = document.body.style.userSelect = 'none';
                  lastX = evt.offsetX || (evt.pageX - {uuid}_canvas.offsetLeft);
                  lastY = evt.offsetY || (evt.pageY - {uuid}_canvas.offsetTop);
                  dragStart = {uuid}_context.transformedPoint(lastX, lastY);
                  dragged = false;
                  document.getElementById('{uuid}_image_canvas').style.cursor = 'grabbing';
              }}, false);
  
              {uuid}_canvas.addEventListener('mousemove', function(evt) {{
                  lastX = evt.offsetX || (evt.pageX - {uuid}_canvas.offsetLeft);
                  lastY = evt.offsetY || (evt.pageY - {uuid}_canvas.offsetTop);
                  dragged = true;
                  if (dragStart) {{
                      var pt = {uuid}_context.transformedPoint(lastX, lastY);
                      {uuid}_context.translate(pt.x - dragStart.x, pt.y - dragStart.y);
                      {uuid}_redraw();
                  }}
              }}, false);
  
              {uuid}_canvas.addEventListener('mouseup', function(evt) {{
                  dragStart = null;
                  document.getElementById('{uuid}_image_canvas').style.cursor = 'grab';
              }}, false);
  
              var scaleFactor = 1.1;
  
              var {uuid}_zoom = function(clicks) {{
                  var pt = {uuid}_context.transformedPoint(lastX, lastY);
                  {uuid}_context.translate(pt.x, pt.y);
                  var factor = Math.pow(scaleFactor, clicks);
                  {uuid}_context.scale(factor, factor);
                  {uuid}_context.translate(-pt.x, -pt.y);
                  {uuid}_redraw();
              }}
              
              var {uuid}_reset = function(clicks) {{
                  {uuid}_context.restore();
                  {uuid}_redraw();
                  {uuid}_context.save();
              }}
              
              var handleScroll = function(evt) {{
                  var delta = evt.wheelDelta ? evt.wheelDelta / 40 : evt.detail ? -evt.detail : 0;
                  if (delta) {uuid}_zoom(delta);
                  return evt.preventDefault() && false;
              }}
  
              {uuid}_canvas.addEventListener('DOMMouseScroll', handleScroll, false);
              {uuid}_canvas.addEventListener('mousewheel', handleScroll, false);
  
  
  
              function {uuid}_trackTransforms(ctx) {{
                  var svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
                  var xform = svg.createSVGMatrix();
                  ctx.getTransform = function() {{
                      return xform;
                  }}
  
                  var savedTransforms = [];
                  var save = ctx.save;
                  ctx.save = function() {{
                      savedTransforms.push(xform.translate(0, 0));
                      return save.call(ctx);
                  }}
  
                  var restore = ctx.restore;
                  ctx.restore = function() {{
                      xform = savedTransforms.pop();
                      return restore.call(ctx);
                  }}
  
                  var scale = ctx.scale;
                  ctx.scale = function(sx, sy) {{
                      xform = xform.scaleNonUniform(sx, sy);
                      return scale.call(ctx, sx, sy);
                  }}
  
                  var rotate = ctx.rotate;
                  ctx.rotate = function(radians) {{
                      xform = xform.rotate(radians * 180 / Math.PI);
                      return rotate.call(ctx, radians);
                  }}
  
                  var translate = ctx.translate;
                  ctx.translate = function(dx, dy) {{
                      xform = xform.translate(dx, dy);
                      return translate.call(ctx, dx, dy);
                  }}
  
                  var transform = ctx.transform;
                  ctx.transform = function(a, b, c, d, e, f) {{
                      var m2 = svg.createSVGMatrix();
                      m2.a = a;
                      m2.b = b;
                      m2.c = c;
                      m2.d = d;
                      m2.e = e;
                      m2.f = f;
                      xform = xform.multiply(m2);
                      return transform.call(ctx, a, b, c, d, e, f);
                  }}
  
                  var setTransform = ctx.setTransform;
                  ctx.setTransform = function(a, b, c, d, e, f) {{
                      xform.a = a;
                      xform.b = b;
                      xform.c = c;
                      xform.d = d;
                      xform.e = e;
                      xform.f = f;
                      return setTransform.call(ctx, a, b, c, d, e, f);
                  }}
  
                  var pt = svg.createSVGPoint();
                  ctx.transformedPoint = function(x, y) {{
                      pt.x = x;
                      pt.y = y;
                      return pt.matrixTransform(xform.inverse());
                  }}
              }}
              
  
              function {uuid}_convert_image_matrix_to_data(image_data_matrix, image_height, image_width, context) {{
  
                  var imageData = context.createImageData(image_height, image_width);
  
                  for(var row_index = 0; row_index < image_height; row_index++) {{
                      for(var col_index = 0; col_index < image_width; col_index++) {{
  
                          index = (row_index * image_width + col_index) * 4;
  
                          imageData.data[index + 0] = image_data_matrix[row_index][col_index][0];
                          imageData.data[index + 1] = image_data_matrix[row_index][col_index][1];
                          imageData.data[index + 2] = image_data_matrix[row_index][col_index][2];
                          imageData.data[index + 3] = 255;
                      }}
                  }}
                  
                  return imageData;
              }}
              
              function {uuid}_blend_image_shap_map(image_data_matrix, shap_color_map, image_height, image_width, alpha, context) {{
                  var blendedImageData = context.createImageData(image_height, image_width);
  
                  for(var row_index = 0; row_index < image_height; row_index++) {{
                  
                      for(var col_index = 0; col_index < image_width; col_index++) {{
                      
                          index = (row_index * image_width + col_index) * 4;
                      
                          blendedImageData.data[index + 0] = image_data_matrix[row_index][col_index][0] * alpha + (shap_color_map[row_index][col_index][0]) * ( 1 - alpha);
                          blendedImageData.data[index + 1] = image_data_matrix[row_index][col_index][1] * alpha + (shap_color_map[row_index][col_index][1]) * ( 1 - alpha);
                          blendedImageData.data[index + 2] = image_data_matrix[row_index][col_index][2] * alpha + (shap_color_map[row_index][col_index][2]) * ( 1 - alpha);
                          blendedImageData.data[index + 3] = image_data_matrix[row_index][col_index][3] * alpha + (shap_color_map[row_index][col_index][3]) * ( 1 - alpha);
                      }}
                  }}
  
                  return blendedImageData;
              }}
  
          </script>
      """
  
      display(HTML(image_viz_html + image_viz_script))
  