Coverage for soxspipe/commonutils/detect_continuum.py: 84%

# Coverage for soxspipe/commonutils/detect_continuum.py : 84%

## 276 statements 233 run 43 missing 0 excluded

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1#!/usr/bin/env python

2# encoding: utf-8

3"""

4*find and fit the continuum in a pinhole flat frame with low-order polynomials. These polynominals are the central loctions of the orders*

5

6:Author:

7 David Young & Marco Landoni

8

9:Date Created:

10 September 10, 2020

11"""

12################# GLOBAL IMPORTS ####################

13 from builtins import object

14 import sys

15 import os

16 os.environ['TERM'] = 'vt100'

17 from fundamentals import tools

18 from soxspipe.commonutils import keyword_lookup

19 from soxspipe.commonutils import detector_lookup

20 from os.path import expanduser

21 import numpy as np

22 from soxspipe.commonutils.polynomials import chebyshev_xy_polynomial

23 from soxspipe.commonutils.filenamer import filenamer

24 import matplotlib.pyplot as plt

25 from astropy.stats import mad_std

26 from astropy.modeling import models, fitting

27 from scipy.signal import find_peaks

28 from random import random

29 from scipy.optimize import curve_fit

30 from astropy.stats import sigma_clip, mad_std

31 from astropy.visualization import hist

32 import collections

33 from fundamentals.renderer import list_of_dictionaries

34 from soxspipe.commonutils.dispersion_map_to_pixel_arrays import dispersion_map_to_pixel_arrays

35 import pandas as pd

36

37

38 class detect_continuum(object):

39 """

40 *find and fit the continuum in a pinhole flat frame with low-order polynomials. These polynominals are the central loctions of the orders*

41

42 **Key Arguments:**

43 - ``log`` -- logger

44 - ``pinholeFlat`` -- calibrationed pinhole flat frame (CCDObject)

45 - ``dispersion_map`` -- path to dispersion map csv file containing polynomial fits of the dispersion solution for the frame

46 - ``settings`` -- the settings dictionary

47

48 **Usage:**

49

50 To use the ``detect_continuum`` object, use the following:

51

52 ```python

53 from soxspipe.commonutils import detect_continuum

54 detector = detect_continuum(

55 log=log,

56 pinholeFlat=pinholeFlat,

57 dispersion_map=dispersion_map,

58 settings=settings

59 )

60 order_table_path = detector.get()

61 ```

62

63 """

64

65 def __init__(

66 self,

67 log,

68 pinholeFlat,

69 dispersion_map,

70 settings=False

71 ):

72 self.log = log

73 log.debug("instansiating a new 'detect_continuum' object")

74 self.settings = settings

75 self.pinholeFlat = pinholeFlat

76 self.dispersion_map = dispersion_map

77

78 # KEYWORD LOOKUP OBJECT - LOOKUP KEYWORD FROM DICTIONARY IN RESOURCES

79 # FOLDER

80 self.kw = keyword_lookup(

81 log=self.log,

82 settings=self.settings

83 ).get

84 self.arm = pinholeFlat.header[self.kw("SEQ_ARM")]

85

86 # DETECTOR PARAMETERS LOOKUP OBJECT

87 self.detectorParams = detector_lookup(

88 log=log,

89 settings=settings

90 ).get(self.arm)

91

92 # DEG OF THE POLYNOMIALS TO FIT THE ORDER CENTRE LOCATIONS

93 self.polyDeg = self.settings[

94 "soxs-order-centre"]["poly-deg"]

95

96 return None

97

98 def get(self):

99 """

100 *return the order centre table filepath*

101

102 **Return:**

103 - ``order_table_path`` -- file path to the order centre table giving polynomial coeffs to each order fit

104 """

105 self.log.debug('starting the ``get`` method')

106

107 arm = self.arm

108

109 # READ THE SPECTRAL FORMAT TABLE TO DETERMINE THE LIMITS OF THE TRACES

110 orderNums, waveLengthMin, waveLengthMax = self.read_spectral_format()

111

112 # CONVERT WAVELENGTH TO PIXEL POSTIONS AND RETURN ARRAY OF POSITIONS TO

113 # SAMPLE THE TRACES

