-
Notifications
You must be signed in to change notification settings - Fork 0
/
histogram.py
executable file
·304 lines (237 loc) · 8.96 KB
/
histogram.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
"""A module providing facilities for generating and analyzing histograms based
on greyscale images.
This module provides methods that take a greyscale image and create a histogram
of pixel values. The resulting histograms can be analyzed to determine the
cloudiness of each image. Histograms can be plotted and saved. Categorization
of histograms can also be done through this module.
"""
import os
from copy import copy
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from PIL import Image
import coordinates
import mask
import image
center = (256, 252)
date = "20170718"
data = []
x = []
d1 = coordinates.timestring_to_obj("20171001", "r_ut013603s08160").plot_date
d2 = coordinates.timestring_to_obj("20171031", "r_ut132350s57840").plot_date
def plot_histogram(img, hist, masking=None, save=True):
"""Plot and save an image and histogram.
Parameters
----------
img : image.AllSkyImage
The image.
hist : array_like
Histogram of image pixel values.
masking : numpy.ndarray, optional
A masking array of pixels to ignore. Defaults to None.
save : bool, optional
If the plot should be saved. Defaults to True.
Notes
-----
This method will save the histogram into Images/histogram/`img.date`/
`img.name`.
"""
# Sets up the image so that the images are on the left
# and the histogram and plot are on the right
fig, ax = plt.subplots(2, 2)
fig.set_size_inches(10, 5.1)
fig.subplots_adjust(hspace=.30, wspace=.07)
# Turn off the actual visual axes on the images.
ax[0, 0].set_axis_off()
ax[1, 0].set_axis_off()
# Display the original image underneath for transparency.
ax[0, 0].imshow(img.data, cmap="gray")
ax[1, 0].imshow(img.data, cmap="gray")
# Creates the histogram with 256 bins (0-255) and places it on the right.
# Kept this super general in case we want to change the amount of bins.
bins = list(range(0, 255))
width = 1
ax[0, 1].bar(bins, hist, width=width, align="edge", color="blue", log=True)
ax[0, 1].set_ylabel("Number of Occurrences")
ax[0, 1].set_xlabel("Pixel Greyscale Value")
# Cloudy pixels thresholded first, then the horizon and moon are masked.
thresh = 160
img2 = np.where(img.data >= thresh, 400, img.data)
mask2 = mask.generate_full_mask()
mask2 = np.ma.make_mask(mask2)
img2 = np.ma.masked_array(img2, masking)
img2 = np.ma.masked_array(img2, mask2)
# This new color palette is greyscale for all non masked pixels, and
# red for any pixels that are masked and ignored. It"s blue for clouds.
# Copied the old palette so I don"t accidentally bugger it.
palette = copy(plt.cm.gray)
palette.set_bad("r", 0.5)
palette.set_over("b", 0.5)
# Need a new normalization so that blue pixels don"t get clipped to white.
ax[1, 0].imshow(img2, cmap=palette,
norm=colors.Normalize(vmin=0, vmax=255), alpha=1)
# Writes the fraction on the image
frac = cloudiness(hist)
ax[0, 1].text(170, 2000, str(frac), fontsize=15, color="red")
# Draws the vertical division line, in red
ax[0, 1].axvline(x=thresh, color="r")
# Forces the histogram to always have the same y axis height.
ax[0, 1].set_ylim(1, 40000)
data.append(frac)
x.append(img.time.plot_date)
ax[1, 1].plot_date(x, data, xdate=True, markersize=1)
ax[1, 1].set_ylim(0, 1.0)
ax[1, 1].set_ylabel("Cloudiness Fraction")
ax[1, 1].set_xlabel("Time after 01/01/2018")
xt = []
for i in range(20180101, 20180132):
x1 = coordinates.timestring_to_obj(str(i), "r_ut000000s00000").plot_date
xt.append(x1)
ax[1, 1].set_xticks(xt)
ax[1, 1].set_xlim(xt[0], xt[-1])
ax[1, 1].xaxis.grid(True)
ax[1, 1].xaxis.set_ticklabels([])
# Saving code.
# This ensures that the directory you're saving to actually exists.
dir_name = os.path.join(os.path.dirname(__file__), *["Images", "histogram", img.date])
if not os.path.exists(dir_name):
os.makedirs(dir_name)
file_name = os.path.join(dir_name, img.name)
if save:
dpi = 350.3
plt.savefig(file_name, dpi=dpi, bbox_inches="tight")
print("Saved: " + img.date + "/" + img.name)
# Close the plot at the end.
plt.close()
def generate_histogram(img, masking=None):
"""Generate a histogram of pixel values.
Parameters
----------
img : image.AllSkyImage
The image.
masking : numpy.ndarray, optional
A masking array of pixels to ignore. Defaults to None.
Returns
-------
hist : list
The histogram values.
"""
# This first applies any passed in mask (like the moon mask)
img1 = np.ma.masked_array(img.data, masking)
# This then applies the horizon/circle mask.
# Converts the 1/0 array to True/False so it can be used as an index.
mask2 = mask.generate_full_mask()
mask2 = np.ma.make_mask(mask2)
img1 = np.ma.masked_array(img1, mask2)
# Pixels from 0-255, so with 256 bins the histogram will give each pixel
# value its own bin.
bins = list(range(0, 256))
hist, bins = np.histogram(img1.compressed(), bins)
return hist
def cloudiness(hist):
"""Calculate the cloudiness of a histogram.
Parameters
----------
hist : array_like
List of histogram bin values.
Returns
-------
float
Cloudiness value of a given histogram.
Notes
-----
The cloudiness fraction for a histogram is calculated by taking the number
of greyscale pixel values above 160 and dividing it by the total number of
greyscale pixel values that appear in the histogram.
"""
# Pretty straight forward math here:
# Num of pixels > thresh / total num of pixels.
thresh = 160
total = np.sum(hist)
clouds = np.sum(hist[thresh:])
frac = clouds/total
return round(frac, 3)
def init_categories():
"""Initialize histogram categories.
Returns
-------
dict
A dictionary mapping category names to the histograms that define them.
Notes
-----
Images used for initializing each category are stored in Images/category/.
These images can be downloaded from the GitHub repository.
"""
# Loads up the category numbers
directory = os.path.join(os.path.dirname(__file__), *["Images", "category"])
files = sorted(os.listdir(directory))
categories = {}
for f in files:
# Opens the image, then uses np.histogram to generate the histogram
# for that image, where the image is masked the same way as in the
# histogram method.
loc = os.path.join(directory, f)
img = np.asarray(Image.open(loc).convert("L"))
masking = mask.generate_full_mask()
masking = 1 - masking
masking = np.ma.make_mask(masking)
img1 = img[masking]
# Creates the histogram and adds it to the dict.
bins = list(range(0, 256))
hist = np.histogram(img1, bins=bins)
name = f[:-4]
categories[name] = hist[0]
return categories
def categorize(histogram, categories):
"""Categorize a histogram based on the given categories.
Parameters
----------
histogram : array_like
List of histogram bin values.
categories : dict
A dictionary mapping category names to the histograms that define them.
Returns
-------
object or None
The category that the histogram belongs to.
Notes
-----
This method uses the histogram intersection algorithm. The
algorithm is defined originally by Swain and Ballard in a paper
entitled Color Indexing [1].
In essence the method decides what category the histogram belongs to by
finding the category whose histogram"s shape most closely
matches that of the input histogram.
References
----------
.. [1] Swain, M.J. & Ballard, D.H. Int J Comput Vision (1991) 7: 11.
https://doi.org/10.1007/BF00130487
"""
best = 0
category = None
for cat in categories:
# Take the minimum value of that bar from both histograms.
minimum = np.minimum(histogram, categories[cat])
# Then normalize based on the num of values in the category histogram
# This is the intersection value.
nummin = np.sum(minimum)
numtot = np.sum(categories[cat])
# Need to use true divide so the division does not floor itself.
intersection = np.true_divide(nummin, numtot)
# We want the category with the highest intersection value.
if intersection > best:
best = intersection
category = cat
# At present I'm currently looking for more categories, so if there isn't
# a category with > thresh% intersection I want to know that.
thresh = 0.35
if best > thresh:
print(best)
return category
return None
if __name__ == "__main__":
blah = image.load_image("r_ut070904s69120.png", "20180323", "KPNO")
h = generate_histogram(blah)
plot_histogram(blah, h)
#init_categories()