Repository navigation
Expand file tree
/
Copy pathbatch.py
More file actions
379 lines (318 loc) · 14.8 KB
/
Copy pathbatch.py
File metadata and controls
379 lines (318 loc) · 14.8 KB
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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
import os
import sys
import numpy as np
import pandas as pd
import logging as log
import datetime as dt
import matplotlib.pyplot as plt
from textwrap import TextWrapper as txwr
import galaxy
import post_process as pp
Galaxy = galaxy.Galaxy
# Create the /Logs folder for the root directory if it doesn't already exist
if not os.path.isdir("Logs") :
os.mkdir("Logs")
def dateFmt () :
"""Returns the date component of the run log file"""
dtStr = str(dt.datetime.now())
dtStr = dtStr[:dtStr.find('.')]
dtStr = dtStr.replace(' ', '_')
return dtStr
# Set the logger for this run of classifications
runlog = log.getLogger(__name__)
runlog.setLevel(log.WARNING)
runLogPath = "Logs/run_{}.log".format(dateFmt())
fileHandler = log.FileHandler(runLogPath)
fileHandler.setFormatter(log.Formatter("%(levelname)s : RUN_INIT : %(asctime)s : %(message)s",
datefmt='%m/%d/%Y %I:%M:%S %p'))
# Ensures that there is only one fileHandler for the current logger
for h in runlog.handlers :
runlog.removeHandler(h)
runlog.addHandler(fileHandler)
runlog.info("Batch runner started!")
sys.setrecursionlimit(10**6)
def logFixFmt (fix, k=50) :
""" Formats error messages for the run logger """
return 2*(k*"#" + '\n') + txwr(width=k).fill(text=fix) + '\n' + 2*(k*"#" + '\n')
class Batch () :
"""
Class that loads the FITS data corresponding to a .csv file of SDSS objIDs
and performs DAGN classification on them
"""
batchRoot = "Batches"
def getBatch (batchName, bands=Galaxy.default_bands, rad=40, csv=None) :
""" Class method to get a batch """
try :
batch = Batch(batchName, (batchName + ".csv") if csv is None else csv,
bands, rad)
except (FileNotFoundError, ValueError) as e :
print("Error initialising batch!")
print("Kindly check the latest message in the logfile '{}' for a fix.".format(
os.path.join(os.getcwd(), runLogPath)
))
print("Abort!")
batch = None
finally :
return batch
#############################################################################################################
#############################################################################################################
def __prelude__ (self) :
"""
Sets up files and folders and checks for existence of
folder indicated by attribute batchName and the 'csv' filename
"""
# To access fileHandler of the logger
global fileHandler
fileHandler.setFormatter(log.Formatter("%(levelname)s : RUN_INIT : %(asctime)s : %(message)s",
datefmt='%m/%d/%Y %I:%M:%S %p'))
# Checks if the batchRoot directory has been created at the root directory
runlog.info("Checking environment for the new batch.")
if not os.path.isdir(Batch.batchRoot) :
runlog.critical("Data folder not found!\n\n{}".format(logFixFmt(
"Please create a folder named 'Data' in the notebook directory and rerun!"
)))
raise FileNotFoundError
# Checks if batchName folder exists in batchRoot
if not os.path.isdir(self.batchFold) :
runlog.critical("Batch folder not found\n\n{}".format(logFixFmt(
"Please create a folder for the batch at '{}' and rerun!".format(self.batchFold)
)))
raise FileNotFoundError
######################################################################
# Checks if the .csv file exists. If the 'csv' argument is None, the
# name of the .csv file is taken to be the same name as its containing
# folder
######################################################################
if not os.path.exists(self.csvPath) :
runlog.critical("Batch .csv file at path '{}' not found\n\n{}".format(self.batchFold, logFixFmt(
"Please supply the name of the appropriate .csv file and rerun!"
)))
raise FileNotFoundError
######################################################################
# Changing name of the run log fileHandler to reflect the batch it is
# presently handling
######################################################################
runlog.info("Valid environment! Changing log format to handle batch '{}'".format(self.batchName))
fileHandler.setFormatter(log.Formatter("%(levelname)s : {} : %(asctime)s : %(message)s".format(self.batchName),
datefmt='%m/%d/%Y %I:%M:%S %p'))
# Ensures only one fileHandler exists
for h in runlog.handlers :
runlog.removeHandler(h)
runlog.addHandler(fileHandler)
######################################################################
# Creates a /FITS folder in the batch folder where all the FITS files will
# be stored
######################################################################
if not os.path.exists(self.fitsFold) :
os.mkdir(self.fitsFold)
runlog.info("Created FITS folder for batch")
else :
runlog.info("FITS folder for the batch already exists")
if not os.path.isdir(self.resFold) :
os.mkdir(self.resFold)
runlog.info("Created results folder for batch")
else :
runlog.info("Results folder for the batch already exists")
def __setLoggers__ (self) :
"""
Sets a logger to record the results as a .csv file
Can do because logging module of Python is thread-safe
"""
def setLogger (csvpath, loggername, headerline, runlogtype) :
""" Internal function to get the logger """
# Creating the .csv file for results
writeHeader = False if os.path.exists(csvpath) else True
logger = log.getLogger(loggername)
logger.setLevel(log.INFO)
fh = log.FileHandler(csvpath)
fh.setFormatter(log.Formatter("%(message)s"))
# Ensuring only one file handler exists
for h in logger.handlers :
logger.removeHandler(h)
logger.addHandler(fh)
if writeHeader :
logger.info(headerline)
runlog.info(f"Created {runlogtype} csv")
else :
runlog.info(f"{runlogtype} csv already exists")
return logger
self.reslog = setLogger(self.resCsvPath,
self.batchName + "_result",
"objid,u-type,u-peaks,g-type,g-peaks,r-type,r-peaks,i-type,i-peaks",
"result"
)
self.purelog = setLogger(self.pureCsvPath,
self.batchName + "_pure",
"objid,bands,pid1,pid2",
"pure"
)
self.impurelog = setLogger(self.impureCsvPath,
self.batchName + "_impure",
"objid,u-type,u-peaks,g-type,g-peaks,r-type,r-peaks,i-type,i-peaks",
"impure"
)
def __setBatchList__ (self) :
""" Sets the list of galaxies to classify -
1. Reads the main .csv file
2. Reads the .csv file which contains results of
already classified galaxies
3. The set difference of (1) and (2) are the galaxies
yet to be classified
"""
# try block to read the master .csv file
try :
df = pd.read_csv(self.csvPath, dtype=object, usecols=["objid", "ra", "dec"])
except ValueError as e :
runlog.critical("Invalid columns in .csv file\n\n{}".format(logFixFmt(
"Please ensure columns 'objid', 'ra' and 'dec' are present in the .csv \
file (in that order) and rerun!"
)))
raise e
# try block to read the result .csv file
try :
resIDs = [] if not os.path.exists(self.resCsvPath) else\
list(pd.read_csv(self.resCsvPath, dtype=object)['objid'])
except ValueError as e :
runlog.critical("Error in loading result csv file\n\n{}".format(logFixFmt(
"Please ensure the first column in 'objid'. If the file is corrupted, delete \
it and rerun!"
)))
self.galaxies = [(str(objid), (ra, dec))
for objid, ra, dec
in zip(df["objid"], df["ra"], df["dec"])
if str(objid) not in resIDs]
def __init__ (self, batchName, csvName, bands, rad) :
"""
Constructor for the batch. Does the following -
1. Sets up the folders/environment for the batch
2. Sets the result logger for the batch in the batch folder
3. Reads in the batch .csv and the result .csv file and decides
which objects remain to be classified
"""
self.batchName = batchName
self.csvName = csvName
self.__prelude__()
runlog.info("Successfully created environment for batch")
# Function to check if the band(s) supplied by the user is valid
areBandsValid = lambda bs : len([b for b in bs if b in Galaxy.default_bands]) == len(bs) != 0
######################################################################
# If the bands are not valid, a warning is logged
# This is because the Galaxy object internally takes care of
# invalid bands
######################################################################
if not areBandsValid(bands) :
runlog.warning("One or more bands in '{}' invalid\n\n{}".format(bands, logFixFmt(
"Please ensure that bands are a combination of 'ugri' only!"
)))
raise ValueError("Invalid Band. Please use 'ugri'")
self.bands = bands
# Sets the result logger for the batch
self.__setLoggers__()
# Initialises the galaxy objects that are yet to be classified in this batch
self.__setBatchList__()
print("Batch successfully initialised. \
\nThe classifications will be available at {} \
\nIn the event of any program crash/error, please check the log file at {} for details"\
.format(self.resCsvPath, os.path.join(os.getcwd(), runLogPath)))
print("Number of galaxies to classify - {}".format(len(self.galaxies)))
def __str__ (self) :
""" Batch object to string """
return self.csvPath
def __len__ (self) :
""" Length of the batch """
return len(self.galaxies)
@property
def batchFold (self) :
""" Property attribute - Path of the batch folder """
return os.path.join (os.getcwd(), Batch.batchRoot, self.batchName)
@property
def fitsFold (self) :
""" Property attribute - Path of the FITS folder for the batch """
return os.path.join (self.batchFold, "FITS")
@property
def resFold (self) :
""" Property attribute - Path of the folder that contains result images
post classificaton """
return os.path.join(self.batchFold, "Results")
@property
def csvPath (self) :
""" Property attribute - Path of the csv File """
return os.path.join(self.batchFold, self.csvName)
@property
def resCsvPath (self) :
""" Property attribute - Path of the result .csv file """
return os.path.join(self.batchFold, self.csvName[:-4] + "_result.csv")
@property
def pureCsvPath (self) :
""" Property attribute - Path of the pure .csv file """
return os.path.join(self.batchFold, self.csvName[:-4] + "_pure_pids.csv")
@property
def impureCsvPath (self) :
""" Property attribute - Path of the impure .csv file """
return os.path.join(self.batchFold, self.csvName[:-4] + "_impure.csv")
@property
def logPath (self) :
""" Property attribute - Path of the log file for the batch """
return os.path.join(os.getcwd(), self.batchFold, "{}.log".format(self.batchName))
def classifyGal (self, args) :
"""
Performs the following for an argument
1. Download the FITS file if necessary
2. Read the FITS file and obtain the cutout
3. Smoothen the cutout data
3. Find the hull region where peak searching is done
4. Filter the bands in this galaxy where signal is unlikely to be found
5. Fit the intensity distribution to a light profile
6. Find the peaks using Stochastic Hill Climbing and DFS
"""
try :
args += (self.fitsFold, self.bands)
g = Galaxy(*args)
g.download()
runlog.info("{} --> Downloaded".format(g.objid))
g.cutout()
runlog.info("{} --> Loaded and done cutout".format(g.objid))
g.smoothen()
runlog.info("{} --> Smoothed".format(g.objid))
g.hullRegion()
runlog.info("{} --> Found hull region".format(g.objid))
g.filter()
runlog.info("{} --> Filtered".format(g.objid))
g.fitProfile()
runlog.info("{} --> Fit intensity profile".format(g.objid))
g.setPeaks()
runlog.info("{} --> Found peaks".format(g.objid))
ret = (g.csvLine(), g.progressLine())
csvLine, progressLine = ret
purity, rep_band = pp.get_purity_band(g)
if rep_band is not None :
if purity :
bands = pp.get_bands_csv (g, self.bands)
pid1, pid2 = pp.peak_to_objid(g.cutouts[rep_band].wcs, g.peaks[rep_band].filtPeaks)
self.purelog.info(f"{g.objid},{bands},{pid1},{pid2}")
else :
self.impurelog.info(csvLine)
for b in g.bands :
if len(g.peaks[b].filtPeaks) != 2 :
continue
img = g.getPeaksMarked(b, True)
plt.imshow(img)
plt.axis('off')
plt.savefig(os.path.join(self.resFold, "{}-{}_result.png".format(g.objid, b)),
bbox_inches='tight',
pad_inches=0)
plt.close()
runlog.info("{} --> Results for manual impure classification".format(g.objid))
except Exception as e :
runlog.info("{} --> ERROR : {}".format(g.objid, e))
ret = (str(g.objid) + 2*len(self.bands)*",ERROR", str(g.objid) + " -->" + len(self.bands)*" ERROR")
g.delete()
runlog.info("{} --> Deleted files".format(g.objid))
del g
return ret
def classifySerial (self) :
self.gals = []
for i, args in enumerate(self.galaxies) :
csvLine, progLine = self.classifyGal(args)
self.reslog.info(csvLine)
print("{}. {}".format(i+1, progLine))