Skip to content

Instantly share code, notes, and snippets.

@Miladiouss
Last active July 21, 2020 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Miladiouss/86bed5c53595116dfe18ee0a5c84f838 to your computer and use it in GitHub Desktop.
Save Miladiouss/86bed5c53595116dfe18ee0a5c84f838 to your computer and use it in GitHub Desktop.
Image Histogram Transformation.
import numpy as np
from matplotlib import pyplot as plt
import torch
class HistogramTransform(object):
"""
Transforms the distribution of the input tensor to match that
of the list of template histograms corresponding to each channel.
A template historgram must be set initially.
Args:
tensor (numpy.ndarray):
Image to transform; the histogram is computed over the flattened
array
noise_range (float): Default is 0. A uniform noise ranged between
(-noise_range, +noise_range) will be added to pixels randomly.
Returns:
histogram transformed tensor:
The output tensor type matches the input, either numpy.ndarray or torch.Tensor.
"""
def __init__(self, template_histograms):
'''
Args:
template_histograms:
A list of template histograms.
Each template histogram must consist of the tuple
(counts (numpy.ndarray), bins (numpy.ndarray)).
template_histograms is a list of numpy.histogram outputs,
each corresponding to each channel of the input tensor to be transformed.
If 1 channel, still feed as a list, i.e. [(counts, bin)].
Example:
Assuming img is made by ToTensor(some pil image) and has 3 (RGB) channels, one can get the histogram as such:
histR = np.histogram(img[0].numpy().ravel(), bins = 256, range = [0, 1])
histG = np.histogram(img[1].numpy().ravel(), bins = 256, range = [0, 1])
histB = np.histogram(img[2].numpy().ravel(), bins = 256, range = [0, 1])
'''
self.template_histograms = template_histograms
self.num_channels = len(template_histograms)
def __call__(self, tensor, noise_range = 0, dtype = torch.float32):
"""
Transforms the distribution of the input tensor to match that
of the template histogram. If a list of histograms is provided
and it maches the number of channels of the input tensor, each
channel will be transformed with the corresponding histogram.
This funciton utilises histogram_tranform_1D for an easier user interface.
Args:
tensor (numpy.ndarray):
Image to transform; the histogram is computed over the
flattened array for each channel.
noise_range (float): Default is 0. A uniform noise ranged between
(-noise_range, +noise_range) will be added to pixels randomly.
Returns:
histogram transformed tensor:
The output tensor type matches the input, either numpy.ndarray or torch.Tensor.
"""
tensorType = type(tensor)
tensor = np.asanyarray(tensor)
channels = []
for c, templateHisto in enumerate(self.template_histograms):
channels.append(self.histogram_transform_1D(tensor[c], templateHisto, noise_range = noise_range))
transformed_tensor = np.asanyarray(channels)
# Convert to the original type
if tensorType == torch.Tensor:
transformed_tensor = torch.tensor(transformed_tensor, dtype=dtype)
return transformed_tensor
# Core of the computation, to be used by histogram_transform method internally
def histogram_transform_1D(self, tensor, template_histogram, noise_range = 0):
"""
Transforms the distribution of the input tensor to match that
of the template histogram.
Input tensor will be flattened, transformed, and rearranged
to the original shape.
Mainly intended for call by class functions.
Args:
tensor (numpy.ndarray): Image to transform; the histogram is computed
over the flattened array.
template_histogram (tubple of (numpy.ndarray, numpy.ndarray)):
The template histogram consisiting of a tuple of (counts, bins).
See (the output of) numpy.histogram.
noise_range (float): Default is 0. A uniform noise ranged between
(-noise_range, +noise_range) will be added to pixels randomly.
Returns:
histogram transformed array (numpy.ndarray):
The transformed output tensor/image that maches the input
"""
# === Template Histogram ===
# t_... stands for template_
t_counts, t_bins = template_histogram
# t_bin_idx not required
# Take the cumsum of the counts and normalize by the number of pixels to
# Get the empirical cumulative distribution functions
# (maps value --> quantile)
t_quantiles = np.cumsum(t_counts).astype(np.float32)
t_quantiles /= t_quantiles[-1]
# === Input Tensor ===
# Convert to flattened numpy array
tensor = np.asanyarray(tensor)
originalShape = tensor.shape
tensor = tensor.ravel()
# Get counts, bins, and corresponding bin indices for each tensor value
counts, bins = np.histogram(tensor, bins = t_bins)
bin_idx = np.searchsorted(t_bins[:-2], tensor)
# See comments for t_quantiles
quantiles = np.cumsum(counts).astype(np.float32)
quantiles /= quantiles[-1]
# === Histogram Transformation ===
# interpolate linearly to find the pixel values in the template image
# that corresponds most closely to the quantiles for the input tensor
interp_t_values = np.interp(quantiles, t_quantiles, t_bins[:-1])
tensor_transformed = interp_t_values[bin_idx]
noise = np.random.uniform(
low=-noise_range,
high=+noise_range,
size=(len(tensor_transformed))
)
tensor_transformed += noise
tensor_transformed = np.maximum(tensor_transformed, min(t_bins))
return tensor_transformed.reshape(originalShape)
def histo_plot(img, plt_name = ""):
"""
Calculates and plots the histogram of the input image.
img must be a CHW np.array bounded between 0 and 1.
"""
img = np.asanyarray(img)
histR = np.histogram(img[0].ravel(), bins = 256, range = [0, 1])
histB = np.histogram(img[1].ravel(), bins = 256, range = [0, 1])
histG = np.histogram(img[2].ravel(), bins = 256, range = [0, 1])
fig, (ax_left, ax_right) = plt.subplots(
nrows=1,
ncols=2,
gridspec_kw = {'width_ratios':[1, 2]},
figsize=(14, 6)
)
fig.set_facecolor('white')
imgHWC = img.transpose((1, 2, 0))
ax_left.imshow(imgHWC)
ax_left.get_xaxis().set_visible(False)
ax_left.get_yaxis().set_visible(False)
# ax_right.imshow(img2)
Plot = ax_right.scatter;
marker = 'o'
alpha = 0.15
_ = Plot(histR[-1][:-1], histR[0], marker='o', alpha=alpha, color="r");
_ = Plot(histG[-1][:-1], histG[0], marker='o', alpha=alpha, color="g");
_ = Plot(histB[-1][:-1], histB[0], marker='o', alpha=alpha, color="b");
# ax_right.set_ylim(top=200)
ax_right.set_yscale('symlog')
fig.suptitle(plt_name, fontsize=16)
plt.show()
def module_test():
import torchvision.transforms as transforms
TT = transforms.ToTensor()
TPIL = transforms.ToPILImage()
import numpy as np
from PIL import Image
from urllib.request import urlopen
url1 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAbNElEQVR4nC2aybJt6VWdxyz+Yu19%0Azrk3S0FKVCYCG0QEDdxwyy/tt3DYbhGAASMkozIl5b3n7rPWX8zCjfQ7jIhRfYTb8x1ruCCi1sSq%0ArCtJoC4XLRDXUAWSaZWUgrVIacM1K+dpBGmcu9ieBJYexOpkQU8Mz3PSLQLwUIqscDhBk1hp1ahG%0AorbPbVp6bDv2vu542jYEK6qs8F70Wtb8htu5F8S4IDZU1QwtkYTSWmcbEtgHsBqVzcxiZloI4jwK%0AuscIYXZShAvgtZAMWRqlmDknsw4iAdE8rfWeYKfQPaLKRdnfggrC1ZFViuVg69z1XFfXwmssKCiE%0AFRSsFihzbKmQhQAYUGBWXlLva2zn1aVI7svoKG0VsD+rbqyRlX3Lrvt5YjPzPWiBnaFMK/wgJvDw%0A4A4eiMICJKUwHLzG7p3N3JPh4YB2rk57w0CNd3Y42LaVndR4TUINCuvuQ54BKvZYOBhvoc/AZLNA%0AAbgCgXRG1g20GhYrwWGF7uUd+ljGlqR5UHkkgb0U3XlJvhM5gZ1RQwsHiN48bzXDKSAUqR0e0DN2%0A5anRTzAje/hiGIlmBrjAtlGwgoPjCjpgUyksu0rYBmQBwHVHWaAN4Y6yaCGb2ibJaYQkgJELHXBg%0AAhBwKmw1q9FdWKdM6IC6Eyl38LkyHMqcPZmInqm61l2KSmN+eeFtUojwTqmhL95PlJ2wOA4wMhie%0AWBIcOsHZw8G1z0tIDRUUNagWBgMD3PJW/Xu1DCI9MzGMIkcUkcquzipFU6tVBlAcVJ4/q3HmqkQr%0AutiVrAwzRkcZSiA0Wha3ykJIZk/qTQ7l2BEd9cL5EvK7mEbZ4tOVTxVY8Qgi96NmQmgH3YTePBjG%0AYaZU1sE69sioVckZCdprAxu4KU6DAKXjGlpgpCAjILmyIWXlBlxvLEa9vHiZ6qCodEPuNUmap6QS%0Aww3ywuKCkKSKfKNS6wE+vjzeD4q/ifX788Mv4vEW7crkXIklkABFuIEiOBmcudN7LOA22IBVsu83%0AIpXUB4JAhAzsilyoLBtCWA6mShhRWroLmySCoARbkoAjwYS5N0YTUccIKjjCCUG0qN1UnZQhknhw%0AGdpFhATvbt3vT++f1dRXpeBWuL0XA5UnfkpuTjxBTOicPc+W+w4MVpOTTRm3vYFOXF2YIAd7gAFI%0AqQVURftSQCM6eUXSVBQvPSvcENL1YL8BpYAFOFrrk0IcnXkJ012akhTQptsTvznylFKFQPY1kwn9%0AlfmXy348y3dBEdX8+ct8C1Kll83haUzByp3MsDdaciGqjUCpSSm5UNDFFZ5DRIZ0xVKNi3MjfIm3%0AWaop9oVgTKo8cQ23llyXj70MW8ETVG6tUaXkyIqcfBcyw7jh6Y3QGIui5I1pRSGCvehnS/lr9IFV%0AAu88fpU2bWwsTTlTU5xSNKYHHFWRjk0Oo0QiKNS2S2YGwecm4IAE0ipXT5eY6wIO5ekUJECKEvaC%0AcEYU5TTsrCW3V/d1406UvEEhsdgnbUsG6UfGQbaDI4PwYMmNDZGFT1eO7/CwbNP5F/L4zB9CHAQm%0A6Wniu8cIilQ++XrwIjCzfcGu5Aqzcs9UhQMFUoUSa3THilVymxUGYBGaeYv1HJFp3uAUGjCmRCtl%0AZHcStO78KQ8sck0wURUSZWKqDWZJylcnVrpBnzgjXadRz5d37B9zaPnuIvy08AcCepvMm11BZ8TK%0A2sPugUAIXUXwgAawsmkuRnp0SlR4l9E71OzprCM1oFqAqqjAqYinDAkMUHBqd8OOXRbUanVlm1Rr%0Ak9Z4lLhZMUUPckAcWfgaqDe4cSlinkeVmq3N9aGhfc24hH6b/t7DtD5z+2jX526vq7/hDRmcmBor%0A+J3rFCA7Y9QE5fou8wgflOQVoBgReiLRhQO8LsMdcMJoOEZlWW+JGiV1m3EiBKjK3mM56PnzZgO1%0AdbDlrqKI2JRCVJEbNWBMVIhBxRoQzH6JlOgoqy8KjfWe+AMiIc+MMWw7TYxClWCRsOiHxg/j6Wf0%0AKdIkiYBhlgza7DISbAHsDQiSe107yCMBwlKtO1blnpFOI0IKqfeM04g1Kz0/fxF1sKsuS1F2z1bA%0AlthkJZOSSGQgn7BH6cE39as2TVq5K2WI9GXB+la8EOuV52EFKZYroRLbKIDShEtgZXASIhevalic%0Al1v12JyYhQ+niAmu01YQuuqEOUldyYgFKATsKm3S9IVGoJfnZ/JAd0ThLKBJDseNy1sAvDu66Qrn%0AwkxCk0g9hIOEESbMV2rk5iwlkCUq01xUSAOLIxdYI5F4R7GYtwlo76QD8ohhwC3ZEgvRYFg2FRa3%0ATpFuG5VyC4G3DIwbFzNbSAG0lw0EuTCRxs7IGziA7XTLUSgmfbqpC4mxTypSdcvecOVZahrzQ9i4%0AuhOp1lqrMKC0KWaC6My5U4DWiAXgjEfqWyK13vBCEGOq/FRRPLUzFHZFv7Qq3e5u6TxFkQFlAq/Y%0AXcopuY7WANI6XTSyXhEc10J1rEGzBmrGWzxdqYr3BDZyYteckVTQwMzcLTWVuXGKjirAWORvyMHL%0ACx4CwSSXlpdjFWSyBFXHjlQj2nVWkkz/jIyJREIQnNwBsG5a3mTj4hHQ4Vg+Jw6P5M7xdA2Aq2xg%0ALfMAmNEi5QY9Mi/oKU8dp3JMzJXIEENnKYHpiqwwjGjBlY6S1Gt9SuvSmcFbOJH+FOkshTyoMcaK%0AIMmkvGUyssdgNyvZhItMFemIt2AIF10dVvCUqYrWC3hzE8ET60KEh8ej0CQ9QYIA0kidY4dM48xA%0Awhou527ISB/wZEo8OOFaWxvvFHST7J/50z1V232JPpdWuUhW1SoCfQAwOsktw/O2iabRgTWQNbcT%0Av8Wd42DIt9H/E8pG7xzEulkMt0ZeGFSVgpSLL+Sm3eU40nCgd9y8Z/cNKLgms+iNGmXZVEqw4Y2C%0ABAWogACUfLhmaq6k32s75P7U9V4JtYeCD6BidX/XKjNIoxcLih7MII5sXkTooY0YBMBDhRB+cvsh%0AP/1j7K8JzF2TatTKsRnKUohLPVSd1HJKPWuuGsU1NuBMi0FKCJWgd09H0i31JA/Xu+bGJNYJr1oo%0AC8rZ70+FsdqNS2U5IZ8PZRbjMI83SuJl17ay25qXnMvDFSwmjJbYwpwcJWKjIkyZHS9C9t7nR3Fx%0AvwV9ogxHZVqU8PcTb+95DYOTDTfhkpB1ZrS3TBaIC/FeAt6a9AQmJgax7AQvLUcyIE4pnFVfCt3W%0AcaK+q3TsXpZ2Ed5q7+n+3fz4FHvjrGm5XdWgjRcptkteudh3JzA97QhCPoFOq6Rvn1P/kPTnnv8G%0AeRCD8iu+/4YvIflTyaeov834vxKHicp2yOUufIkhFnPnoAmq4ouUuuMV0ASEugE1wDwK+Ei8VtW7%0AWtMX/aaVRJlf1Kdv+w/P8vbWfzDlN+Wsc1jgQ+ST5nS75qbqFu6ZcoRMUYfL8mfBb9l/EEwUZ+RF%0ADeV12/2P4R9JPghKlr/V8c9pjf5Ua/3P+2z7Zx/k9SXv/5o2ke/78zwfaBHbmiOwFzQ0A/QUCImt%0AnOf3bo7KTEb+jlsRea71z/Xz35W46SX9ffls4faXf3GA8fm/jNFf//VHcf+7+JnaDeOpyVgannU7%0AKpaEKqeLLH53i48LHiBjL/7Rqfw8VjAq5xeuVxl/zN881/HN/T3oBeNX/+X1y/9p/PM4Cfwjzu9o%0AX73BHTVVeQ9PMjBZwfg+pwYGsAWGCAoEqev9rl/+DX8+mO5hje4/U+L28uWBDnwLSO90/+N/6rPo%0AsUpw2aZQEuE4BBt6wBC2aAboL+uXXZ+Q8WcRX9FeyYsssV6CjZ8+yhevwDf9T3B8jtsz7l+iPv1H%0ArgPRiF/RBNxPloEnJFbmhgCdcwDWURupgwoTggd0ZW42k/yY/kT5G0iV+MMat17fP339FfA58A74%0AW+Bv7nLUHwi9R3bQk1EZjCATR2GYsnPtJCnxS5or6c/obokMoUBQfpb8Ifw1Hum/+wnGv8V3CEcM%0A+G/B8x7rr8GR/ha+nFF3qVhXlRlaUZxF0TcARGJGeglsBlM/uM+wui9p33yb3nB8hcc/7/15nA/k%0ABv0S+DHwD0DPwwLK7YYPv86T0i2jeaOSLRCpiqKZG6v6TZM+5uu36E9+Nc5EOYGN/Sfuv/L2s/Ld%0Av7/2P42fuOCXn/JH/vjHtJ+AIu2Z1469C9Y4VPwqiKF5AAoGJqFx1Ibt1IKkZUm6wIw8nM9fxrvn%0A/EDhD4+/Gl/8/euvP778wZfAvwAT+OXra87f32w/4vhCbmd+G5ykEOMHezVjoo38owjNx8kN8G+y%0A/FZkZGjQ1xj/EPQT0D2DFzP9/H9EUPzRjJ/+e/KZ9imnGydyXlBRceKyMBBwt1R6+eIbxBvCaQtl%0AgmcKsyjV2lK0yssLbvcfV/qK7w/1fz/ad0/Hj4/jf737wfvM/vr2m/MTXn++rsuG+tsvePvOfLdW%0AhoaV4IsXKl6Irh+BPkAP8l8va4kHRQSA7cYOqmTgTpwzpVO2jEdG+uhcH26y+cpoYolKOnYijMDa%0AldYbVDOE95lHphXJnbawuvUkt/2p2vvf5j51/ybyoPUVxT9N+3L11/X4uAzzV9cO3/lsv3+1LFhl%0Atwsv4iNQGor2bnb9Wbz/Pzm+Jv8lbeL45D0xCbFTVULgE4XSyGlEkGU0uKemj7m95XJVzzkDZckg%0ArblW53tOevfZS4rRFamFU8kROEUVWZQI1Y+X8vLxxl99rf0HFX+nn73q/KL2kzzCXzx+ZR9uQ3Of%0Aa4427sHTr5U+lETpR38is2v8dHKYPeHxY27/2+fM682SMV6dStKGTremSslr2K7jSHEXr+UetpYP%0A5hvFGYzNyHErurZZqSiq9O6L9/AMKyyDMyklnVAEuIQ6O0mn+vK+x6MH18/ecfmOgnUFPXY+y66M%0Ab22dsSrZ6avy6IwTaUpa7lXkyyfOPxivr+f9I+e7/PQ0/e951MjL/GLcE04TJpu2RCfaznWu7Bpp%0AvLGIlRG2LPyp8iMZxKDVpY/TAYVlMFMZMAopLPv74Rqjg8/UppXs9dNi56e0b39XSfFF3/MUI/+4%0AIgdSjWRd5izLCK/hfANNrV+tOBqecfw06Ify5v76rcsrR0cbEUmzmj/W9lJZZ92VaTxwk0Qzvzaj%0ADyR1JQ8EV5THSnRm4npt5wR2Yfrs+Q9DBvIF+ZG3eN9MSb6QAISkFr5ydi7anql8Sq3BQrSV6SMd%0At7nY1kIcWzcAN7OgbBrCUkPrH+jxlkf1V4vHuct7og/x6SXIzD54QDLCidZGSGJag7quMELlFnkR%0AxOERHTG09sTwCygNmbCFA5WhMzlY35jF2cgoPdLutBXc6GnHuBFG0p4hs/sn2DVs8R7UzofttR1h%0A/LaN5pKtcLjRla72kHj9bvnfyu/dHj8M/0O5/hDXu8hPtBgm6mY52DZBF2wpOOoyE8CF/bJVAy12%0ARUx2mG2+FE2wBa7KwNLgiIsoHISLSBUAhuatpClOyg+wu5seaeRz5JA6YN3GstNwYQ1iUMxgxg45%0AnYnEiCPmMxMgz1g/Od9KxG+Q31l8cv8kmUG/jQhI5Kkw+8RL7q4FJtEKtML3CkimRDASDhEFu8MA%0Ar0doeukEZ6PPPn8fwliJrdwmGDg9OjOBnJFJRqyatAQrtYIhHjAN2UqSGc6atqhUy0xT4gluYkj9%0AI9x+IaEsLxODd6BMnukbawt0ehpv33EEHhzwBKkuR8kskpdxQyb7ZJTOcYYLyr6RXkw1MUU5r1Cb%0AzAxwR3kDCSKhBl+BytiBjk6wh/DNk5iZYrgfDNcaNiThwUaM3IXZYEjhQPGe8H/j1SJK8kXsGeFv%0AwhUhSDPEpqBgGhxtQFG4q9l1xJ2QbqeWHDtJcV+6HD2I1OMYKUjLsjApGJ1ZIzEzCaIJDStByXJj%0AMkCYiPPBhGTnorw4TIABobmenVeuTiTunZTcl2umF6IAFPGc8wvnt70uf0OGwjXCw4xlwcgGGNnW%0ASTehLtcgZrC+TVwLnaiiQkKuWjVh1XPx8MKTGZ7Ui/FE0NPtRcxRKl6ajE2AUcCSuYIjw7EXVaPN%0AKY1lx34mneyRpCI7RcJWcIKJTiUOSMkixMbJ7kpAVAsEpsiGjwmUVGQP+B7RyxwiZoKxWqG5iUok%0Al7q3xVFu8FxmhGIaUOdNSqqJ4W8AQuleDqoHVSJPRlIZxLdY7xIP5oFgWMlY3JHeIG9YIL1nMPDG%0ANYgs90vWk1AiicnhTchS2D0RO01T4UiOA3ES9AoWW96mQmDNtgkmf3881gosDlW2YShAlZ5EyVdy%0A88xyrQ1sutWGNdD0pON4IffSCenBTewgPonCW+VYaZGhzEFCyAJjYqbqEYXygayk6tuzCOED5k2I%0Ao/5Q6q8QZud74t85fa8cA4I8JllZ2MzCPe3BGm+G1qEEE9mPLKgTA8AdbBJRZJPcF6YQr6mAl5Lu%0AGjEUoczEodIsNDmYLvQ3V3VmgNKKtCJoXFsmgcG1JURUmt60fi5fJJUXFa+8FHcuTIxCv4tzYyX4%0AN2EFvgIzQOFIhGbSjbgy8zmKwNozgEFx6V7RekwKVKDg7Ym5gIbDKVxFQZWTUHmviAQkWLzS/V3b%0AcpPTCzlIiAuawRgAJ9OhOZqqUPuEdVMEnkWPv5DbT0DfjEF8XhE/8XXL/QiqtFdSQEv4BO3YDX5m%0AILUiQMZBmFjQiAu3DnPQxlB0QpIExM0kNJFEOxIKIeRZ4gacibvp2SJFy7W2AKZ0ay87rHNu1QIm%0AD5QENwIRK9gEyfyZfnPy61/3z/8c7b/Xp/+qnwED62dY87+d+7P4+ItMId2xZmzJ3CmRyyklzaPX%0ABeMRguASS1ADb6j35RvuT7098qFTIHD3xJ2xAGTdbWEIQaitIFRDGhS4wDdwdoQlr+oKRw2S7RWe%0AFiM5LNzTz0wK5qBP/jb3+XvDGfQfOFAT+sms/h3nD8h/GT7c5vawyNCN5ViFLAIgUR6nRhSYcGCj%0AALHKsWyUiAZ5RGA3a0wkiVrwVrE7mJaMAq56LEzwgnDdqBAweLZgrtOhFKZgn6y3oLeBhN3UkWye%0AXLANO8xLWhf5+fx9lv6y6nf++i7Gp/P6dcRhbyvCkza8Y2cUIo3AQ5QnhYYDPJe1ykaQyQkJZEFk%0AdNtTsTah3TCn3Qlro24o9DwC1z7iuC6v4BLxtgyoq0qfnImxznuCjqe2NlqAWKy5TkRWJGmtuS/v%0AWUfLVD6cSIgrJJQ+RznSZ9jIeHg3mpYRCCARBhKDWwZY26vlwQnzS6InEEoEEnOrAbMSReEbVJXN%0AeMMSgppYu3Oupri2ghfQkeP/czdZsRYXRHRQKy/GqJihqIsB5mq2gQJaArhLKU0IyNDE4qx8VNAK%0AP3jvoMCazpPRc22qgrUcXZDedyLEEVSF1xvQTWC02AyoAAeMuvKYrlmQmwUrINw8dj9iXkgSkSw7%0AUGsumxRo6BMjCaDW6mTus9UJZcmGVpzMAS3tNAYTHYrYOEeA09/I4RHuy1LTPoaMFJAqGIRJ1QNh%0AN4juZI2INEURLB+jNiOzKhwcyACgAErZIG2wWnDDIgDNfUJjZc1kRAZiSB1rTaB1wtCR4JpoxQAw%0AGWKAVgH5XlOLBuss8xbEYSZGAq+IDNqdYkEBX3QaMuE1/c19X1aDK5awGgjL3HiCNcL2mcQuuk5t%0Aha+NCOiBbmJLcaYz0ybEaRc4j8ZEHbpvPBhPURHJFUiA9VmmF0DQEImK9BnMSZwFCd8wSmBlBngw%0AQ12ZH1KoRkC2oV7OOgl5LqxHEo35GhPEdqcSNlOqB2gz+p3DyjAIokRGU8XhttGu0ASYshkAtI2x%0ANgm2ghHqzp4Bo1Ny4oEFhRiqAZ1OE1pyGKRgA7YgK1iTWcGwYtAOq2Iw8ojuEYoquYlJdvVYIInG%0AFqiJp1xLGdFoubpfETXj4vWyVqzxRuh+gJXLruAdo4JiFQUrI6+cCRBgR2NwMaAgOhObOhfiu07k%0A0Qqq41SwArE9c8G9YRmoLhaoNqLaat5AZ00AAGr0FQOAFliCweEVHMgF1L4wyoII3ujl7m8pGRYO%0AFGB1kjzUT1j9BHvq/BjWwKxB4MD3DRgAWLpHjpwHYFVp2YLeYUYAwUIr4WqMEQ22gLzd6IoEJEcq%0AGhDGwbqx+VYAjCVqBANQNhw4mNldZSOikhFiIwjA0MUMIHHXkcVBhSoESFWK1NyZ8FIbMoc9AxAs%0AAzQoyhsaWKy00wdySlcoIox6ucMCNfPmAYdZtToMKAEFwNcpMo4ctdY0RMECwIujcCwAeRREgoCl%0AicS6IoIHiAALtGRJFMATjHCCVZy+ggmYlpqKzSaGwE5vhIcgU3EpIFEKsGXFRE5rvnlWKGnHqAPa%0ADJN3dhCW4CTgppBZmYMwGZaMyGYGKGztAomrJpBQUTBDUCWACrgqT1mB3g8T72Hcq3KuGQRYY3Ew%0AwIky0DZktkmVxdRBq3mDLbimOafCCorBN3K/JPsNaAy7cJtQtosWI7qbA+I1ExewAALcwFgRuB2q%0AUAFQJoDLsTUNGgjqlOZck26lUVWsTE5EEID6xPKYGwqIKWDO4KK0IzjCEIpK8M1Se2D4QgXPW4oR%0Arxp8nswtI0nBrp5bgMUF4VrTloJR2TkqxSer6qsEXxydEaprE5GnhQAd/U0HFPBGOasBgDBlTS34%0AdIENjN4tY6uC40IHkOuxJjswWaxbdBTAt3kNBkyQ1mODSgBn7iDopFBzZvVqZ4CDPZGG5CSFr8KM%0AQcqRVfu4xV5GI7cDukpgghAYhu+nBOZAhQMnDwjIcOzZAAUCSCUeTBeeuEoFE8aiCCm7IGkEQOB9%0ARAc54Bs5cIYYYMAywJFlZAUbBUNqYzVLYHH60Fy9o4UJoxLHwLTOZbMQlRygZbszEqCSvkgvGCDK%0ADIbiJg5sMA7DwbUAt0S3CmAzW5XaAQuUDlXuaAI2R1+Q+YjBrSO7LoS+YWvyZopiACOP1LIRDIEU%0Agq26K2MgllsNvYVxZNLlNx3CQAXOO2vh4tZSIKLLM3ZB4iFS+t4g1AZoB3gHWAwU6Q41XIEIM4ED%0AC3lBdeVSj4FIAMsofefcTMdxQzgCAIhhIdG9DBDjEjw5IphVFy1zFIYQZqIYiBksyyJ63pVwOqSY%0AblgDnLclMBr6hAEKGItGOJIBxd14XCyHio1LgQuQ7zk6U2bLjjzBwFZoYAU6IExLiQHxIjVprteo%0AJJ+3OrB2VwSX4E0gs1KV50zIhAAbYJRy0zwvEBKwqpJF4zoZdWmAy40zVjh7EZxzFe47vgeKC4cB%0AENTNkynD78CDIOX701VVEGq+gWjQBK0GzAQCNbAggmzEkjyA4NWz2tOKT0rA/wNRiAVn1y/X/QAA%0AAABJRU5ErkJggg=="
url2 = "data:image/png;base64,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%0ArkJggg=="
# TT automatically converts a PIL image to a CHW torch.tensor bounded between 0 and 1
img1 = TT(Image.open(urlopen(url1)))
img2 = TT(Image.open(urlopen(url2)))
from HistogramTransformClass import histo_plot
import HistogramTransformClass as HTC
# Extract histograms for each channel of the template image
histR2 = np.histogram(img2[0].numpy().ravel(), bins = 256, range = [0, 1])
histB2 = np.histogram(img2[1].numpy().ravel(), bins = 256, range = [0, 1])
histG2 = np.histogram(img2[2].numpy().ravel(), bins = 256, range = [0, 1])
# Create a histogram transformation object, initialize with channel histograms
HT = HTC.HistogramTransform(template_histograms=(histR2, histG2, histB2))
# Transform the image
img3 = HT(img1)
# Plot histograms
histo_plot(img1, "Original Image")
histo_plot(img2, "Template Image")
histo_plot(img3, "Transformed Image")
if __name__ == "__main__":
module_test()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment