def mean_std_luts(in_img, ref_img, in_mask=None, ref_mask=None, dtype=np.uint16): if in_mask is not None: in_mask = np.tile(in_mask[..., None], (1, 1, in_img.shape[2])) in_img = np.ma.masked_where(in_mask, in_img) if ref_mask is not None: ref_mask = np.tile(ref_mask[..., None], (1, 1, ref_img.shape[2])) ref_img = np.ma.masked_where(ref_mask, ref_img) # Need to make sure we check after masking invalid values # Some color targets have values out of the 12-bit range. _check_match_images(in_img, ref_img, dtype) nbands = in_img.shape[2] in_mean = np.array([in_img[..., i].mean() for i in range(nbands)]) in_std = np.array([in_img[..., i].std() for i in range(nbands)]) ref_mean = np.array([ref_img[..., i].mean() for i in range(nbands)]) ref_std = np.array([ref_img[..., i].std() for i in range(nbands)]) logging.info("Input image mean: {}" \ .format(in_mean.tolist())) logging.info("Input image stddev: {}" \ .format(in_std.tolist())) logging.info("Reference image mean: {}" \ .format(ref_mean.tolist())) logging.info("Reference image stddev: {}" \ .format(ref_std.tolist())) out_luts = [] if dtype == np.uint16: # Assume any 16-bit images are actually 12-bit. minimum = 0 maximum = 4096 else: minimum = np.iinfo(dtype).min maximum = np.iinfo(dtype).max in_lut = np.arange(minimum, maximum + 1, dtype=dtype) # Need to rescale for 8-bit color targets... if ref_img.dtype != dtype: dmin = np.iinfo(ref_img.dtype).min dmax = np.iinfo(ref_img.dtype).max ref_mean = maximum * (ref_mean - dmin) / float(dmax - dmin) ref_std = maximum * ref_std / float(dmax - dmin) for bidx in range(3): if in_std[bidx] == 0: scale = 0 else: scale = float(ref_std[bidx]) / in_std[bidx] offset = ref_mean[bidx] - scale * in_mean[bidx] lut = ci.scale_offset_lut(in_lut, scale=scale, offset=offset) out_luts.append(lut) return out_luts
def mean_std_luts(in_img, ref_img, in_mask=None, ref_mask=None, dtype=np.uint16): _check_match_images(in_img, ref_img, dtype) # Create a 3d mask from the 2d mask # Numpy masked arrays treat True as masked values. Opposite of OpenCV if in_mask is not None: indices = np.where(in_mask.astype(bool) == False) r = in_img[:, :, 0][indices] g = in_img[:, :, 1][indices] b = in_img[:, :, 2][indices] else: r = in_img[:, :, 0] g = in_img[:, :, 1] b = in_img[:, :, 2] in_mean = np.array([r.mean(), g.mean(), b.mean()]) in_std = np.array([r.std(), g.std(), b.std()]) if ref_mask is not None: indices = np.where(ref_mask.astype(bool) == False) r_ref = ref_img[:, :, 0][indices] g_ref = ref_img[:, :, 1][indices] b_ref = ref_img[:, :, 2][indices] else: r_ref = ref_img[:, :, 0] g_ref = ref_img[:, :, 1] b_ref = ref_img[:, :, 2] ref_mean = np.array([r_ref.mean(), g_ref.mean(), b_ref.mean()]) ref_std = np.array([r_ref.std(), g_ref.std(), b_ref.std()]) logging.info("Input image mean: {}".format(in_mean.tolist())) logging.info("Input image stddev: {}".format(in_std.tolist())) logging.info("Reference image mean: {}".format(ref_mean.tolist())) logging.info("Reference image stddev: {}".format(ref_std.tolist())) out_luts = [] # minimum = np.iinfo(dtype).min # maximum = np.iinfo(dtype).max # test out 12bit minimum = 0 maximum = 4096 in_lut = np.arange(minimum, maximum + 1, dtype=dtype) for bidx in range(3): if in_std[bidx] == 0: scale = 0 else: scale = float(ref_std[bidx]) / in_std[bidx] offset = ref_mean[bidx] - scale * in_mean[bidx] lut = ci.scale_offset_lut(in_lut, scale=scale, offset=offset) out_luts.append(lut) return out_luts
def test_scale_offset_lut(self): test_lut = np.array(range(10)) # Test that the returned lut equals the entered lut if # no scale or offset is given expected = test_lut lut = colorimage.scale_offset_lut(test_lut) np.testing.assert_array_equal(lut, expected) # Test clipping at top values expected = np.array([5, 6, 7, 8, 9, 9, 9, 9, 9, 9]) lut = colorimage.scale_offset_lut(test_lut, offset=5) np.testing.assert_array_equal(lut, expected) # Test clipping at bottom values expected = np.array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4]) lut = colorimage.scale_offset_lut(test_lut, offset=-5) np.testing.assert_array_equal(lut, expected) # Test scaling by less than one expected = np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4]) lut = colorimage.scale_offset_lut(test_lut, scale=0.5) np.testing.assert_array_equal(lut, expected) # Test scaling by greater than one expected = np.array([0, 2, 4, 6, 8, 9, 9, 9, 9, 9]) lut = colorimage.scale_offset_lut(test_lut, scale=2) np.testing.assert_array_equal(lut, expected) # Test for clipping effects # Results should be the same as test 1 above test_lut = 2 * np.array(range(10)) expected = np.array([5, 6, 7, 8, 9, 9, 9, 9, 9, 9]) lut = colorimage.scale_offset_lut(test_lut, scale=0.5, offset=5) np.testing.assert_array_equal(lut, expected)
def test_scale_offset_lut(self): test_lut = numpy.array(range(10)) # Test that the returned lut equals the entered lut if # no scale or offset is given expected = test_lut lut = colorimage.scale_offset_lut(test_lut) numpy.testing.assert_array_equal(lut, expected) # Test clipping at top values expected = numpy.array([5, 6, 7, 8, 9, 9, 9, 9, 9, 9]) lut = colorimage.scale_offset_lut(test_lut, offset=5) numpy.testing.assert_array_equal(lut, expected) # Test clipping at bottom values expected = numpy.array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4]) lut = colorimage.scale_offset_lut(test_lut, offset=-5) numpy.testing.assert_array_equal(lut, expected) # Test scaling by less than one expected = numpy.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4]) lut = colorimage.scale_offset_lut(test_lut, scale=0.5) numpy.testing.assert_array_equal(lut, expected) # Test scaling by greater than one expected = numpy.array([0, 2, 4, 6, 8, 9, 9, 9, 9, 9]) lut = colorimage.scale_offset_lut(test_lut, scale=2) numpy.testing.assert_array_equal(lut, expected) # Test for clipping effects # Results should be the same as test 1 above test_lut = 2 * numpy.array(range(10)) expected = numpy.array([5, 6, 7, 8, 9, 9, 9, 9, 9, 9]) lut = colorimage.scale_offset_lut(test_lut, scale=0.5, offset=5) numpy.testing.assert_array_equal(lut, expected)
def mean_std_luts(in_img, ref_img, in_mask=None, ref_mask=None): _check_match_images(in_img, ref_img) # Create a 3d mask from the 2d mask # Numpy masked arrays treat True as masked values. Opposite of OpenCV in_mask = np.dstack(3 * [np.logical_not(in_mask.astype(bool))]) ref_mask = np.dstack(3 * [np.logical_not(ref_mask.astype(bool))]) height1, width1, count1 = in_img.shape height2, width2, count2 = ref_img.shape in_img = np.ma.MaskedArray(in_img, in_mask).reshape( (height1 * width1, count1)) ref_img = np.ma.MaskedArray(ref_img, ref_mask).reshape( (height2 * width2, count2)) in_mean = in_img.mean(axis=0).data in_std = in_img.std(axis=0).data ref_mean = ref_img.mean(axis=0).data ref_std = ref_img.std(axis=0).data logging.info("Input image mean: {}" \ .format(in_mean.tolist())) logging.info("Input image stddev: {}" \ .format(in_std.tolist())) logging.info("Reference image mean: {}" \ .format(ref_mean.tolist())) logging.info("Reference image stddev: {}" \ .format(ref_std.tolist())) out_luts = [] in_lut = np.array(range(0, 256), dtype=np.uint8) for bidx in range(count1): if in_std[bidx] == 0: scale = 0 else: scale = float(ref_std[bidx]) / in_std[bidx] offset = ref_mean[bidx] - scale * in_mean[bidx] lut = ci.scale_offset_lut(in_lut, scale=scale, offset=offset) out_luts.append(lut) return out_luts
def mean_std_luts(in_img, ref_img, in_mask=None, ref_mask=None): _check_match_images(in_img, ref_img) # Create a 3d mask from the 2d mask # Numpy masked arrays treat True as masked values. Opposite of OpenCV in_mask = np.dstack(3 * [np.logical_not(in_mask.astype(bool))]) ref_mask = np.dstack(3 * [np.logical_not(ref_mask.astype(bool))]) height1, width1, count1 = in_img.shape height2, width2, count2 = ref_img.shape in_img = np.ma.MaskedArray(in_img, in_mask).reshape((height1 * width1, count1)) ref_img = np.ma.MaskedArray(ref_img, ref_mask).reshape((height2 * width2, count2)) in_mean = in_img.mean(axis=0).data in_std = in_img.std(axis=0).data ref_mean = ref_img.mean(axis=0).data ref_std = ref_img.std(axis=0).data logging.info("Input image mean: {}" \ .format(in_mean.tolist())) logging.info("Input image stddev: {}" \ .format(in_std.tolist())) logging.info("Reference image mean: {}" \ .format(ref_mean.tolist())) logging.info("Reference image stddev: {}" \ .format(ref_std.tolist())) out_luts = [] in_lut = np.array(range(0, 256), dtype=np.uint8) for bidx in range(count1): if in_std[bidx] == 0: scale = 0 else: scale = float(ref_std[bidx]) / in_std[bidx] offset = ref_mean[bidx] - scale * in_mean[bidx] lut = ci.scale_offset_lut(in_lut, scale=scale, offset=offset) out_luts.append(lut) return out_luts
def mean_std_luts(in_img, ref_img, in_mask=None, ref_mask=None, dtype=np.uint16): """ Create a look up table to linearly scale the colors of the input image to the reference image based on their relative means and standard deviations. :param in_img: Input image to be scaled as a 3D (height x width x nbands) numpy array. :param ref_img: Reference image to base the scaling on as a 3D numpy array. :param in_mask: 2D boolean mask to be applied to the input image. True values will not be included in the color scaling calculation. :param ref_mask: 2D boolean mask to be applied to the reference image. True values will not be included in the color scaling calculation. :param dtype: Numpy data type used to represent the output LUT :return look up table: """ if in_mask is not None: in_mask = np.tile(in_mask[..., None], (1, 1, in_img.shape[2])) in_img = np.ma.masked_where(in_mask, in_img) if ref_mask is not None: ref_mask = np.tile(ref_mask[..., None], (1, 1, ref_img.shape[2])) ref_img = np.ma.masked_where(ref_mask, ref_img) # Need to make sure we check after masking invalid values # Some color targets have values out of the 12-bit range. _check_match_images(in_img, ref_img, dtype) nbands = in_img.shape[2] in_mean = np.array([in_img[..., i].mean() for i in range(nbands)]) in_std = np.array([in_img[..., i].std() for i in range(nbands)]) ref_mean = np.array([ref_img[..., i].mean() for i in range(nbands)]) ref_std = np.array([ref_img[..., i].std() for i in range(nbands)]) logging.info("Input image mean: {}" \ .format(in_mean.tolist())) logging.info("Input image stddev: {}" \ .format(in_std.tolist())) logging.info("Reference image mean: {}" \ .format(ref_mean.tolist())) logging.info("Reference image stddev: {}" \ .format(ref_std.tolist())) out_luts = [] if dtype == np.uint16: # Assume any 16-bit images are actually 12-bit. minimum = 0 maximum = 4096 else: minimum = np.iinfo(dtype).min maximum = np.iinfo(dtype).max in_lut = np.arange(minimum, maximum + 1, dtype=dtype) # Need to rescale for 8-bit color targets... if ref_img.dtype != dtype: dmin = np.iinfo(ref_img.dtype).min dmax = np.iinfo(ref_img.dtype).max ref_mean = maximum * (ref_mean - dmin) / float(dmax - dmin) ref_std = maximum * ref_std / float(dmax - dmin) for bidx in range(3): if in_std[bidx] == 0: scale = 0 else: scale = float(ref_std[bidx]) / in_std[bidx] offset = ref_mean[bidx] - scale * in_mean[bidx] lut = ci.scale_offset_lut(in_lut, scale=scale, offset=offset) out_luts.append(lut) return out_luts