def aggregate_rvecs(rvecs, maws, error_flags): r""" Compute aggregated residual vectors Phi(X_c) CommandLine: python -m wbia.algo.smk.smk_funcs aggregate_rvecs --show Example: >>> # ENABLE_DOCTEST >>> from wbia.algo.smk.smk_funcs import * # NOQA >>> vecs, words = ut.take(testdata_rvecs(), ['vecs', 'words']) >>> word = words[-1] >>> rvecs, error_flags = compute_rvec(vecs, word) >>> maws = [1.0] * len(rvecs) >>> agg_rvec, agg_flag = aggregate_rvecs(rvecs, maws, error_flags) >>> ut.quit_if_noshow() >>> import wbia.plottool as pt >>> pt.qt4ensure() >>> pt.figure() >>> # recenter residuals for visualization >>> agg_cvec = agg_rvec + word >>> cvecs = (rvecs + word[None, :]) >>> pt.plot(word[0], word[1], 'r*', markersize=12, label='word') >>> pt.plot(agg_cvec[0], agg_cvec[1], 'ro', label='re-centered agg_rvec') >>> pt.plot(vecs.T[0], vecs.T[1], 'go', label='original vecs') >>> pt.plot(cvecs.T[0], cvecs.T[1], 'b.', label='re-centered rvec') >>> pt.draw_line_segments2([word] * len(cvecs), cvecs, alpha=.5, color='black') >>> pt.draw_line_segments2([word], [agg_cvec], alpha=.5, color='red') >>> pt.gca().set_aspect('equal') >>> pt.legend() >>> ut.show_if_requested() """ # Propogate errors from previous step agg_flag = np.any(error_flags, axis=0) if rvecs.shape[0] == 0: raise ValueError('cannot compute without rvecs') if rvecs.shape[0] == 1: # Efficiency shortcut agg_rvec = rvecs else: # Prealloc residual vector, take the weighted sum and renormalize. agg_rvec = np.empty(rvecs.shape[1], dtype=np.float32) out = agg_rvec if False: # Take weighted average of multi-assigned vectors coeff = np.divide(maws, maws.sum())[:, None] agg_rvec = (coeff * rvecs).sum(axis=0, out=out) else: # Don't consider multiple assignment weights agg_rvec = rvecs.sum(axis=0, out=out) is_zero = np.all(agg_rvec == 0) vt.normalize(agg_rvec, axis=0, out=agg_rvec) if is_zero: agg_flag = True return agg_rvec, agg_flag
def bow_vector(X, wx_to_weight, nwords): import vtool as vt wxs = sorted(list(X.wx_set)) tf = np.array(ut.take(X.termfreq, wxs)) idf = np.array(ut.take(wx_to_weight, wxs)) bow_ = tf * idf bow_ = vt.normalize(bow_) bow = SparseVector(dict(zip(wxs, bow_))) X.bow = bow
def compute_rvec(vecs, word): """ Compute residual vectors phi(x_c) Subtract each vector from its quantized word to get the resiudal, then normalize residuals to unit length. CommandLine: python -m wbia.algo.smk.smk_funcs compute_rvec --show Example: >>> # ENABLE_DOCTEST >>> from wbia.algo.smk.smk_funcs import * # NOQA >>> vecs, words = ut.take(testdata_rvecs(), ['vecs', 'words']) >>> word = words[-1] >>> rvecs, error_flags = compute_rvec(vecs, word) >>> ut.quit_if_noshow() >>> import wbia.plottool as pt >>> pt.figure() >>> # recenter residuals for visualization >>> cvecs = (rvecs + word[None, :]) >>> pt.plot(word[0], word[1], 'r*', markersize=12, label='word') >>> pt.plot(vecs.T[0], vecs.T[1], 'go', label='original vecs') >>> pt.plot(cvecs.T[0], cvecs.T[1], 'b.', label='re-centered rvec') >>> pt.draw_line_segments2(cvecs, [word] * len(cvecs), alpha=.5, color='black') >>> pt.gca().set_aspect('equal') >>> pt.legend() >>> ut.show_if_requested() """ rvecs = np.subtract(vecs.astype(np.float32), word.astype(np.float32)) # If a vec is a word then the residual is 0 and it cant be L2 noramlized. error_flags = np.all(rvecs == 0, axis=1) vt.normalize(rvecs, axis=1, out=rvecs) # reset these values to zero if np.any(error_flags): rvecs[error_flags, :] = 0 return rvecs, error_flags
def run_asmk_script(): with ut.embed_on_exception_context: # NOQA """ >>> from wbia.algo.smk.script_smk import * """ # NOQA # ============================================== # PREPROCESSING CONFIGURATION # ============================================== config = { # 'data_year': 2013, 'data_year': None, 'dtype': 'float32', # 'root_sift': True, 'root_sift': False, # 'centering': True, 'centering': False, 'num_words': 2**16, # 'num_words': 1E6 # 'num_words': 8000, 'kmeans_impl': 'sklearn.mini', 'extern_words': False, 'extern_assign': False, 'assign_algo': 'kdtree', 'checks': 1024, 'int_rvec': True, 'only_xy': False, } # Define which params are relevant for which operations relevance = {} relevance['feats'] = ['dtype', 'root_sift', 'centering', 'data_year'] relevance['words'] = relevance['feats'] + [ 'num_words', 'extern_words', 'kmeans_impl', ] relevance['assign'] = relevance['words'] + [ 'checks', 'extern_assign', 'assign_algo', ] # relevance['ydata'] = relevance['assign'] + ['int_rvec'] # relevance['xdata'] = relevance['assign'] + ['only_xy', 'int_rvec'] nAssign = 1 class SMKCacher(ut.Cacher): def __init__(self, fname, ext='.cPkl'): relevant_params = relevance[fname] relevant_cfg = ut.dict_subset(config, relevant_params) cfgstr = ut.get_cfg_lbl(relevant_cfg) dbdir = ut.truepath('/raid/work/Oxford/') super(SMKCacher, self).__init__(fname, cfgstr, cache_dir=dbdir, ext=ext) # ============================================== # LOAD DATASET, EXTRACT AND POSTPROCESS FEATURES # ============================================== if config['data_year'] == 2007: data = load_oxford_2007() elif config['data_year'] == 2013: data = load_oxford_2013() elif config['data_year'] is None: data = load_oxford_wbia() offset_list = data['offset_list'] all_kpts = data['all_kpts'] raw_vecs = data['all_vecs'] query_uri_order = data['query_uri_order'] data_uri_order = data['data_uri_order'] # del data # ================ # PRE-PROCESS # ================ import vtool as vt # Alias names to avoid errors in interactive sessions proc_vecs = raw_vecs del raw_vecs feats_cacher = SMKCacher('feats', ext='.npy') all_vecs = feats_cacher.tryload() if all_vecs is None: if config['dtype'] == 'float32': logger.info('Converting vecs to float32') proc_vecs = proc_vecs.astype(np.float32) else: proc_vecs = proc_vecs raise NotImplementedError('other dtype') if config['root_sift']: with ut.Timer('Apply root sift'): np.sqrt(proc_vecs, out=proc_vecs) vt.normalize(proc_vecs, ord=2, axis=1, out=proc_vecs) if config['centering']: with ut.Timer('Apply centering'): mean_vec = np.mean(proc_vecs, axis=0) # Center and then re-normalize np.subtract(proc_vecs, mean_vec[None, :], out=proc_vecs) vt.normalize(proc_vecs, ord=2, axis=1, out=proc_vecs) if config['dtype'] == 'int8': smk_funcs all_vecs = proc_vecs feats_cacher.save(all_vecs) del proc_vecs # ===================================== # BUILD VISUAL VOCABULARY # ===================================== if config['extern_words']: words = data['words'] assert config['num_words'] is None or len( words) == config['num_words'] else: word_cacher = SMKCacher('words') words = word_cacher.tryload() if words is None: with ut.embed_on_exception_context: if config['kmeans_impl'] == 'sklearn.mini': import sklearn.cluster rng = np.random.RandomState(13421421) # init_size = int(config['num_words'] * 8) init_size = int(config['num_words'] * 4) # converged after 26043 iterations clusterer = sklearn.cluster.MiniBatchKMeans( config['num_words'], init_size=init_size, batch_size=1000, compute_labels=False, max_iter=20, random_state=rng, n_init=1, verbose=1, ) clusterer.fit(all_vecs) words = clusterer.cluster_centers_ elif config['kmeans_impl'] == 'yael': from yael import ynumpy centroids, qerr, dis, assign, nassign = ynumpy.kmeans( all_vecs, config['num_words'], init='kmeans++', verbose=True, output='all', ) words = centroids word_cacher.save(words) # ===================================== # ASSIGN EACH VECTOR TO ITS NEAREST WORD # ===================================== if config['extern_assign']: assert config[ 'extern_words'], 'need extern cluster to extern assign' idx_to_wxs = vt.atleast_nd(data['idx_to_wx'], 2) idx_to_maws = np.ones(idx_to_wxs.shape, dtype=np.float32) idx_to_wxs = np.ma.array(idx_to_wxs) idx_to_maws = np.ma.array(idx_to_maws) else: from wbia.algo.smk import vocab_indexer vocab = vocab_indexer.VisualVocab(words) dassign_cacher = SMKCacher('assign') assign_tup = dassign_cacher.tryload() if assign_tup is None: vocab.flann_params['algorithm'] = config['assign_algo'] vocab.build() # Takes 12 minutes to assign jegous vecs to 2**16 vocab with ut.Timer('assign vocab neighbors'): _idx_to_wx, _idx_to_wdist = vocab.nn_index( all_vecs, nAssign, checks=config['checks']) if nAssign > 1: idx_to_wxs, idx_to_maws = smk_funcs.weight_multi_assigns( _idx_to_wx, _idx_to_wdist, massign_alpha=1.2, massign_sigma=80.0, massign_equal_weights=True, ) else: idx_to_wxs = np.ma.masked_array(_idx_to_wx, fill_value=-1) idx_to_maws = np.ma.ones(idx_to_wxs.shape, fill_value=-1, dtype=np.float32) idx_to_maws.mask = idx_to_wxs.mask assign_tup = (idx_to_wxs, idx_to_maws) dassign_cacher.save(assign_tup) idx_to_wxs, idx_to_maws = assign_tup # Breakup vectors, keypoints, and word assignments by annotation wx_lists = [ idx_to_wxs[left:right] for left, right in ut.itertwo(offset_list) ] maw_lists = [ idx_to_maws[left:right] for left, right in ut.itertwo(offset_list) ] vecs_list = [ all_vecs[left:right] for left, right in ut.itertwo(offset_list) ] kpts_list = [ all_kpts[left:right] for left, right in ut.itertwo(offset_list) ] # ======================= # FIND QUERY SUBREGIONS # ======================= ibs, query_annots, data_annots, qx_to_dx = load_ordered_annots( data_uri_order, query_uri_order) daids = data_annots.aids qaids = query_annots.aids query_super_kpts = ut.take(kpts_list, qx_to_dx) query_super_vecs = ut.take(vecs_list, qx_to_dx) query_super_wxs = ut.take(wx_lists, qx_to_dx) query_super_maws = ut.take(maw_lists, qx_to_dx) # Mark which keypoints are within the bbox of the query query_flags_list = [] only_xy = config['only_xy'] for kpts_, bbox in zip(query_super_kpts, query_annots.bboxes): flags = kpts_inside_bbox(kpts_, bbox, only_xy=only_xy) query_flags_list.append(flags) logger.info('Queries are crops of existing database images.') logger.info('Looking at average percents') percent_list = [ flags_.sum() / flags_.shape[0] for flags_ in query_flags_list ] percent_stats = ut.get_stats(percent_list) logger.info('percent_stats = %s' % (ut.repr4(percent_stats), )) import vtool as vt query_kpts = vt.zipcompress(query_super_kpts, query_flags_list, axis=0) query_vecs = vt.zipcompress(query_super_vecs, query_flags_list, axis=0) query_wxs = vt.zipcompress(query_super_wxs, query_flags_list, axis=0) query_maws = vt.zipcompress(query_super_maws, query_flags_list, axis=0) # ======================= # CONSTRUCT QUERY / DATABASE REPR # ======================= # int_rvec = not config['dtype'].startswith('float') int_rvec = config['int_rvec'] X_list = [] _prog = ut.ProgPartial(length=len(qaids), label='new X', bs=True, adjust=True) for aid, fx_to_wxs, fx_to_maws in _prog( zip(qaids, query_wxs, query_maws)): X = new_external_annot(aid, fx_to_wxs, fx_to_maws, int_rvec) X_list.append(X) # ydata_cacher = SMKCacher('ydata') # Y_list = ydata_cacher.tryload() # if Y_list is None: Y_list = [] _prog = ut.ProgPartial(length=len(daids), label='new Y', bs=True, adjust=True) for aid, fx_to_wxs, fx_to_maws in _prog(zip(daids, wx_lists, maw_lists)): Y = new_external_annot(aid, fx_to_wxs, fx_to_maws, int_rvec) Y_list.append(Y) # ydata_cacher.save(Y_list) # ====================== # Add in some groundtruth logger.info('Add in some groundtruth') for Y, nid in zip(Y_list, ibs.get_annot_nids(daids)): Y.nid = nid for X, nid in zip(X_list, ibs.get_annot_nids(qaids)): X.nid = nid for Y, qual in zip(Y_list, ibs.get_annot_quality_texts(daids)): Y.qual = qual # ====================== # Add in other properties for Y, vecs, kpts in zip(Y_list, vecs_list, kpts_list): Y.vecs = vecs Y.kpts = kpts imgdir = ut.truepath('/raid/work/Oxford/oxbuild_images') for Y, imgid in zip(Y_list, data_uri_order): gpath = ut.unixjoin(imgdir, imgid + '.jpg') Y.gpath = gpath for X, vecs, kpts in zip(X_list, query_vecs, query_kpts): X.kpts = kpts X.vecs = vecs # ====================== logger.info('Building inverted list') daids = [Y.aid for Y in Y_list] # wx_list = sorted(ut.list_union(*[Y.wx_list for Y in Y_list])) wx_list = sorted(set.union(*[Y.wx_set for Y in Y_list])) assert daids == data_annots.aids assert len(wx_list) <= config['num_words'] wx_to_aids = smk_funcs.invert_lists(daids, [Y.wx_list for Y in Y_list], all_wxs=wx_list) # Compute IDF weights logger.info('Compute IDF weights') ndocs_total = len(daids) # Use only the unique number of words ndocs_per_word = np.array([len(set(wx_to_aids[wx])) for wx in wx_list]) logger.info('ndocs_perword stats: ' + ut.repr4(ut.get_stats(ndocs_per_word))) idf_per_word = smk_funcs.inv_doc_freq(ndocs_total, ndocs_per_word) wx_to_weight = dict(zip(wx_list, idf_per_word)) logger.info('idf stats: ' + ut.repr4(ut.get_stats(wx_to_weight.values()))) # Filter junk Y_list_ = [Y for Y in Y_list if Y.qual != 'junk'] # ======================= # CHOOSE QUERY KERNEL # ======================= params = { 'asmk': dict(alpha=3.0, thresh=0.0), 'bow': dict(), 'bow2': dict(), } # method = 'bow' method = 'bow2' method = 'asmk' smk = SMK(wx_to_weight, method=method, **params[method]) # Specific info for the type of query if method == 'asmk': # Make residual vectors if True: # The stacked way is 50x faster # TODO: extend for multi-assignment and record fxs flat_query_vecs = np.vstack(query_vecs) flat_query_wxs = np.vstack(query_wxs) flat_query_offsets = np.array( [0] + ut.cumsum(ut.lmap(len, query_wxs))) flat_wxs_assign = flat_query_wxs flat_offsets = flat_query_offsets flat_vecs = flat_query_vecs tup = smk_funcs.compute_stacked_agg_rvecs( words, flat_wxs_assign, flat_vecs, flat_offsets) all_agg_vecs, all_error_flags, agg_offset_list = tup if int_rvec: all_agg_vecs = smk_funcs.cast_residual_integer( all_agg_vecs) agg_rvecs_list = [ all_agg_vecs[left:right] for left, right in ut.itertwo(agg_offset_list) ] agg_flags_list = [ all_error_flags[left:right] for left, right in ut.itertwo(agg_offset_list) ] for X, agg_rvecs, agg_flags in zip(X_list, agg_rvecs_list, agg_flags_list): X.agg_rvecs = agg_rvecs X.agg_flags = agg_flags[:, None] flat_wxs_assign = idx_to_wxs flat_offsets = offset_list flat_vecs = all_vecs tup = smk_funcs.compute_stacked_agg_rvecs( words, flat_wxs_assign, flat_vecs, flat_offsets) all_agg_vecs, all_error_flags, agg_offset_list = tup if int_rvec: all_agg_vecs = smk_funcs.cast_residual_integer( all_agg_vecs) agg_rvecs_list = [ all_agg_vecs[left:right] for left, right in ut.itertwo(agg_offset_list) ] agg_flags_list = [ all_error_flags[left:right] for left, right in ut.itertwo(agg_offset_list) ] for Y, agg_rvecs, agg_flags in zip(Y_list, agg_rvecs_list, agg_flags_list): Y.agg_rvecs = agg_rvecs Y.agg_flags = agg_flags[:, None] else: # This non-stacked way is about 500x slower _prog = ut.ProgPartial(label='agg Y rvecs', bs=True, adjust=True) for Y in _prog(Y_list_): make_agg_vecs(Y, words, Y.vecs) _prog = ut.ProgPartial(label='agg X rvecs', bs=True, adjust=True) for X in _prog(X_list): make_agg_vecs(X, words, X.vecs) elif method == 'bow2': # Hack for orig tf-idf bow vector nwords = len(words) for X in ut.ProgIter(X_list, label='make bow vector'): ensure_tf(X) bow_vector(X, wx_to_weight, nwords) for Y in ut.ProgIter(Y_list_, label='make bow vector'): ensure_tf(Y) bow_vector(Y, wx_to_weight, nwords) if method != 'bow2': for X in ut.ProgIter(X_list, 'compute X gamma'): X.gamma = smk.gamma(X) for Y in ut.ProgIter(Y_list_, 'compute Y gamma'): Y.gamma = smk.gamma(Y) # Execute matches (could go faster by enumerating candidates) scores_list = [] for X in ut.ProgIter(X_list, label='query %s' % (smk, )): scores = [smk.kernel(X, Y) for Y in Y_list_] scores = np.array(scores) scores = np.nan_to_num(scores) scores_list.append(scores) import sklearn.metrics avep_list = [] _iter = list(zip(scores_list, X_list)) _iter = ut.ProgIter(_iter, label='evaluate %s' % (smk, )) for scores, X in _iter: truth = [X.nid == Y.nid for Y in Y_list_] avep = sklearn.metrics.average_precision_score(truth, scores) avep_list.append(avep) avep_list = np.array(avep_list) mAP = np.mean(avep_list) logger.info('mAP = %r' % (mAP, ))
def compute_stacked_agg_rvecs(words, flat_wxs_assign, flat_vecs, flat_offsets): """ More efficient version of agg on a stacked structure Args: words (ndarray): entire vocabulary of words flat_wxs_assign (ndarray): maps a stacked index to word index flat_vecs (ndarray): stacked SIFT descriptors flat_offsets (ndarray): offset positions per annotation Example: >>> # DISABLE_DOCTEST >>> from wbia.algo.smk.smk_funcs import * # NOQA >>> data = testdata_rvecs(dim=2, nvecs=1000, nannots=10) >>> words = data['words'] >>> flat_offsets = data['offset_list'] >>> flat_wxs_assign, flat_vecs = ut.take(data, ['idx_to_wx', 'vecs']) >>> tup = compute_stacked_agg_rvecs(words, flat_wxs_assign, flat_vecs, flat_offsets) >>> all_agg_vecs, all_error_flags, agg_offset_list = tup >>> agg_rvecs_list = [all_agg_vecs[l:r] for l, r in ut.itertwo(agg_offset_list)] >>> agg_flags_list = [all_error_flags[l:r] for l, r in ut.itertwo(agg_offset_list)] >>> assert len(agg_flags_list) == len(flat_offsets) - 1 Example: >>> # DISABLE_DOCTEST >>> from wbia.algo.smk.smk_funcs import * # NOQA >>> data = testdata_rvecs(dim=2, nvecs=100, nannots=5) >>> words = data['words'] >>> flat_offsets = data['offset_list'] >>> flat_wxs_assign, flat_vecs = ut.take(data, ['idx_to_wx', 'vecs']) >>> tup = compute_stacked_agg_rvecs(words, flat_wxs_assign, flat_vecs, flat_offsets) >>> all_agg_vecs, all_error_flags, agg_offset_list = tup >>> agg_rvecs_list = [all_agg_vecs[l:r] for l, r in ut.itertwo(agg_offset_list)] >>> agg_flags_list = [all_error_flags[l:r] for l, r in ut.itertwo(agg_offset_list)] >>> assert len(agg_flags_list) == len(flat_offsets) - 1 """ grouped_wxs = [ flat_wxs_assign[left:right] for left, right in ut.itertwo(flat_offsets) ] # Assume single assignment, aggregate everything # across the entire database flat_offsets = np.array(flat_offsets) idx_to_dx = (np.searchsorted( flat_offsets, np.arange(len(flat_wxs_assign)), side='right') - 1).astype(np.int32) if isinstance(flat_wxs_assign, np.ma.masked_array): wx_list = flat_wxs_assign.T[0].compressed() else: wx_list = flat_wxs_assign.T[0].ravel() unique_wx, groupxs = vt.group_indices(wx_list) dim = flat_vecs.shape[1] if isinstance(flat_wxs_assign, np.ma.masked_array): dx_to_wxs = [np.unique(wxs.compressed()) for wxs in grouped_wxs] else: dx_to_wxs = [np.unique(wxs.ravel()) for wxs in grouped_wxs] dx_to_nagg = [len(wxs) for wxs in dx_to_wxs] num_agg_vecs = sum(dx_to_nagg) # all_agg_wxs = np.hstack(dx_to_wxs) agg_offset_list = np.array([0] + ut.cumsum(dx_to_nagg)) # Preallocate agg residuals for all dxs all_agg_vecs = np.empty((num_agg_vecs, dim), dtype=np.float32) all_agg_vecs[:, :] = np.nan # precompute agg residual stack i_to_dxs = vt.apply_grouping(idx_to_dx, groupxs) subgroup = [vt.group_indices(dxs) for dxs in ut.ProgIter(i_to_dxs)] i_to_unique_dxs = ut.take_column(subgroup, 0) i_to_dx_groupxs = ut.take_column(subgroup, 1) num_words = len(unique_wx) # Overall this takes 5 minutes and 21 seconds # I think the other method takes about 12 minutes for i in ut.ProgIter(range(num_words), 'agg'): wx = unique_wx[i] xs = groupxs[i] dxs = i_to_unique_dxs[i] dx_groupxs = i_to_dx_groupxs[i] word = words[wx:wx + 1] offsets1 = agg_offset_list.take(dxs) offsets2 = [np.where(dx_to_wxs[dx] == wx)[0][0] for dx in dxs] offsets = np.add(offsets1, offsets2, out=offsets1) # if __debug__: # assert np.bincount(dxs).max() < 2 # offset = agg_offset_list[dxs[0]] # assert np.all(dx_to_wxs[dxs[0]] == all_agg_wxs[offset:offset + # dx_to_nagg[dxs[0]]]) # Compute residuals rvecs = flat_vecs[xs] - word vt.normalize(rvecs, axis=1, out=rvecs) rvecs[np.all(np.isnan(rvecs), axis=1)] = 0 # Aggregate across same images grouped_rvecs = vt.apply_grouping(rvecs, dx_groupxs, axis=0) agg_rvecs_ = [rvec_group.sum(axis=0) for rvec_group in grouped_rvecs] # agg_rvecs = np.vstack(agg_rvecs_) all_agg_vecs[offsets, :] = agg_rvecs_ assert not np.any(np.isnan(all_agg_vecs)) logger.info('Apply normalization') vt.normalize(all_agg_vecs, axis=1, out=all_agg_vecs) all_error_flags = np.all(np.isnan(all_agg_vecs), axis=1) all_agg_vecs[all_error_flags, :] = 0 # ndocs_per_word1 = np.array(ut.lmap(len, wx_to_unique_dxs)) # ndocs_total1 = len(flat_offsets) - 1 # idf1 = smk_funcs.inv_doc_freq(ndocs_total1, ndocs_per_word1) tup = all_agg_vecs, all_error_flags, agg_offset_list return tup
def sift_test(): """ Play with SIFT equations using python so I can see and compare results. """ import numpy as np # Sample measurement from lena sift_raw = np.array([ 48.0168, 130.017, 159.065, 54.5727, 63.7103, 14.3629, 27.0228, 15.3527, 40.5067, 165.721, 511.036, 196.888, 4.72748, 8.85093, 15.9457, 14.4198, 49.7571, 209.104, 452.047, 223.972, 2.66391, 16.8975, 21.7488, 13.6855, 0.700244, 10.2518, 312.483, 282.647, 1.82898, 3.01759, 0.448028, 0, 144.834, 300.438, 131.837, 40.3284, 11.1998, 9.68647, 7.68484, 29.166, 425.953, 386.903, 352.388, 267.883, 12.9652, 18.833, 8.55462, 71.7924, 112.282, 295.512, 678.599, 419.405, 21.3151, 91.9408, 22.8681, 9.83749, 3.06347, 97.6562, 458.799, 221.873, 68.1473, 410.764, 48.9493, 2.01682, 194.794, 43.7171, 16.2078, 17.5604, 48.8504, 48.3823, 45.7636, 299.432, 901.565, 188.732, 32.6512, 23.6874, 55.379, 272.264, 68.2334, 221.37, 159.631, 44.1475, 126.636, 95.1978, 74.1097, 1353.24, 239.319, 33.5368, 5.62254, 69.0013, 51.7629, 9.55458, 26.4599, 699.623, 208.78, 2.09156, 135.278, 19.5378, 52.0265, 51.8445, 49.1938, 9.04161, 11.6605, 87.4498, 604.012, 85.6801, 42.9738, 75.8549, 183.65, 206.912, 34.2781, 95.0146, 13.4201, 83.7426, 440.322, 83.0038, 125.663, 457.333, 52.6424, 4.93713, 0.38947, 244.762, 291.113, 7.50165, 8.16208, 73.2169, 21.9674, 0.00429259, ]) import vtool as vt # CONFIRMED: One normalization followed by another does not do anything #sift_root1 = vt.normalize(sift_root1, ord=2) #sift_root1 = vt.normalize(sift_root1, ord=1) sift_clip = sift_raw.copy() sift_clip = vt.normalize(sift_clip, ord=2) sift_clip[sift_clip > .2] = .2 sift_clip = vt.normalize(sift_clip, ord=2) siff_ell2 = vt.normalize(sift_raw, ord=2) siff_ell1 = vt.normalize(sift_raw, ord=1) # Two versions of root SIFT # They are equlivalent # taken from https://hal.inria.fr/hal-00840721/PDF/RR-8325.pdf normalize1 = lambda x: vt.normalize(x, ord=1) # NOQA normalize2 = lambda x: vt.normalize(x, ord=2) # NOQA assert np.all( np.isclose(np.sqrt(normalize1(sift_raw)), normalize2(np.sqrt(sift_raw)))) # How do we genralize this for alpha != .5? # Just always L2 normalize afterwords? alpha = .2 powerlaw = lambda x: np.power(x, alpha) # NOQA sift_root1 = normalize2(powerlaw(normalize1(sift_raw))) sift_root2 = normalize2(powerlaw(sift_raw)) flags = np.isclose(sift_root1, sift_root2) print(flags) assert np.all(flags) #sift_root_quant = np.clip((sift_root1 * 512), 0, 255).astype(np.uint8) #p = (np.bincount(sift_root_quant) / 128) #entropy = -np.nansum(p * np.log2(p)) s = sift_raw[0:10] np.sqrt(s) / (np.sqrt(s).sum()**2) np.power(normalize1(s), 2) #b = powerlaw(normalize1(s)) #print(np.isclose(a, b)) np.isclose(normalize1(s), normalize1(normalize2(s))) # Another root SIFT version from # https://hal.inria.fr/hal-00688169/document # but this doesnt seem to work with uint8 representations sift_root3 = np.sqrt(sift_raw) sift_root3 = sift_root3 / np.sqrt(np.linalg.norm(sift_root3)) import plottool as pt import utool as ut ut.qtensure() fig = pt.figure(fnum=1, pnum=None) def draw_sift(sift, pnum, title, **kwargs): ax = fig.add_subplot(*pnum) pt.draw_sifts(ax, sift[None, :], **kwargs) ax.set_xlim(-1, 1) ax.set_ylim(-1, 1) ax.grid(False) ax.set_aspect('equal') ax.set_xticks([]) ax.set_yticks([]) if title: ax.set_title(title) fig.clf() pnum_ = pt.make_pnum_nextgen(2, 4) draw_sift(sift_raw, pnum_(), 'raw/max(raw)', fidelity=sift_raw.max()) draw_sift(sift_clip, pnum_(), 'clip', fidelity=1.0) draw_sift(siff_ell2, pnum_(), 'l2', fidelity=1.0) draw_sift(siff_ell1, pnum_(), 'l1', fidelity=1.0) draw_sift(sift_root1, pnum_(), 'root1', fidelity=1.0) draw_sift(sift_root2, pnum_(), 'root2', fidelity=1.0) draw_sift(sift_root3, pnum_(), 'root3', fidelity=2.0)