/
driver.py
331 lines (262 loc) · 9.12 KB
/
driver.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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
"""
Driver to handle inputs and configure compression.
Classes:
Driver
"""
from core.image import Image
import os
class Driver:
def __init__(self, argv: list, argc: int):
"""
argv: ./__main__.py [SOURCE] [ALGORITHM] [MODE] [COMPRESSION] [OVERFLOW] [TARGET]
argc: number of command line arguments
Methods:
validImageFile
validExtension
validCompression
getValidInput
saveLog
run
"""
self.name = argv[0]
self.validExt = (".jpg", ".jpeg", ".png", ".tif")
self.initialized = False
if argc == 2:
print(self.usage() + "\n")
elif argc == 1 or argc > 8:
print(self.usage())
return
print("Initializing...")
self.imgName = None
self.source = None
self.path = None
self.algorithm = None
self.mode = None
self.compression = None
self.overflow = False
self.shouldLog = False
self.target = None
self.resourcePath = os.path.join(os.getcwd(), "input")
self.outputPath = os.path.join(os.getcwd(), "output")
self.variances = {
"low": 90.,
"medium": 95.,
"high": 99.99,
}
# parse cmd line arguments and get user input
if argc >= 2:
imgName = argv[1]
self.source = argv[1] # will get changed to full path later
if argc >= 3:
self.algorithm = argv[2]
if argc >= 4:
self.mode = argv[3]
if argc >= 5:
self.compression = argv[4]
if argc >= 6:
try:
self.overflow = bool(int(argv[5]))
except:
self.overflow = not bool(self.getValidInput("\nAllow overflow?\n\t1) True\n\t2) False", int, valid={1, 2})-1)
if argc >= 7:
try:
self.shouldLog = bool(int(argv[6]))
except:
self.shouldLog = not bool(self.getValidInput("\nLog data?\n\t1) True\n\t2) False: ", int, valid={1, 2})-1)
if argc == 8:
self.target = argv[7]
if not self.validExtension(self.target):
self.target = self.getValidInput("\nInput valid target file name: ", str, isValid=self.validExtension)
if self.source is None or not self.validImageFile(self.source): # get valid image file name
available = os.listdir(self.resourcePath)
if len(available) == 0:
print("No images in \"images\" folder. Add more and try again...")
return
options = ""
for imageName in available:
options += "\n\t- " + imageName
self.source = self.getValidInput("\nSelect valid file from \"images\" folder."+options, str, isValid=self.validImageFile)
self.path = os.path.join(self.resourcePath, self.source)
self.imgName = ""
for l in imgName:
if l != ".":
self.imgName += l
else:
break
self.image = Image(self.imgName, self.path)
if self.algorithm is None or self.algorithm not in {"pca", "svd"}: # check which algorithm user wants to use
choice = self.getValidInput("\nWhich algorithm would you like to use?\n\t1) PCA\n\t2) SVD", int, valid={1, 2})
if choice == 1:
self.algorithm = "pca"
else: # choice == 2
self.algorithm = "svd"
if self.mode is None or self.mode not in {"v", "c", "q"}: # check which mode user wants to use
if self.algorithm == "pca":
choice = self.getValidInput("\nHow would you like to determine compression?\n\tv) Variance\n\tc) Component\n\tq) Qualitative", str, valid={"v", "c", "q"})
else: # self.algorithm == "svd"
choice = "c"
self.mode = choice
if self.algorithm == "svd" and not self.mode == "c":
print("Invalid mode for SVD: " + str(self.mode) + "\n\t- setting mode to \"c\", \"components\"")
self.mode = "c"
self.compression = None
if self.compression is None or not self.validCompression(self.mode, self.compression): # get value for compression
if self.mode == "v": # mode is variance
choice = self.getValidInput("\nHow much variance would you like to retain? (0, 100): ", float, lower=0, upper=100)
elif self.mode == "c": # mode is components
choice = self.getValidInput("\nHow many components would you like to use? (0, " + str(self.image.data.shape[0]) + "): ", int, lower=0, upper=self.image.data.shape[0])
else: # mode is qualitative
quality = {
1: "high",
2: "medium",
3: "low",
}
choice = quality[self.getValidInput("\nHow much quality would you like?\n\t1) High\n\t2) Medium\n\t3) Low", int, valid={1, 2, 3})]
self.compression = choice
elif self.mode == "v": # compression passed through command line
self.compression = float(self.compression)
elif self.mode == "c": # compression passed through command line
self.compression = int(self.compression)
if self.mode == "q":
self.compression = self.variances[self.compression]
self.mode = "v"
self.initialized = True
def usage(self) -> str:
return f"""\
{"o"+"="*85+"o"}
{"IMAGE COMPRESSOR!!!"}
Runs lossy image compression on images with choice parameters.
Usage:
{self.name} [SOURCE] [ALGORITHM] [MODE] [COMPRESSION] [OVERFLOW] [TARGET]
{self.name} [SOURCE] [ALGORITHM] [MODE] [COMPRESSION] [OVERFLOW]
{self.name} [SOURCE] [ALGORITHM] [MODE] [COMPRESSION]
{self.name} [SOURCE] [ALGORITHM] [MODE]
{self.name} [SOURCE]
Note: If the former is used, user will complete parameters through terminal
Run image compression on image with valid extension: {self.validExt}
Algorithms:
pca: Principal Component Analysis
svd: Singular Value Decomposition
Modes:
v: Select a percentage of variance to keep. Only valid if mode=pca.
c: Select a number of components to keep.
q: Select compression level from predefined settings. Only valid if mode=pca.
Compression ranges:
Given mode...
v -> float between 0 and 100
c -> int
q -> Either low, medium, or high quality
Overflow:
1: True, values of pixels on one or more channels may overflow, resulting in cool noise
0: False, prevent cool noise but retain maximum image quality
Optional
Log:
1: True, log data to logs/
0: False do not log data to logs/
Optional
Target:
file name: name with valid extension which will be saved to "output" folder
Optional
Examples:
{self.name} tiger.jpg pca 1 95 1 0 tiger_pca_v_95.tif
{self.name} flower.jpg svd 3 min 1
{self.name} knight.png
{"o"+"="*85+"o"}"""
def validImageFile(self, fileName: str) -> bool:
"""
Determine whether image file is in "input" folder and whether it has a proper extension
fileName: name of file w/o full path
Returns: True/ False
"""
availableImages = os.listdir(self.resourcePath)
exists = fileName in availableImages
extValid = self.validExtension(fileName)
return exists and extValid
def validExtension(self, fileName: str) -> bool:
"""
Determine whether image file has proper extension
fileName: name of file w/o full path
Returns: True/ False
"""
extValid = False
for ext in self.validExt:
if ext in fileName:
valid = True
for i in range(1, len(ext)+1):
if fileName[-1*i] != ext[-1*i]:
valid = False
if valid:
return True
return False
def validCompression(self, mode: str, compression: int or float or str) -> bool:
"""
Determines whether "compression" value is valid given "mode" parameter
mode: mode to use, either "v", "v", or "q"
compression: degree of compression associated with mode
Returns: True/ False
"""
if mode == "v":
try:
return (0 <= float(compression) <= 100)
except ValueError:
return False
elif mode == "c":
try:
return (0 <= int(compression) <= self.image.data.shape[0])
except ValueError:
return False
else: # mode == q
return compression in {"low", "medium", "high"}
def getValidInput(self, msg: str, dtype: any, lower: float=None, upper: float=None, valid: set=None, isValid: callable=None) -> any:
"""
Gets input from user contrained by parameters
msg: message to print out to user requesting input
dtype: type that input will get converted to
lower: numerical lower bound
upper: numerical upper bound
valid: set of possible valid inputs
isValid: function returning bool to determine if input is valid
Returns: valid input
"""
print(msg)
while True:
try:
choice = dtype(input("\nChoice: "))
except ValueError:
continue
if (lower is not None and choice < lower) or \
(upper is not None and choice > upper) or \
(valid is not None and choice not in valid) or \
(isValid is not None and not isValid(choice)):
continue
return choice
def saveLog(self, name: str, data: list):
"""
Save log data to text file in "logs" folder
name: name of log
data: data to write to log file
"""
path = "logs/" + name + ".txt"
f = open(path, "w+")
print("Writing log files to " + path)
for pt in data:
f.write(str(pt[0])+" "+str(pt[1])+"\n")
f.close()
def run(self):
"""
Run compression, handle saving image and logging data.
"""
if not self.initialized:
return
logs = self.image.compress(self.algorithm, self.mode, self.compression, overflow=self.overflow)
print()
if self.shouldLog:
for channel, log in logs.items():
name = self.image.name +"_"+self.algorithm+"_"+self.mode+"_"+str(self.compression).replace(".", "-")+"_"+channel+"_"+str(int(self.overflow))
self.saveLog(name, log)
if self.target is not None:
path = os.path.join(self.outputPath, self.target)
self.image.save(path)
else:
print("\nShowing image...")
self.image.show()