114 lineList = self.create_pixel_arrays(

115 orderNums,

116 waveLengthMin,

117 waveLengthMax)

118 # SLICE LENGTH TO SAMPLE TRACES IN THE CROSS-DISPERSION DIRECTION

119 self.sliceLength = self.settings[

120 "soxs-order-centre"]["slice-length"]

121 self.peakSigmaLimit = self.settings[

122 "soxs-order-centre"]["peak-sigma-limit"]

123

124 # FOR EACH ORDER, FOR EACH PIXEL POSITION SAMPLE, FIT A 1D GAUSSIAN IN

125 # CROSS-DISPERSION DIRECTTION. RETURN PEAK POSTIONS

126 lineList = lineList.apply(

127 self.fit_1d_gaussian_to_slice, axis=1)

128 allLines = len(lineList.index)

129 # DROP ROWS WITH NAN VALUES

130 lineList.dropna(axis='index', how='any',

131 subset=['cont_x'], inplace=True)

132 foundLines = len(lineList.index)

133 percent = 100 * foundLines / allLines

134 print(f"{foundLines} out of {allLines} found ({percent:3.0f}%)")

135

136 from tabulate import tabulate

137 print(tabulate(lineList, headers='keys', tablefmt='psql'))

138

139 # guassianPixelArrays = {}

140 # for order, xy_array in pixelArrays.items():

141 # found_xy_array = []

142 # for xy in xy_array:

143 # found_xy = self.fit_1d_gaussian_to_slice(

144 # xy, sliceLength, peakSigmaLimit)

145 # if found_xy:

146 # found_xy_array.append(found_xy)

147 # guassianPixelArrays[order] = found_xy_array

148

149 # percent = 100 * len(found_xy_array) / len(xy_array)

150 # print(f"{len(found_xy_array)} out of {len(xy_array)} found

151 # ({percent:3.0f}%)")

152

153 # GROUP RESULTS

154 # GET UNIQUE VALUES IN COLUMN

155 uniqueOrders = lineList['Order'].unique()

156 orderGroups = lineList.groupby(['Order'])

157

158 orderLoctions = {}

159 allResiduals = []

160 allXfit = []

161 allXcoords = []

162 allYcoords = []

163 for o in uniqueOrders:

164 # GROUP RESULTS

165 orderLineList = orderGroups.get_group(o)

166 # ITERATIVELY FIT THE POLYNOMIAL SOLUTIONS TO THE DATA

167 coeff, residuals, xfit, xcoords, ycoords = self.fit_polynomial(

168 orderLineList=orderLineList

169 )

170 allResiduals.extend(residuals)

171 allXfit.extend(xfit)

172 allXcoords.extend(xcoords)

173 allYcoords.extend(ycoords)

174 orderLoctions[o] = coeff

175

176 # sys.exit(0)

177

178 # orderLoctions = {}

179 # allResiduals = []

180 # allXfit = []

181 # allXcoords = []

182 # allYcoords = []

183 # for order, pixelArray in guassianPixelArrays.items():

184 # # ITERATIVELY FIT THE POLYNOMIAL SOLUTIONS TO THE DATA

185 # coeff, residuals, xfit, xcoords, ycoords = self.fit_polynomial(

186 # pixelArray=pixelArray

187 # )

188 # allResiduals.extend(residuals)

189 # allXfit.extend(xfit)

190 # allXcoords.extend(xcoords)

191 # allYcoords.extend(ycoords)

192 # orderLoctions[order] = coeff

193

194 self.plot_results(

195 allResiduals=allResiduals,

196 allXfit=allXfit,

197 allXcoords=allXcoords,

198 allYcoords=allYcoords,

199 orderLoctions=orderLoctions

200 )

201

202 mean_res = np.mean(np.abs(allResiduals))

203 std_res = np.std(np.abs(allResiduals))

204

205 print(f'\nThe order centre polynomial fitted against the observed 1D gaussian peak positions with a mean residual of {mean_res:2.2f} pixels (stdev = {std_res:2.2f} pixles)')

206

207 # WRITE OUT THE FITS TO THE ORDER CENTRE TABLE

208 order_table_path = self.write_order_table_to_file(orderLoctions)

209

210 self.log.debug('completed the ``get`` method')

