def make_heatmask(probs, cmap='plasma', with_alpha=1.0, space='rgb', dsize=None): """ Colorizes a single-channel intensity mask (with an alpha channel) Args: probs (ndarray): 2D probability map with values between 0 and 1 cmap (str): mpl colormap with_alpha (float): between 0 and 1, uses probs as the alpha multipled by this number. space (str): output colorspace dsize (tuple): if not None, then output is resized to W,H=dsize SeeAlso: kwimage.overlay_alpha_images Example: >>> # xdoc: +REQUIRES(module:matplotlib) >>> probs = np.tile(np.linspace(0, 1, 10), (10, 1)) >>> heatmask = make_heatmask(probs, with_alpha=0.8, dsize=(100, 100)) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.imshow(heatmask, fnum=1, doclf=True, colorspace='rgb') >>> kwplot.show_if_requested() """ import kwimage import matplotlib as mpl import matplotlib.cm # NOQA assert len(probs.shape) == 2 cmap_ = mpl.cm.get_cmap(cmap) probs = kwimage.ensure_float01(probs) heatmask = cmap_(probs).astype(np.float32) heatmask = kwimage.convert_colorspace(heatmask, 'rgba', space, implicit=True) if with_alpha is not False and with_alpha is not None: heatmask[:, :, 3] = (probs * with_alpha) # assign probs to alpha channel if dsize is not None: import cv2 heatmask = cv2.resize(heatmask, tuple(dsize), interpolation=cv2.INTER_NEAREST) return heatmask
def make_vector_field(dx, dy, stride=0.02, thresh=0.0, scale=1.0, alpha=1.0, color='red', thickness=1, tipLength=0.1, line_type='aa'): """ Create an image representing a 2D vector field. Args: dx (ndarray): grid of vector x components dy (ndarray): grid of vector y components stride (int | float): sparsity of vectors, int specifies stride step in pixels, a float specifies it as a percentage. thresh (float): only plot vectors with magnitude greater than thres scale (float): multiply magnitude for easier visualization alpha (float): alpha value for vectors. Non-vector regions receive 0 alpha (if False, no alpha channel is used) color (str | tuple | kwimage.Color): RGB color of the vectors thickness (int, default=1): thickness of arrows tipLength (float, default=0.1): fraction of line length line_type (int): either cv2.LINE_4, cv2.LINE_8, or cv2.LINE_AA Returns: ndarray[float32]: vec_img: an rgb/rgba image in 0-1 space SeeAlso: kwimage.overlay_alpha_images DEPRECATED USE: draw_vector_field instead Example: >>> x, y = np.meshgrid(np.arange(512), np.arange(512)) >>> dx, dy = x - 256.01, y - 256.01 >>> radians = np.arctan2(dx, dy) >>> mag = np.sqrt(dx ** 2 + dy ** 2) >>> dx, dy = dx / mag, dy / mag >>> img = make_vector_field(dx, dy, scale=10, alpha=False) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> kwplot.imshow(img) >>> kwplot.show_if_requested() """ import warnings warnings.warn('Deprecated, use draw_vector_field instead', DeprecationWarning) import cv2 import kwimage color = kwimage.Color(color).as255('rgb') vecmask = np.zeros(dx.shape + (3, ), dtype=np.uint8) line_type_lookup = {'aa': cv2.LINE_AA} line_type = line_type_lookup.get(line_type, line_type) width = dx.shape[1] height = dy.shape[0] x_grid = np.arange(0, width, 1) y_grid = np.arange(0, height, 1) # Vector locations and directions X, Y = np.meshgrid(x_grid, y_grid) U, V = dx, dy XYUV = [X, Y, U, V] if isinstance(stride, float): if stride < 0 or stride > 1: raise ValueError('Floating point strides must be between 0 and 1') stride = int(np.ceil(stride * min(width, height))) # stride the points if stride is not None and stride > 1: XYUV = [a[::stride, ::stride] for a in XYUV] # flatten the points XYUV = [a.ravel() for a in XYUV] # Filter out points with low magnitudes if thresh is not None and thresh > 0: M = np.sqrt((XYUV[2]**2) + (XYUV[3]**2)).ravel() XYUV = np.array(XYUV) flags = M > thresh XYUV = [a[flags] for a in XYUV] # Adjust vector magnitude for visibility if scale is not None: XYUV[2] *= scale XYUV[3] *= scale for (x, y, u, v) in zip(*XYUV): pt1 = (int(x), int(y)) pt2 = tuple(map(int, map(np.round, (x + u, y + v)))) cv2.arrowedLine(vecmask, pt1, pt2, color=color, thickness=thickness, tipLength=tipLength, line_type=line_type) vecmask = kwimage.ensure_float01(vecmask) if isinstance(alpha, np.ndarray): # Alpha specified as explicit numpy array vecmask = kwimage.ensure_alpha_channel(vecmask) vecmask[:, :, 3] = alpha elif alpha is not False and alpha is not None: # Alpha specified as a scale factor vecmask = kwimage.ensure_alpha_channel(vecmask) # vecmask[:, :, 3] = (vecmask[:, :, 0:3].sum(axis=2) > 0) * alpha vecmask[:, :, 3] = vecmask[:, :, 0:3].sum(axis=2) * alpha return vecmask
def _check(): # Find the sigma closest to the pyrDown op [1, 4, 6, 4, 1] / 16 import cv2 import numpy as np import scipy import ubelt as ub def sigma_error(sigma): sigma = np.asarray(sigma).ravel()[0] got = (cv2.getGaussianKernel(5, sigma) * 16).ravel() want = np.array([1, 4, 6, 4, 1]) loss = ((got - want) ** 2).sum() return loss result = scipy.optimize.minimize(sigma_error, x0=1.0, method='Nelder-Mead') print('result = {}'.format(ub.repr2(result, nl=1))) best_loss = float('inf') best = None methods = ['Nelder-Mead', 'Powell', 'CG', 'BFGS', # 'Newton-CG', 'L-BFGS-B', 'TNC', 'COBYLA', 'SLSQP', # 'trust-constr', # 'dogleg', # 'trust-ncg', 'trust-exact', # 'trust-krylov' ] # results = {} x0 = 1.06 for i in range(100): for method in methods: best_method_loss, best_method_sigma, best_method_x0 = results.get(method, (float('inf'), 1.06, x0)) result = scipy.optimize.minimize(sigma_error, x0=x0, method=method) sigma = np.asarray(result.x).ravel()[0] loss = sigma_error(sigma) if loss <= best_method_loss: results[method] = (loss, sigma, x0) best_method = ub.argmin(results) best_loss, best_sigma = results[best_method][0:2] rng = np.random if rng.rand() > 0.5: x0 = best_sigma else: x0 = best_sigma + rng.rand() * 0.0001 print('best_method = {!r}'.format(best_method)) print('best_loss = {!r}'.format(best_loss)) print('best_sigma = {!r}'.format(best_sigma)) print('results = {}'.format(ub.repr2(results, nl=1, align=':'))) print('best_method = {}'.format(ub.repr2(best_method, nl=1))) print('best_method = {!r}'.format(best_method)) sigma_error(1.0565139268118493) sigma_error(1.0565137190917149) # scipy.optimize.minimize_scalar(sigma_error, bounds=(1, 1.1)) import kwarray import numpy as np a = (kwarray.ensure_rng(0).rand(32, 32) * 256).astype(np.uint8) a = kwimage.ensure_float01(a) b = cv2.GaussianBlur(a.copy(), (1, 1), 3, 3) assert np.all(a == b) import timerit ti = timerit.Timerit(100, bestof=10, verbose=2) for timer in ti.reset('time'): with timer: b = cv2.GaussianBlur(a.copy(), (9, 9), 3, 3) import timerit ti = timerit.Timerit(100, bestof=10, verbose=2) for timer in ti.reset('time'): with timer: c = cv2.GaussianBlur(a.copy(), (1, 9), 3, 3) zR= cv2.GaussianBlur(c.copy(), (9, 1), 3, 3)
def draw_on(self, image, color='white', radius=None, copy=False): """ CommandLine: xdoctest -m ~/code/kwimage/kwimage/structs/points.py Points.draw_on --show Example: >>> # xdoc: +REQUIRES(module:kwplot) >>> from kwimage.structs.points import * # NOQA >>> s = 128 >>> image = np.zeros((s, s)) >>> self = Points.random(10).scale(s) >>> image = self.draw_on(image) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.figure(fnum=1, doclf=True) >>> kwplot.autompl() >>> kwplot.imshow(image) >>> self.draw(radius=3, alpha=.5) >>> kwplot.show_if_requested() Example: >>> # xdoc: +REQUIRES(module:kwplot) >>> from kwimage.structs.points import * # NOQA >>> s = 128 >>> image = np.zeros((s, s)) >>> self = Points.random(10).scale(s) >>> image = self.draw_on(image, radius=3, color='distinct') >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.figure(fnum=1, doclf=True) >>> kwplot.autompl() >>> kwplot.imshow(image) >>> self.draw(radius=3, alpha=.5, color='classes') >>> kwplot.show_if_requested() Example: >>> import kwimage >>> s = 32 >>> self = kwimage.Points.random(10).scale(s) >>> color = 'blue' >>> # Test drawong on all channel + dtype combinations >>> im3 = np.zeros((s, s, 3), dtype=np.float32) >>> im_chans = { >>> 'im3': im3, >>> 'im1': kwimage.convert_colorspace(im3, 'rgb', 'gray'), >>> 'im4': kwimage.convert_colorspace(im3, 'rgb', 'rgba'), >>> } >>> inputs = {} >>> for k, im in im_chans.items(): >>> inputs[k + '_01'] = (kwimage.ensure_float01(im.copy()), {'radius': None}) >>> inputs[k + '_255'] = (kwimage.ensure_uint255(im.copy()), {'radius': None}) >>> outputs = {} >>> for k, v in inputs.items(): >>> im, kw = v >>> outputs[k] = self.draw_on(im, color=color, **kw) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.figure(fnum=2, doclf=True) >>> kwplot.autompl() >>> pnum_ = kwplot.PlotNums(nCols=2, nRows=len(inputs)) >>> for k in inputs.keys(): >>> kwplot.imshow(inputs[k][0], fnum=2, pnum=pnum_(), title=k) >>> kwplot.imshow(outputs[k], fnum=2, pnum=pnum_(), title=k) >>> kwplot.show_if_requested() """ import kwimage dtype_fixer = _generic._consistent_dtype_fixer(image) if radius is None: if color == 'distinct': raise NotImplementedError image = kwimage.atleast_3channels(image) image = kwimage.ensure_float01(image, copy=copy) # value = kwimage.Color(color).as01() value = kwimage.Color(color)._forimage(image) image = self.data['xy'].fill( image, value, coord_axes=[1, 0], interp='bilinear') else: import cv2 image = kwimage.atleast_3channels(image, copy=copy) # note: ellipse has a different return type (UMat) and does not # work inplace if the input is not contiguous. image = np.ascontiguousarray(image) xy_pts = self.data['xy'].data.reshape(-1, 2) if color == 'distinct': colors = kwimage.Color.distinct(len(xy_pts)) elif color == 'classes': # TODO: read colors from categories if they exist class_idxs = self.data['class_idxs'] _keys, _vals = kwarray.group_indices(class_idxs) cls_colors = kwimage.Color.distinct(len(self.meta['classes'])) colors = list(ub.take(cls_colors, class_idxs)) colors = [kwimage.Color(c)._forimage(image) for c in colors] # if image.dtype.kind == 'f': # colors = [kwimage.Color(c).as01() for c in colors] # else: # colors = [kwimage.Color(c).as255() for c in colors] else: value = kwimage.Color(color)._forimage(image) colors = [value] * len(xy_pts) # image = kwimage.ensure_float01(image) for xy, color_ in zip(xy_pts, colors): # center = tuple(map(int, xy.tolist())) center = tuple(xy.tolist()) axes = (radius / 2, radius / 2) center = tuple(map(int, center)) axes = tuple(map(int, axes)) # print('center = {!r}'.format(center)) # print('axes = {!r}'.format(axes)) cv2.ellipse(image, center, axes, angle=0.0, startAngle=0.0, endAngle=360.0, color=color_, thickness=-1) image = dtype_fixer(image, copy=False) return image
def warp_image_test(image, transform, dsize=None): """ from kwimage.transform import Affine import kwimage image = kwimage.grab_test_image('checkerboard', dsize=(2048, 2048)).astype(np.float32) image = kwimage.grab_test_image('astro', dsize=(2048, 2048)) transform = Affine.random() @ Affine.scale(0.01) """ from kwimage.transform import Affine import kwimage import numpy as np import ubelt as ub # Choose a random affine transform that probably has a small scale # transform = Affine.random() @ Affine.scale((0.3, 2)) # transform = Affine.scale((0.1, 1.2)) # transform = Affine.scale(0.05) transform = Affine.random() @ Affine.scale(0.01) # transform = Affine.random() image = kwimage.grab_test_image('astro') image = kwimage.grab_test_image('checkerboard') image = kwimage.ensure_float01(image) from kwimage import im_cv2 import kwarray import cv2 transform = Affine.coerce(transform) if 1 or dsize is None: h, w = image.shape[0:2] boxes = kwimage.Boxes(np.array([[0, 0, w, h]]), 'xywh') poly = boxes.to_polygons()[0] warped_poly = poly.warp(transform.matrix) warped_box = warped_poly.to_boxes().to_ltrb().quantize() dsize = tuple(map(int, warped_box.data[0, 2:4])) import timerit ti = timerit.Timerit(10, bestof=3, verbose=2) def _full_gauss_kernel(k0, sigma0, scale): num_downscales = np.log2(1 / scale) if num_downscales < 0: return 1, 0 # Define b0 = kernel size for one downsample operation b0 = 5 # Define sigma0 = sigma for one downsample operation sigma0 = 1 # The kernel size and sigma doubles for each 2x downsample k = int(np.ceil(b0 * (2 ** (num_downscales - 1)))) sigma = sigma0 * (2 ** (num_downscales - 1)) if k % 2 == 0: k += 1 return k, sigma def pyrDownK(a, k=1): assert k >= 0 for _ in range(k): a = cv2.pyrDown(a) return a for timer in ti.reset('naive'): with timer: interpolation = 'nearest' flags = im_cv2._coerce_interpolation(interpolation) final_v5 = cv2.warpAffine(image, transform.matrix[0:2], dsize=dsize, flags=flags) # -------------------- # METHOD 1 # for timer in ti.reset('resize+warp'): with timer: params = transform.decompose() sx, sy = params['scale'] noscale_params = ub.dict_diff(params, {'scale'}) noscale_warp = Affine.affine(**noscale_params) h, w = image.shape[0:2] resize_dsize = (int(np.ceil(sx * w)), int(np.ceil(sy * h))) downsampled = cv2.resize(image, dsize=resize_dsize, fx=sx, fy=sy, interpolation=cv2.INTER_AREA) interpolation = 'linear' flags = im_cv2._coerce_interpolation(interpolation) final_v1 = cv2.warpAffine(downsampled, noscale_warp.matrix[0:2], dsize=dsize, flags=flags) # -------------------- # METHOD 2 for timer in ti.reset('fullblur+warp'): with timer: k_x, sigma_x = _full_gauss_kernel(k0=5, sigma0=1, scale=sx) k_y, sigma_y = _full_gauss_kernel(k0=5, sigma0=1, scale=sy) image_ = image.copy() image_ = cv2.GaussianBlur(image_, (k_x, k_y), sigma_x, sigma_y) image_ = kwarray.atleast_nd(image_, 3) # image_ = image_.clip(0, 1) interpolation = 'linear' flags = im_cv2._coerce_interpolation(interpolation) final_v2 = cv2.warpAffine(image_, transform.matrix[0:2], dsize=dsize, flags=flags) # -------------------- # METHOD 3 for timer in ti.reset('pyrDown+blur+warp'): with timer: temp = image.copy() params = transform.decompose() sx, sy = params['scale'] biggest_scale = max(sx, sy) # The -2 allows the gaussian to be a little bigger. This # seems to help with border effects at only a small runtime cost num_downscales = max(int(np.log2(1 / biggest_scale)) - 2, 0) pyr_scale = 1 / (2 ** num_downscales) # Does the gaussian downsampling temp = pyrDownK(image, num_downscales) rest_sx = sx / pyr_scale rest_sy = sy / pyr_scale partial_scale = Affine.scale((rest_sx, rest_sy)) rest_warp = noscale_warp @ partial_scale k_x, sigma_x = _full_gauss_kernel(k0=5, sigma0=1, scale=rest_sx) k_y, sigma_y = _full_gauss_kernel(k0=5, sigma0=1, scale=rest_sy) temp = cv2.GaussianBlur(temp, (k_x, k_y), sigma_x, sigma_y) temp = kwarray.atleast_nd(temp, 3) interpolation = 'cubic' flags = im_cv2._coerce_interpolation(interpolation) final_v3 = cv2.warpAffine(temp, rest_warp.matrix[0:2], dsize=dsize, flags=flags) # -------------------- # METHOD 4 - dont do the final blur for timer in ti.reset('pyrDown+warp'): with timer: temp = image.copy() params = transform.decompose() sx, sy = params['scale'] biggest_scale = max(sx, sy) num_downscales = max(int(np.log2(1 / biggest_scale)), 0) pyr_scale = 1 / (2 ** num_downscales) # Does the gaussian downsampling temp = pyrDownK(image, num_downscales) rest_sx = sx / pyr_scale rest_sy = sy / pyr_scale partial_scale = Affine.scale((rest_sx, rest_sy)) rest_warp = noscale_warp @ partial_scale interpolation = 'linear' flags = im_cv2._coerce_interpolation(interpolation) final_v4 = cv2.warpAffine(temp, rest_warp.matrix[0:2], dsize=dsize, flags=flags) if 1: def get_title(key): from ubelt.timerit import _choose_unit value = ti.measures['mean'][key] suffix, mag = _choose_unit(value) unit_val = value / mag return key + ' ' + ub.repr2(unit_val, precision=2) + ' ' + suffix final_v2 = final_v2.clip(0, 1) final_v1 = final_v1.clip(0, 1) final_v3 = final_v3.clip(0, 1) final_v4 = final_v4.clip(0, 1) final_v5 = final_v5.clip(0, 1) import kwplot kwplot.autompl() kwplot.imshow(final_v5, pnum=(1, 5, 1), title=get_title('naive')) kwplot.imshow(final_v2, pnum=(1, 5, 2), title=get_title('fullblur+warp')) kwplot.imshow(final_v1, pnum=(1, 5, 3), title=get_title('resize+warp')) kwplot.imshow(final_v3, pnum=(1, 5, 4), title=get_title('pyrDown+blur+warp')) kwplot.imshow(final_v4, pnum=(1, 5, 5), title=get_title('pyrDown+warp'))