class_docstr: Array writer that can use slope and intercept to scale array
  
  The writer can subtract an intercept, and divided by a slope, in order to
  be able to convert floating point values into a (u)int range, or to convert
  larger (u)ints to smaller.
  
  It extends the ArrayWriter class with attributes:
  
  * inter
  * slope
  
  and methods:
  
  * reset() - reset inter, slope to default (not adapted to self.array)
  * calc_scale() - calculate inter, slope to best write self.array
class_docstr_tok: ["Array","writer","that","can","use","slope","and","intercept","to","scale","array","The","writer","can","subtract","an","intercept",",","and","divided","by","a","slope",",","in","order","to","be","able","to","convert","floating","point","values","into","a","(","u",")","int","range",",","or","to","convert","larger","(","u",")","ints","to","smaller",".","It","extends","the","ArrayWriter","class","with","attributes",":","*","inter","*","slope","and","methods",":","*","reset","(",")","-","reset","inter",",","slope","to","default","(","not","adapted","to","self.array",")","*","calc_scale","(",")","-","calculate","inter",",","slope","to","best","write","self.array"]
docstr: Calculate scaling, intercept based on data range and output type
docstr_tok: ["Calculate","scaling",",","intercept","based","on","data","range","and","output","type"]
code: def _range_scale(self, in_min, in_max):
      """Calculate scaling, intercept based on data range and output type"""
      if in_max == in_min:  # Only one number in array
          self.slope = 1.0
          self.inter = in_min
          return
      big_float = best_float()
      in_dtype = self._array.dtype
      out_dtype = self._out_dtype
      working_dtype = self.scaler_dtype
      if in_dtype.kind == "f":  # Already floats
          # float64 and below cast correctly to longdouble.  Longdouble needs
          # no casting
          in_min, in_max = np.array([in_min, in_max], dtype=big_float)
          in_range = np.diff([in_min, in_max])
      else:  # max possible (u)int range is 2**64-1 (int64, uint64)
          # On windows longdouble is the same as double so in_range will be 2**64 -
          # thus overestimating slope slightly.  Casting to int needed to allow
          # in_max-in_min to be larger than the largest (u)int value
          in_min, in_max = int(in_min), int(in_max)
          in_range = big_float(in_max - in_min)
          # Cast to float for later processing.
          in_min, in_max = (big_float(v) for v in (in_min, in_max))
      if out_dtype.kind == "f":
          # Type range, these are also floats
          info = type_info(out_dtype)
          out_min, out_max = info["min"], info["max"]
      else:
          # Use shared range to avoid rounding to values outside range. This
          # doesn't matter much except for the case of nan2zero were we need
          # to be able to represent the scaled zero correctly in order not to
          # raise an error when writing
          out_min, out_max = shared_range(working_dtype, out_dtype)
          out_min, out_max = np.array(
              (out_min, out_max), dtype=big_float
          )
      # We want maximum precision for the calculations. Casting will not lose
      # precision because min/max are of fp type.
      assert [v.dtype.kind for v in (out_min, out_max)] == ["f", "f"]
      out_range = out_max - out_min
      """
          Think of the input values as a line starting (left) at in_min and
          ending (right) at in_max.
  
          The output values will be a line starting at out_min and ending at
          out_max.
  
          We are going to match the input line to the output line by subtracting
          `inter` then dividing by `slope`.
  
          Slope must scale the input line to have the same length as the output
          line.  We find this scale factor by dividing the input range (line
          length) by the output range (line length)
          """
      slope = in_range / out_range
      """
          Now we know the slope, we need the intercept.  The intercept will be
          such that:
  
              (in_min - inter) / slope = out_min
  
          Solving for the intercept:
  
              inter = in_min - out_min * slope
  
          We can also flip the sign of the slope.  In that case we match the
          in_max to the out_min:
  
              (in_max - inter_flipped) / -slope = out_min
              inter_flipped = in_max + out_min * slope
  
          When we reconstruct the data, we're going to do:
  
              data = saved_data * slope + inter
  
          We can't change the range of the saved data (the whole range of the
          integer type) or the range of the output data (the values we input). We
          can change the intermediate values ``saved_data * slope`` by choosing
          the sign of the slope to match the in_min or in_max to the left or
          right end of the saved data range.
  
          If the out_dtype is signed int, then abs(out_min) = abs(out_max) + 1
          and the absolute value and therefore precision for values at the left
          and right of the saved data range are very similar (e.g. -128 * slope,
          127 * slope respectively).
  
          If the out_dtype is unsigned int, then the absolute value at the left
          is 0 and the precision is much higher than for the right end of the
          range (e.g. 0 * slope, 255 * slope).
  
          If the out_dtype is unsigned int then we choose the sign of the slope
          to match the smaller of the in_min, in_max to the zero end of the saved
          range.
          """
      if out_min == 0 and np.abs(in_max) < np.abs(in_min):
          inter = in_max + out_min * slope
          slope *= -1
      else:
          inter = in_min - out_min * slope
      # slope, inter properties force scaling_dtype cast
      self.inter = inter
      self.slope = slope
      if not np.all(np.isfinite([self.slope, self.inter])):
          raise ScalingError("Slope / inter not both finite")
      # Check nan fill value
      if not (
          0 in (in_min, in_max) and self._nan2zero and self.has_nan
      ):
          return
      nan_fill_f = -self.inter / self.slope
      nan_fill_i = np.rint(nan_fill_f)
      if nan_fill_i == np.array(nan_fill_i, dtype=out_dtype):
          return
      # recalculate intercept using dtype of inter, scale
      self.inter = -np.clip(nan_fill_f, out_min, out_max) * self.slope
      nan_fill_i = np.rint(-self.inter / self.slope)
      assert nan_fill_i == np.array(nan_fill_i, dtype=out_dtype)
  
imports: import numpy
  from .casting import best_float, floor_exact, int_abs, shared_range, type_info
  from .volumeutils import array_to_file, finite_range
label_desc: Use image node_id 7 for calling the SlopeInterArrayWriter obj's underlying member method code with example usage: obj._range_scale(in_min, in_max) without return types