211 return order_table_path

212

213 def read_spectral_format(

214 self):

215 """*read the spectral format table to get some key parameters*

216

217 **Return:**

218 - ``orderNums`` -- a list of the order numbers

219 - ``waveLengthMin`` -- a list of the maximum wavelengths reached by each order

220 - ``waveLengthMax`` -- a list of the minimum wavelengths reached by each order

221 """

222 self.log.debug('starting the ``read_spectral_format`` method')

223

224 kw = self.kw

225 pinholeFlat = self.pinholeFlat

226 dp = self.detectorParams

227

228 # READ THE SPECTRAL FORMAT TABLE FILE

229 home = expanduser("~")

230 calibrationRootPath = self.settings[

231 "calibration-data-root"].replace("~", home)

232 spectralFormatFile = calibrationRootPath + \

233 "/" + dp["spectral format table"]

234 spectralFormat = np.genfromtxt(

235 spectralFormatFile, delimiter=',', names=True)

236

237 # print(spectralFormat.dtype.names)

238

239 # EXTRACT REQUIRED PARAMETERS

240 orderNums = spectralFormat["ORDER"]

241 waveLengthMin = spectralFormat["WLMINFUL"]

242 waveLengthMax = spectralFormat["WLMAXFUL"]

243

244 self.log.debug('completed the ``read_spectral_format`` method')

245 return orderNums, waveLengthMin, waveLengthMax

246

247 def create_pixel_arrays(

248 self,

249 orderNums,

250 waveLengthMin,

251 waveLengthMax):

252 """*create a pixel array for the approximate centre of each order*

253

254 **Key Arguments:**

255 - ``orderNums`` -- a list of the order numbers

256 - ``waveLengthMin`` -- a list of the maximum wavelengths reached by each order

257 - ``waveLengthMax`` -- a list of the minimum wavelengths reached by each order

258

259 **Return:**

260 - ``lineList`` -- a data-frame containing lines and associated pixel locations

261 """

262 self.log.debug('starting the ``create_pixel_arrays`` method')

263

264 # READ ORDER SAMPLING RESOLUTION FROM SETTINGS

265 sampleCount = self.settings[

266 "soxs-order-centre"]["order-sample-count"]

267

268 # CREATE THE WAVELENGTH/ORDER ARRAYS TO BE CONVERTED TO PIXELS

269 myDict = {

270 "Order": np.asarray([]),

271 "Wavelength": np.asarray([]),

272 "slit_position": np.asarray([])

273 }

274 for o, wmin, wmax in zip(orderNums, waveLengthMin, waveLengthMax):

275

276 wlArray = np.arange(

277 wmin, wmax, (wmax - wmin) / sampleCount)

278 myDict["Wavelength"] = np.append(myDict["Wavelength"], wlArray)

279 myDict["Order"] = np.append(

280 myDict["Order"], np.ones(len(wlArray)) * o)

281 myDict["slit_position"] = np.append(

282 myDict["slit_position"], np.zeros(len(wlArray)))

283

284 lineList = pd.DataFrame(myDict)

285 lineList = dispersion_map_to_pixel_arrays(

286 log=self.log,

287 dispersionMapPath=self.dispersion_map,

288 lineList=lineList

289 )

290

291 self.log.debug('completed the ``create_pixel_arrays`` method')

292 return lineList

293

294 def fit_1d_gaussian_to_slice(

295 self,

296 linePixelPostion):

297 """*cut a slice from the pinhole flat along the cross-dispersion direction centred on pixel position, fit 1D gaussian and return the peak pixel position*

298

299 **Key Arguments:**

300 - ``linePixelPostion`` -- the x,y pixel coordinate from lineList data-frame (series)

301

302 **Return:**

303 - ``linePixelPostion`` -- now including gaussian fit peak xy position

304 """

305 self.log.debug('starting the ``fit_1d_gaussian_to_slice`` method')

306

307 # CLIP OUT A SLICE TO INSPECT CENTRED AT POSITION

308 halfSlice = self.sliceLength / 2

309

310 try:

311 slice = self.pinholeFlat.data[int(linePixelPostion["fit_y"]), max(

312 0, int(linePixelPostion["fit_x"] - halfSlice)):min(2048, int(linePixelPostion["fit_x"] + halfSlice))]

