def as_pil(ndarray, min_val=None, max_val=None):
  """
  Converts an ndarray to a PIL image.
  :param ndarray: The numpy ndarray to convert
  :param min_val: The minimum pixel value in the image format
  :param max_val: The maximum pixel valie in the image format
    If min_val and max_val are not specified, attempts to
    infer whether the image is in any of the common ranges:
      [0, 1], [-1, 1], [0, 255]
    This can be ambiguous, so it is better to specify if known.
  """

  assert isinstance(ndarray, np.ndarray)

  # rows x cols for grayscale image
  # rows x cols x channels for color
  assert ndarray.ndim in [2, 3]
  if ndarray.ndim == 3:
    channels = ndarray.shape[2]
    # grayscale or RGB
    assert channels in [1, 3]

  actual_min = ndarray.min()
  actual_max = ndarray.max()

  if min_val is not None:
    assert actual_min >= min_val
    assert actual_max <= max_val

  if np.issubdtype(ndarray.dtype, np.floating):
    if min_val is None:
      if actual_min < -1.:
        raise ValueError("Unrecognized range")
      if actual_min < 0:
        min_val = -1.
      else:
        min_val = 0.
    if max_val is None:
      if actual_max > 255.:
        raise ValueError("Unrecognized range")
      if actual_max > 1.:
        max_val = 255.
      else:
        max_val = 1.
    ndarray = (ndarray - min_val)
    value_range = max_val - min_val
    ndarray *= (255. / value_range)
    ndarray = np.cast['uint8'](ndarray)
  elif 'int' in str(ndarray.dtype):
    if min_val is not None:
      assert min_val == 0
    else:
      assert actual_min >= 0.
    if max_val is not None:
      assert max_val == 255
    else:
      assert actual_max <= 255.
  else:
    raise ValueError("Unrecognized dtype")

  out = Image.fromarray(ndarray)

  return out