def test_aitchison_norm(): """Test Aitchison norm.""" # Given data = np.array([1/3, 1/3, 1/3]) # When output = coda.aitchison_norm(data[None, :]) # Then assert output == pytest.approx(0)
def simplex_color_balance(bary, center=True, standardize=False, trunc_max=False): """Compositional data based method for color balance. Highly experimental! Parameters ---------- bary: numpy.ndarray Barycentric coordinates. Sum of all channels should add up to 1 (closed). center: bool Center barycentric coordinates (similar to de-meaning). Single-hue dominated images will be balanced to cover all hues. standardize: bool Standardize barycentric coordinates. Standardized compositions make better use of the simplex space dynamic range. trunc_max: bool Truncate maximum barycentric coordinates to eliminate extreme hues that are not prevalent in the image. Returns ------- bary: numpy.ndarray Processed composition. """ dims = bary.shape bary = bary.reshape([np.prod(dims[:-1]), dims[-1]]) bary = np.nan_to_num(bary) # Do not consider values <= 0 mask = np.prod(bary, axis=-1) mask = mask > 0 temp = bary[mask] # Interpretation of centering: Compositions cover the simplex space more # balanced across components. Similar to de-mean data. if center: sample_center = coda.sample_center(temp) temp2 = np.full(temp.shape, sample_center) temp = coda.perturb(temp, temp2**-1.) temp2 = None # Interpretation of standardization: Centered compositions cover the # dynamic range of simplex space more. if standardize: # Standardize totvar = coda.sample_total_variance(temp, sample_center) temp = coda.power(temp, np.power(totvar, -1./2.)) # Interpretation of max truncation: Pull the exterme compositions to # threshold distance by using scaling. Scaling factor is determined for # each outlier composition to pull more extreme compositions more strongly. if trunc_max: # Use Aitchison norm and powering to truncate extreme compositions anorm = coda.aitchison_norm(temp) anorm_thr_min, anorm_thr_max = np.percentile(anorm, [1., 99.]) # Max truncate idx_trunc = anorm > anorm_thr_max truncation_power = anorm[idx_trunc] / anorm_thr_max correction = np.ones(anorm.shape) correction[idx_trunc] = truncation_power temp = coda.power(temp, correction[:, None]) # min truncate idx_trunc = anorm < anorm_thr_min truncation_power = anorm[idx_trunc] / anorm_thr_min correction = np.ones(anorm.shape) correction[idx_trunc] = truncation_power temp = coda.power(temp, correction[:, None]) # TODO: Implement this similar to truncate and scpe function but for # simplex space. Proportion of dynamic range to the distance of between # aitchison norm percentiles gives the global scaling factor. This should # be done after truncation though. # Put back processed composition bary[mask] = temp return bary.reshape(dims)
# Do not consider masked values p_mask = mask.reshape(dims[0] * dims[1] * dims[2]) p_comp = comp[p_mask > 0] # Centering center = coda.sample_center(p_comp) temp = np.ones(p_comp.shape) * center p_comp = coda.perturb(p_comp, temp**-1.) # Standardize totvar = coda.sample_total_variance(p_comp, center) p_comp = coda.power(p_comp, np.power(totvar, -1. / 2.)) # Use Aitchison norm and powerinf for truncation of extreme compositions anorm_thr = 3 anorm = coda.aitchison_norm(p_comp) idx_trunc = anorm > anorm_thr truncation_power = anorm[idx_trunc] / anorm_thr correction = np.ones(anorm.shape) correction[idx_trunc] = truncation_power comp_bal = coda.power(p_comp, correction[:, None]) # go to hexcone lattice for exports comp[p_mask > 0] = comp_bal # lightness balance light = truncate_range(light, percMin=1, percMax=99) hexc = comp * light[:, None] hexc = hexc.reshape(dims[0], dims[1], dims[2], dims[3]) for i in range(hexc.shape[3]):
# temp = truncate_range(temp) # temp = scale_range(temp, scale_factor=1000) # comp[:, i] = temp # Impute comp[comp == 0] = 1. # Closure comp = coda.closure(comp) # Aitchison inner product ref = np.ones(comp.shape) ref[:, 1] = ref[:, 1] * 10 ref = coda.closure(ref) ip = coda.aitchison_inner_product(comp, ref) cos_theta = ip / (coda.aitchison_norm(comp) * coda.aitchison_norm(ref)) rad = np.arccos(cos_theta) deg = rad * (360 / (2*np.pi)) # # Determine sector # idx_sector2 = comp[:, 1] > comp[:, 2] # deg[idx_sector2] = 360. - deg[idx_sector2] # Create output out = np.zeros(nii1.shape) out[idx_mask] = deg img = Nifti1Image(out, affine=nii1.affine) save(img, os.path.join(dirname, 'theta2.nii.gz')) print('Finished.')
dims = img.shape # impute zeros idx1 = (img[..., 0] <= 0) + (img[..., 1] <= 0) + (img[..., 2] <= 0) gauss = gaussian(img, sigma=0.5, multichannel=True) img[idx1, :] = gauss[idx1, :] # rgb to hsv for control hsv = color.rgb2hsv(img) hsv[..., 0] = -np.abs(hsv[..., 0] - 0.5) + 0.5 # coda stuff angdif_norm_int = np.zeros(img.shape) temp = img.reshape(dims[0] * dims[1], dims[2]) * 1.0 bary = coda.closure(temp, 100) anorm = coda.aitchison_norm(bary) # prepare reference vector for angular difference ref = np.ones(bary.shape) ref[:, 0] = ref[:, 0] * 0.8 ref[:, 1] = ref[:, 1] * 0.1 ref[:, 2] = ref[:, 2] * 0.1 # compute aitchion angular difference ainp = coda.aitchison_inner_product(bary, ref) ref_norm = coda.aitchison_norm(ref) ang_dif = np.zeros(anorm.shape) # deal with zero norms idx = anorm != 0 # (choose one) wrapped angle range ang_dif[idx] = np.arccos(ainp[idx]/(anorm[idx] * ref_norm[idx])) ang_dif[np.isnan(ang_dif)] = 0 # fix nans assigned to bright