313 except:

314 linePixelPostion["cont_x"] = np.nan

315 linePixelPostion["cont_y"] = np.nan

316 return linePixelPostion

317

318 # CHECK THE SLICE POINTS IF NEEDED

319 if 1 == 0:

320 x = np.arange(0, len(slice))

321 plt.figure(figsize=(8, 5))

322 plt.plot(x, slice, 'ko')

323 plt.xlabel('Position')

324 plt.ylabel('Flux')

325 plt.show()

326

327 # EVALUATING THE MEAN AND STD-DEV FOR PEAK FINDING - REMOVES SLICE

328 # CONTAINING JUST NOISE

329 median_r = np.median(slice)

330 std_r = mad_std(slice)

331 peaks, _ = find_peaks(slice, height=median_r +

332 self.peakSigmaLimit * std_r, width=1)

333

334 # CHECK PEAK HAS BEEN FOUND

335 if peaks is None or len(peaks) <= 0:

336 # CHECK THE SLICE POINTS IF NEEDED

337 if 1 == 0:

338 x = np.arange(0, len(slice))

339 plt.figure(figsize=(8, 5))

340 plt.plot(x, slice, 'ko')

341 plt.xlabel('Position')

342 plt.ylabel('Flux')

343 plt.show()

344 return None

345

346 # FIT THE DATA USING A 1D GAUSSIAN - USING astropy.modeling

347 # CENTRE THE GAUSSIAN ON THE PEAK

348 g_init = models.Gaussian1D(

349 amplitude=1000., mean=peaks[0], stddev=1.)

350 # print(f"g_init: {g_init}")

351 fit_g = fitting.LevMarLSQFitter()

352

353 # NOW FIT

354 g = fit_g(g_init, np.arange(0, len(slice)), slice)

355 linePixelPostion["cont_x"] = g.mean + \

356 max(0, int(linePixelPostion["fit_x"] - halfSlice))

357 linePixelPostion["cont_y"] = linePixelPostion["fit_y"]

358

359 # PRINT A FEW PLOTS IF NEEDED - GUASSIAN FIT OVERLAYED

360 if 1 == 0 and random() < 0.02:

361 x = np.arange(0, len(slice))

362 plt.figure(figsize=(8, 5))

363 plt.plot(x, slice, 'ko')

364 plt.xlabel('Position')

365 plt.ylabel('Flux')

366 guassx = np.arange(0, max(x), 0.05)

367 plt.plot(guassx, g(guassx), label='Gaussian')

368 plt.show()

369

370 self.log.debug('completed the ``fit_1d_gaussian_to_slice`` method')

371 return linePixelPostion

372

373 def fit_polynomial(

374 self,

375 orderLineList):

376 """*iteratively fit the dispersion map polynomials to the data, clipping residuals with each iteration*

377

378 **Key Arguments:**

379 - ``orderLineList`` -- data-frame group containing x,y pixels of measured 1D guassian peak positions

380

381 **Return:**

382 - ``coeffs`` -- the coefficients of the polynomial fit

383 - ``residuals`` -- the residuals of the fit compared to original guassian peak positions

384 - ``xfit`` -- the polynomial fits x-positions

385 - ``xcoords`` -- the clean guassian peak x-coordinate list (post-clipping)

386 - ``ycoords`` -- the clean guassian peak x-coordinate list (post-clipping)

387 """

388 self.log.debug('starting the ``fit_polynomial`` method')

389

390 arm = self.arm

391

392 clippedCount = 1

393

394 poly = chebyshev_xy_polynomial(

395 log=self.log, deg=self.polyDeg).poly

396

397 clippingSigma = self.settings[

398 "soxs-order-centre"]["poly-fitting-residual-clipping-sigma"]

399

400 clippingIterationLimit = self.settings[

401 "soxs-order-centre"]["clipping-iteration-limit"]

402

403 xcoords = orderLineList["cont_x"].values

404 ycoords = orderLineList["cont_y"].values

405

406 iteration = 0

407 while clippedCount > 0 and iteration < clippingIterationLimit:

408 iteration += 1

409 # USE LEAST-SQUARED CURVE FIT TO FIT CHEBY POLY

410 coeff = np.ones((self.polyDeg + 1))

411 coeff, pcov_x = curve_fit(

412 poly, xdata=ycoords, ydata=xcoords, p0=coeff)

413

414 residuals, mean_res, std_res, median_res, xfit = self.calculate_residuals(

415 xcoords=xcoords,

416 ycoords=ycoords,

417 coeff=coeff)

418

419 # SIGMA-CLIP THE DATA

420 masked_residuals = sigma_clip(

421 residuals, sigma_lower=clippingSigma, sigma_upper=clippingSigma, maxiters=1, cenfunc='median', stdfunc=mad_std)

422

423 # MASK DATA ARRAYS WITH CLIPPED RESIDUAL MASK

424 startCount = len(xcoords)

425 a = [xcoords, ycoords, residuals]

426 xcoords, ycoords, residuals = [np.ma.compressed(np.ma.masked_array(

427 i, masked_residuals.mask)) for i in a]

428 clippedCount = startCount - len(xcoords)

429 print(f'{clippedCount} pixel positions where clipped in this iteration of fitting an order centre polynomial')

430

431 self.log.debug('completed the ``fit_polynomials`` method')

432 return coeff, residuals, xfit, xcoords, ycoords

433

434 def fit_polynomial_bk(

435 self,

436 pixelArray):

437 """*iteratively fit the dispersion map polynomials to the data, clipping residuals with each iteration*

438

439 **Key Arguments:**

440 - ``pixelArray`` -- the array of x,y pixels of measured 1D guassian peak positions

441

442 **Return:**

443 - ``coeffs`` -- the coefficients of the polynomial fit

444 - ``residuals`` -- the residuals of the fit compared to original guassian peak positions

445 - ``xfit`` -- the polynomial fits x-positions

446 - ``xcoords`` -- the clean guassian peak x-coordinate list (post-clipping)

447 - ``ycoords`` -- the clean guassian peak x-coordinate list (post-clipping)

448 """

449 self.log.debug('starting the ``fit_polynomial`` method')

450

451 arm = self.arm

452

453 clippedCount = 1

454

455 poly = chebyshev_xy_polynomial(

456 log=self.log, deg=self.polyDeg).poly

457

458 clippingSigma = self.settings[

459 "soxs-order-centre"]["poly-fitting-residual-clipping-sigma"]

460

461 clippingIterationLimit = self.settings[

462 "soxs-order-centre"]["clipping-iteration-limit"]

463

464 xcoords = [v[0] for v in pixelArray]

465 ycoords = [v[1] for v in pixelArray]

466

467 iteration = 0

468 while clippedCount > 0 and iteration < clippingIterationLimit:

469 iteration += 1

470 # USE LEAST-SQUARED CURVE FIT TO FIT CHEBY POLY

471 coeff = np.ones((self.polyDeg + 1))

472 coeff, pcov_x = curve_fit(

473 poly, xdata=ycoords, ydata=xcoords, p0=coeff)

474

475 residuals, mean_res, std_res, median_res, xfit = self.calculate_residuals(

476 xcoords=xcoords,

477 ycoords=ycoords,

478 coeff=coeff)

479

480 # SIGMA-CLIP THE DATA

481 masked_residuals = sigma_clip(

482 residuals, sigma_lower=clippingSigma, sigma_upper=clippingSigma, maxiters=1, cenfunc='median', stdfunc=mad_std)

483

484 # MASK DATA ARRAYS WITH CLIPPED RESIDUAL MASK

485 startCount = len(xcoords)

486 a = [xcoords, ycoords, residuals]

487 xcoords, ycoords, residuals = [np.ma.compressed(np.ma.masked_array(

488 i, masked_residuals.mask)) for i in a]

489 clippedCount = startCount - len(xcoords)

490 print(f'{clippedCount} pixel positions where clipped in this iteration of fitting an order centre polynomial')

491

492 self.log.debug('completed the ``fit_polynomials`` method')

493 return coeff, residuals, xfit, xcoords, ycoords

494

495 def calculate_residuals(

496 self,

497 xcoords,

498 ycoords,

499 coeff):

500 """*calculate residuals of the polynomial fits against the observed line postions*

501

502 **Key Arguments:**

503 - ``xcoords`` -- the measured x positions of the gaussian peaks

504 - ``ycoords`` -- the measurd y positions of the gaussian peaks

505 - ``coeff`` -- the coefficients of the fitted polynomial

506

507 **Return:**

508 - ``residuals`` -- x residuals

509 - ``mean`` -- the mean of the residuals

510 - ``std`` -- the stdev of the residuals

511 - ``median`` -- the median of the residuals

512 """

513 self.log.debug('starting the ``calculate_residuals`` method')

514

515 arm = self.arm

516

517 poly = chebyshev_xy_polynomial(

518 log=self.log, deg=self.polyDeg).poly

519

520 # CALCULATE RESIDUALS BETWEEN GAUSSIAN PEAK LINE POSITIONS AND POLY

521 # FITTED POSITIONS

522 xfit = poly(

523 ycoords, *coeff)

524 res_x = np.asarray(xfit) - np.asarray(xcoords)

525

526 # CALCULATE COMBINED RESIDUALS AND STATS

527 res_mean = np.mean(res_x)

528 res_std = np.std(res_x)

529 res_median = np.median(res_x)

530

531 self.log.debug('completed the ``calculate_residuals`` method')

532 return res_x, res_mean, res_std, res_median, xfit

533

534 def plot_results(

535 self,

536 allResiduals,

537 allXfit,

538 allXcoords,

539 allYcoords,

540 orderLoctions):

541 """*generate a plot of the polynomial fits and residuals*

542

543 **Key Arguments:**

544 - ``allResiduals`` -- list of all residuals

545 - ``allXfit`` -- list of all fitted x-positions

546 - ``allXcoords`` -- cleaned list of all guassian x-pixel positions

547 - ``allYcoords`` -- cleaned list of all guassian y-pixel positions

548 - ``orderLoctions`` -- dictionary of order-location polynomial coeff

549

550 **Return:**

551 - ``filePath`` -- path to the plot pdf

552 """

553 self.log.debug('starting the ``plot_results`` method')

554

555 arm = self.arm

556

557 # a = plt.figure(figsize=(40, 15))

558 if arm == "UVB":

559 fig = plt.figure(figsize=(6, 13.5), constrained_layout=True)

560 else:

561 fig = plt.figure(figsize=(6, 11), constrained_layout=True)

562 gs = fig.add_gridspec(6, 4)

563

564 # CREATE THE GID OF AXES

565 toprow = fig.add_subplot(gs[0:2, :])

566 midrow = fig.add_subplot(gs[2:4, :])

567 bottomleft = fig.add_subplot(gs[4:, 0:2])

568 bottomright = fig.add_subplot(gs[4:, 2:])

569

570 # ROTATE THE IMAGE FOR BETTER LAYOUT

571 rotatedImg = np.rot90(self.pinholeFlat.data, 1)

572 toprow.imshow(rotatedImg, vmin=10, vmax=50, cmap='gray', alpha=0.5)

573 toprow.set_title(

574 "1D guassian peak positions (post-clipping)", fontsize=10)

575 x = np.ones(len(allXcoords)) * \

576 self.pinholeFlat.data.shape[1] - allXcoords

577 toprow.scatter(allYcoords, x, marker='x', c='red', s=4)

578 # toprow.set_yticklabels([])

579 # toprow.set_xticklabels([])

580 toprow.set_ylabel("x-axis", fontsize=8)

581 toprow.set_xlabel("y-axis", fontsize=8)

582 toprow.tick_params(axis='both', which='major', labelsize=9)

583

584 midrow.imshow(rotatedImg, vmin=10, vmax=50, cmap='gray', alpha=0.5)

585 midrow.set_title(

586 "order-location fit solutions", fontsize=10)

587 ylinelist = np.arange(0, self.pinholeFlat.data.shape[0], 3)

588 poly = chebyshev_xy_polynomial(

589 log=self.log, deg=self.polyDeg).poly

590 for o, coeff in orderLoctions.items():

591 xfit = poly(ylinelist, *coeff)

592 xfit = np.ones(len(xfit)) * \

593 self.pinholeFlat.data.shape[1] - xfit

594 xfit, ylinelist = zip(

595 *[(x, y) for x, y in zip(xfit, ylinelist) if x > 0 and x < (self.pinholeFlat.data.shape[1]) - 10])

596 midrow.plot(ylinelist, xfit)

597

598 # xfit = np.ones(len(xfit)) * \

599 # self.pinholeFrame.data.shape[1] - xfit

600 # midrow.scatter(yfit, xfit, marker='x', c='blue', s=4)

601 # midrow.set_yticklabels([])

602 # midrow.set_xticklabels([])

603 midrow.set_ylabel("x-axis", fontsize=8)

604 midrow.set_xlabel("y-axis", fontsize=8)

605 midrow.tick_params(axis='both', which='major', labelsize=9)

606

607 # PLOT THE FINAL RESULTS:

608 plt.subplots_adjust(top=0.92)

609 bottomleft.scatter(allXcoords, allResiduals, alpha=0.2, s=1)

610 bottomleft.set_xlabel('x pixel position')

611 bottomleft.set_ylabel('x residual')

612 bottomleft.tick_params(axis='both', which='major', labelsize=9)

613

614 # PLOT THE FINAL RESULTS:

615 plt.subplots_adjust(top=0.92)

616 bottomright.scatter(allYcoords, allResiduals, alpha=0.2, s=1)

617 bottomright.set_xlabel('y pixel position')

618 bottomright.tick_params(axis='both', which='major', labelsize=9)

619 # bottomright.set_ylabel('x residual')

620 bottomright.set_yticklabels([])

621

622 mean_res = np.mean(np.abs(allResiduals))

623 std_res = np.std(np.abs(allResiduals))

624

625 subtitle = f"mean res: {mean_res:2.2f} pix, res stdev: {std_res:2.2f}"

626 fig.suptitle(f"traces of order-centre locations - pinhole flat-frame\n{subtitle}", fontsize=12)

627

628 # plt.show()

629 filename = filenamer(

630 log=self.log,

631 frame=self.pinholeFlat,

632 settings=self.settings

633 )

634 filename = filename.split("FLAT")[0] + "ORDER_CENTRES_residuals.pdf"

635

636 home = expanduser("~")

637 outDir = self.settings["intermediate-data-root"].replace("~", home)

638 filePath = f"{outDir}/{filename}"

639 plt.savefig(filePath)

640

641 self.log.debug('completed the ``plot_results`` method')

642 return filePath

643

644 def write_order_table_to_file(

645 self,

646 orderLoctions):

647 """*write out the fitted polynomial solution coefficients to file*

648

649 **Key Arguments:**

650 - ``orderLoctions`` -- dictionary of the order coefficients

651

652 **Return:**

653 - ``order_table_path`` -- path to the order table file

654 """

655 self.log.debug('starting the ``write_order_table_to_file`` method')

656

657 arm = self.arm

658

659 # SORT COEFFICIENT OUTPUT TO WRITE TO FILE

660 listOfDictionaries = []

661 for k, v in orderLoctions.items():

662 orderDict = collections.OrderedDict(sorted({}.items()))

663 orderDict["order"] = k

664 orderDict["degy"] = self.polyDeg

665 n_coeff = 0

666 for i in range(0, self.polyDeg + 1):

667 orderDict[f'CENT_c{i}'] = v[n_coeff]

668 n_coeff += 1

669 listOfDictionaries.append(orderDict)

670

671 # DETERMINE WHERE TO WRITE THE FILE

672 home = expanduser("~")

673 outDir = self.settings["intermediate-data-root"].replace("~", home)

674

675 filename = filenamer(

676 log=self.log,

677 frame=self.pinholeFlat,

678 settings=self.settings

679 )

680 filename = filename.split("FLAT")[0] + "ORDER_CENTRES.csv"

681

682 order_table_path = f"{outDir}/{filename}"

683 dataSet = list_of_dictionaries(

684 log=self.log,

685 listOfDictionaries=listOfDictionaries

686 )

687 csvData = dataSet.csv(filepath=order_table_path)

688

689 self.log.debug('completed the ``write_order_table_to_file`` method')

690 return order_table_path

691

692 # use the tab-trigger below for new method

693 # xt-class-method