LALPulsar  6.1.0.1-fe68b98
pulsarhtmlutils.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 #
3 # pulsarhtmlutils.py
4 #
5 # Copyright 2016
6 # Matthew Pitkin <matthew.pitkin@ligo.org>
7 #
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 # MA 02110-1301, USA.
23 
24 """
25 Some helper classes and functions for outputing html and LaTeX pages
26 """
27 
28 from __future__ import print_function
29 
30 import re
31 import numpy as np
32 import math
33 import six
34 
35 from lalpulsar.pulsarpputils import rad_to_dms, rad_to_hms
36 
37 # some parameter names for special LaTeX treatment in figures
38 paramlatexdict = {
39  "H0": "$h_0$",
40  "COSIOTA": "$\\cos{\\iota}$",
41  "PSI": "$\\psi$ (rad)",
42  "PHI0": "$\\phi_0$ (rad)",
43  "RA": "$\\alpha$",
44  "DEC": "$\\delta$",
45  "RAJ": "$\\alpha$",
46  "DECJ": "$\\delta$",
47  "F0": "$f_0$ (Hz)",
48  "F1": "$\\dot{f}$ (Hz/s)",
49  "F2": "$\\ddot{f}$ (Hz/s$^2$)",
50  "F3": "$f_3$ (Hz/s$^3$)",
51  "F4": "$f_4$ (Hz/s$^4$)",
52  "F5": "$f_5$ (Hz/s$^5$)",
53  "F6": "$f_6$ (Hz/s$^6$)",
54  "F7": "$f_7$ (Hz/s$^7$)",
55  "F8": "$f_8$ (Hz/s$^8$)",
56  "F9": "$f_9$ (Hz/s$^9$)",
57  "F10": "$f_{10}$ (Hz/s$^{10}$)",
58  "LOGL": "$\\log{L}$",
59  "PMRA": "p.m. $\\alpha$ (rad/s)",
60  "PMDEC": "p.m. $\\delta$ (rad/s)",
61  "PMDC": "p.m. $\\delta$ (rad/s)",
62  "PX": "$\\pi$ (rad)",
63  "A1": "$a \\sin{i}$ (lt s)",
64  "A1_2": "$(a \\sin{i})_{2}$ (lt s)",
65  "A1_3": "$(a \\sin{i})_{3}$ (lt s)",
66  "SINI": "$\\sin{i}$",
67  "PB": "$P_b$ (s)",
68  "PB_2": "$(P_b)_2$ (s)",
69  "PB_3": "$(P_b)_3$ (s)",
70  "T0": "$T_0$ (s)",
71  "T0_2": "$(T_0)_2$ (s)",
72  "T0_3": "$(T_0)_3$ (s)",
73  "TASC": "$T_{\\\textrm{asc}}$ (s)",
74  "OM": "$\\omega_0$ (rad)",
75  "OM_2": "$(\\omega_0)_2$ (rad)",
76  "OM_3": "$(\\omega_0)_3$ (rad)",
77  "PBDT": "$\\dot{P}$ (s/s)",
78  "PBDOT": "$\\dot{P}$ (s/s)",
79  "GAMMA": "$\\gamma$",
80  "E": "$e$",
81  "ECC": "$e$",
82  "ECC_2": "$e_2$",
83  "ECC_3": "$e_3$",
84  "FB0": "$(f_b)_0$ (Hz)",
85  "FB1": "$(f_b)_1$ (Hz)",
86  "FB2": "$(f_b)_2$ (Hz)",
87  "FB3": "$(f_b)_3$ (Hz)",
88  "FB4": "$(f_b)_4$ (Hz)",
89  "FB5": "$(f_b)_5$ (Hz)",
90  "FB6": "$(f_b)_6$ (Hz)",
91  "FB7": "$(f_b)_7$ (Hz)",
92  "FB8": "$(f_b)_8$ (Hz)",
93  "FB9": "$(f_b)_9$ (Hz)",
94  "M2": "$m_2$ (kg)",
95  "MTOT": "$M$ (kg)",
96  "ELL": "$\\varepsilon$",
97  "H95": "$h_0^{95\\%}$",
98  "H0UL": "$h_0^{{{}\\%}}$",
99  "Q22": "$Q_{22}$\\,(kg\\,m$^2$)",
100  "SDRAT": "spin-down ratio",
101  "SDRAT95": "$h_0^{95\\%}$/h_0^{\\rm sd}$",
102  "SDLIM": "$h_0^{\\rm sd}$",
103  "F0ROT": "$f_{\\rm rot}$ (Hz)",
104  "F0GW": "$f_{\\rm gw}$ (Hz)",
105  "F1ROT": "$\\dot{f}_{\\rm rot}$ (Hz/s)",
106  "F1GW": "$\\dot{f}_{\\rm gw}$ (Hz/s)",
107  "SDPOWRAT": "power ratio (\\%)",
108  "OMDOT": "$\\dot{\\omega}$",
109  "OMDT": "$\\dot{\\omega}$",
110  "EPS1": "$\\epsilon_1$",
111  "EPS2": "$\\epsilon_2$",
112  "C22": "$C_{22}$",
113  "C21": "$C_{21}$",
114  "C22UL": "$C_{{22}}^{{{}\\%}}$",
115  "C21UL": "$C_{{21}}^{{{}\\%}}$",
116  "PHI22": "$\\phi_{22}$",
117  "PHI21": "$\\phi_{21}$",
118  "I31": "$I_{31}$",
119  "I21": "$I_{21}$",
120  "I31UL": "$I_{{31}}^{{{}\\%}}$",
121  "I21UL": "$I_{{21}}^{{{}\\%}}$",
122  "LAMBDA": "$\\lambda$ (rad)",
123  "COSTHETA": "$\\cos{\\\theta}$",
124  "DIST": "distance (kpc)",
125  "SNR": "$\\rho$",
126  "BSN": "$\\log{}_{10}\\left(B_{\\\textrm{SvN}}\\right)$",
127  "BCI": "$\\log{}_{10}\\left(B_{\\\textrm{CvI}}\\right)$",
128  "BCIN": "$\\log{}_{10}\\left(B_{\\\textrm{CvIN}}\\right)$",
129 }
130 
131 
132 # html text to display for different parameter names
133 paramhtmldict = {
134  "RAJ": "&alpha;",
135  "DECJ": "&delta;",
136  "RA": "&alpha;",
137  "DEC": "&delta;",
138  "F0": "f<sub>0</sub> (Hz)",
139  "F1": "f<sub>1</sub> (Hz/s)",
140  "F2": "f<sub>2</sub> (Hz/s<sup>2</sup>)",
141  "F3": "f<sub>3</sub> (Hz/s<sup>3</sup>)",
142  "F4": "f<sub>4</sub> (Hz/s<sup>4</sup>)",
143  "F5": "f<sub>5</sub> (Hz/s<sup>5</sup>)",
144  "F6": "f<sub>6</sub> (Hz/s<sup>6</sup>)",
145  "F7": "f<sub>7</sub> (Hz/s<sup>7</sup>)",
146  "F8": "f<sub>8</sub> (Hz/s<sup>8</sup>)",
147  "F9": "f<sub>9</sub> (Hz/s<sup>9</sup>)",
148  "F10": "f<sub>10</sub> (Hz/s<sup>10</sup>)",
149  "F0ROT": "f<sub>rotation</sub> (Hz)",
150  "F1ROT": "Spin-down<sub>rotation</sub> (Hz/s)",
151  "F0GW": "f<sub>GW</sub> (Hz)",
152  "F1GW": "Spin-down<sub>GW</sub> (Hz/s)",
153  "PEPOCH": "epoch (MJD)",
154  "A1": "a sin<it>i</i> (lt s)",
155  "A1_2": "(a sin<it>i</i>)<sub>2</sub> (lt s)",
156  "A1_3": "(a sin<it>i</i>)<sub>3</sub> (lt s)",
157  "SINI": "sin<it>i</it>$",
158  "E": "<it>e</it>",
159  "ECC": "<it>e</it>",
160  "ECC_2": "<it>e</it><sub>2</sub>",
161  "ECC_3": "<it>e</it><sub>3</sub>",
162  "EPS1": "&epsilon;<sub>1</sub>",
163  "EPS2": "&epsilon;<sub>2</sub>",
164  "T0": "T<sub>0</sub> (MJD)",
165  "T0_2": "(T<sub>0</sub>)<sub>2</sub> (MJD)",
166  "T0_3": "(T<sub>0</sub>)<sub>3</sub> (MJD)",
167  "TASC": "T<sub>asc</sub> (MJD)",
168  "OM": "&omega;<sub>0</sub> (deg)",
169  "OM_2": "(&omega;<sub>0</sub>)<sub>2</sub> (deg)",
170  "OM_3": "(&omega;<sub>0</sub>)<sub>3</sub> (deg)",
171  "M2": "<it>m</it><sub>2</sub> (kg)",
172  "MTOT": "<it>M</it> (kg)",
173  "PB": "<it>P</it><sub>b</sub> (days)",
174  "PB_2": "(<it>P</it><sub>b</sub>)<sub>2</sub> (days)",
175  "PB_3": "(<it>P</it><sub>b</sub>)<sub>3</sub> (days)",
176  "FB0": "(<it>f</it><sub>b</sub>)<sub>0</sub> (Hz)",
177  "FB1": "(<it>f</it><sub>b</sub>)<sub>1</sub> (Hz)",
178  "FB2": "(<it>f</it><sub>b</sub>)<sub>2</sub> (Hz)",
179  "FB3": "(<it>f</it><sub>b</sub>)<sub>3</sub> (Hz)",
180  "FB4": "(<it>f</it><sub>b</sub>)<sub>4</sub> (Hz)",
181  "FB5": "(<it>f</it><sub>b</sub>)<sub>5</sub> (Hz)",
182  "FB6": "(<it>f</it><sub>b</sub>)<sub>6</sub> (Hz)",
183  "FB7": "(<it>f</it><sub>b</sub>)<sub>7</sub> (Hz)",
184  "FB8": "(<it>f</it><sub>b</sub>)<sub>8</sub> (Hz)",
185  "FB9": "(<it>f</it><sub>b</sub>)<sub>9</sub> (Hz)",
186  "H0": "h<sub>0</sub>",
187  "C21": "C<sub>21</sub>",
188  "C21UL": "C<sub>21</sub><sup>{}%</sup>",
189  "C22": "C<sub>22</sub>",
190  "C22UL": "C<sub>22</sub><sup>{}%</sup>",
191  "I21": "I<sub>21</sub>",
192  "I21UL": "I<sub>21</sub><sup>{}%</sup>",
193  "I31": "I<sub>31</sub>",
194  "I31UL": "I<sub>31</sub><sup>{}%</sup>",
195  "COSIOTA": "cos&iota;",
196  "PSI": "&psi; (rad)",
197  "PHI0": "&phi;<sub>0</sub> (rad)",
198  "PHI21": "&phi;<sub>21</sub> (rad)",
199  "PHI22": "&phi;<sub>22</sub> (rad)",
200  "PMRA": "p.m. &alpha; (rad/s)",
201  "PMDC": "p.m. &delta; (rad/s)",
202  "PMDEC": "p.m. &delta; (rad/s)",
203  "PX": "&pi; (rad)",
204  "DIST": "Distance (kpc)",
205  "SDLIM": "Spin-down limit",
206  "ELL": "&#949;",
207  "SDRAT": "ratio",
208  "H95": "h<sub>0</sub><sup>95%</sup>",
209  "H0UL": "h<sub>0</sub><sup>{}%</sup>",
210  "H0PRIOR": "h<sub>0</sub><sup>95%</sup> prior",
211  "SDPOWRAT": "power ratio (%)",
212  "Q22": "Q<sub>22</sub> (kg m<sup>2</sup>)",
213  "BSN": "log<sub>10</sub>(B<sub>SvN</sub>)",
214  "BCI": "log<sub>10</sub>(B<sub>CvI</sub>)",
215  "BCIN": "log<sub>10</sub>(B<sub>CvIN</sub>)",
216  "MAXL": "log<sub>10</sub>(max. L)",
217  "SNR": "&rho;",
218 }
219 
220 
221 # function to return a float number to a given number of significant figures
222 def sigfig(x, sf):
223  return round(x, -int(math.floor(math.log10(abs(x))) - (sf - 1)))
224 
225 
226 # function will return a string with the number (input as a string) to two decimal
227 # places if it is greater than |0.01|, or the number in exponent for to one decimal
228 # place it it is smaller
229 def dec_or_exp(f, dp=2, horl="html"):
230  fv = float(f)
231  if np.abs(fv) > 0.01 and np.abs(fv) < 1000.0:
232  if horl == "html":
233  return repr(round(fv, dp))
234  else:
235  return "$%s$" % repr(round(fv, dp))
236  else:
237  return exp_str(fv, dp, otype=horl)
238 
239 
240 # a class containing functions to output html parameter values in the appropriate format
242  def RAJ(f, stype="hms"): # default to output in hh:mm:ss.s format
243  if stype == "hms":
244  return ra_str(f)
245  else: # output in radians
246  return dec_or_exp(f)
247 
248  def RA(f, stype="hms"): # default to output in hh:mm:ss.s format
249  if stype == "hms":
250  return ra_str(f)
251  else: # output in radians
252  return dec_or_exp(f)
253 
254  def DECJ(f, stype="dms"): # default to output in dd:mm:ss.s format
255  if stype == "dms":
256  return dec_str(f)
257  else: # output in radians
258  return dec_or_exp(f)
259 
260  def DEC(f, stype="dms"): # default to output in dd:mm:ss.s format
261  if stype == "dms":
262  return dec_str(f)
263  else: # output in radians
264  return dec_or_exp(f)
265 
266  def PMRA(f):
267  return dec_or_exp(f)
268 
269  def PMDEC(f):
270  return dec_or_exp(f)
271 
272  def PMDC(f):
273  return dec_or_exp(f)
274 
275  def F0(f, dp=5): # default to returning with 5
276  return dec_or_exp(f, dp=dp)
277 
278  def F1(f):
279  return exp_str(float(f), 2)
280 
281  def F2(f):
282  return exp_str(float(f), 2)
283 
284  def F3(f):
285  return exp_str(float(f), 2)
286 
287  def F4(f):
288  return exp_str(float(f), 2)
289 
290  def F5(f):
291  return exp_str(float(f), 2)
292 
293  def F6(f):
294  return exp_str(float(f), 2)
295 
296  def F7(f):
297  return exp_str(float(f), 2)
298 
299  def F8(f):
300  return exp_str(float(f), 2)
301 
302  def F9(f):
303  return exp_str(float(f), 2)
304 
305  def F10(f):
306  return exp_str(float(f), 2)
307 
308  def PEPOCH(f):
309  return "%.1f" % float(
310  repr(44244.0 + (float(f) + 51.184) / 86400.0)
311  ) # return epoch as an float (converted from GPS to MJD)
312 
313  def A1(f):
314  return dec_or_exp(f)
315 
316  def E(f):
317  return dec_or_exp(f)
318 
319  def EPS1(f):
320  return dec_or_exp(f)
321 
322  def EPS2(f):
323  return dec_or_exp(f)
324 
325  def M2(f):
326  return dec_or_exp(f)
327 
328  def MTOT(f):
329  return dec_or_exp(f)
330 
331  def SINI(f):
332  return dec_or_exp(f)
333 
334  def T0(f, stype=None):
335  if stype == "diff":
336  return dec_or_exp(repr((float(f) + 51.184) / 86400.0), dp=2)
337  else:
338  return "%.2f" % float(
339  repr(44244.0 + ((float(f) + 51.184) / 86400.0))
340  ) # convert from GPS to MJD for display
341 
342  def TASC(f, stype=None):
343  if stype == "diff":
344  return dec_or_exp(repr((float(f) + 51.184) / 86400.0), dp=2)
345  else:
346  return "%.2f" % float(
347  repr(44244.0 + ((float(f) + 51.184) / 86400.0))
348  ) # convert from GPS to MJD for display
349 
350  def OM(f):
351  return dec_or_exp(
352  repr(float(f) * 180.0 / math.pi), dp=1
353  ) # convert from rads to deg
354 
355  def PB(f):
356  return dec_or_exp(repr(float(f) / 86400.0)) # convert from seconds to days
357 
358  def H0(f):
359  return exp_str(float(f), 1)
360 
361  def H0UL(f):
362  return exp_str(float(f), 1)
363 
364  def C21(f):
365  return exp_str(float(f), 1)
366 
367  def C21UL(f):
368  return exp_str(float(f), 1)
369 
370  def C22(f):
371  return exp_str(float(f), 1)
372 
373  def C22UL(f):
374  return exp_str(float(f), 1)
375 
376  def I21(f):
377  return exp_str(float(f), 1)
378 
379  def I21UL(f):
380  return exp_str(float(f), 1)
381 
382  def I31(f):
383  return exp_str(float(f), 1)
384 
385  def I31UL(f):
386  return exp_str(float(f), 1)
387 
388  def COSIOTA(f):
389  return dec_or_exp(f)
390 
391  def PHI0(f):
392  return dec_or_exp(f)
393 
394  def PHI22(f):
395  return dec_or_exp(f)
396 
397  def PHI21(f):
398  return dec_or_exp(f)
399 
400  def PSI(f):
401  return dec_or_exp(f)
402 
403  def ELL(f):
404  return exp_str(float(f), 1)
405 
406  def SDLIM(f):
407  return exp_str(float(f), 1)
408 
409  def SDRAT(f):
410  fsf = sigfig(float(f), 2) # get value rounded to 2 significant figure
411  if fsf < 1.0: # if spin-down ratio is less than 1
412  return "%.2f" % fsf
413  elif fsf < 10.0: # if spin-down ratio is less than 10
414  return "%.1f" % fsf
415  else: # otherwise round to the nearest integer
416  return "%d" % round(fsf)
417 
418  def DIST(f):
419  return "%.1f" % float(f)
420 
421  def SDPOWRAT(f):
422  return "%d" % round(float(f))
423 
424  def Q22(f):
425  return dec_or_exp(f, dp=1) # quadrupole moment
426 
427  def F0ROT(f):
428  return dec_or_exp(f)
429 
430  def F0GW(f):
431  return dec_or_exp(f)
432 
433  def F1ROT(f):
434  return exp_str(float(f), 1)
435 
436  def F1GW(f):
437  return exp_str(float(f), 1)
438 
439  def PMRA(f):
440  return dec_or_exp(f)
441 
442  def PMDC(f):
443  return dec_or_exp(f)
444 
445  def BSN(f):
446  return dec_or_exp(f)
447 
448  def BCI(f):
449  return dec_or_exp(f)
450 
451  def BCIN(f):
452  return dec_or_exp(f)
453 
454  def DEFAULTSTR(f):
455  return f
456 
457 
458 # a class for outputting parameter values to the table
460  def ELL(f):
461  return exp_str(float(f), 1, "latex")
462 
463  def H95(f):
464  return exp_str(float(f), 1, "latex")
465 
466  def H0(f):
467  return exp_str(float(f), 1, "latex")
468 
469  def H0UL(f):
470  return exp_str(float(f), 1, "latex")
471 
472  def C21(f):
473  return exp_str(float(f), 1, "latex")
474 
475  def C21UL(f):
476  return exp_str(float(f), 1, "latex")
477 
478  def C22(f):
479  return exp_str(float(f), 1, "latex")
480 
481  def C22UL(f):
482  return exp_str(float(f), 1, "latex")
483 
484  def I21(f):
485  return exp_str(float(f), 1, "latex")
486 
487  def I21UL(f):
488  return exp_str(float(f), 1, "latex")
489 
490  def I31(f):
491  return exp_str(float(f), 1, "latex")
492 
493  def I31UL(f):
494  return exp_str(float(f), 1, "latex")
495 
496  def H0PRIOR(f):
497  return exp_str(float(f), 1, "latex")
498 
499  def SDLIM(f):
500  return exp_str(float(f), 1, "latex")
501 
502  def SDRAT(f):
503  fsf = sigfig(float(f), 2) # get value rounded to 2 significant figure
504  if fsf < 1.0: # if spin-down ratio is less than 1
505  return "%.2f" % fsf
506  elif fsf < 10.0: # if spin-down ratio is less than 10
507  return "%.1f" % fsf
508  else: # otherwise round to the nearest integer
509  return "%d" % fsf
510 
511  def RAJ(f):
512  return ra_str(f, "latex") # RA in string format
513 
514  def DECJ(f):
515  return dec_str(f, "latex") # dec in string format
516 
517  def RA(f):
518  return ra_str(f, "latex") # RA in string format
519 
520  def DEC(f):
521  return dec_str(f, "latex") # dec in string format
522 
523  def PMRA(f):
524  return dec_or_exp(f, horl="latex")
525 
526  def PMDEC(f):
527  return dec_or_exp(f, horl="latex")
528 
529  def PMDC(f):
530  return dec_or_exp(f, horl="latex")
531 
532  def M2(f):
533  return dec_or_exp(f, horl="latex")
534 
535  def MTOT(f):
536  return dec_or_exp(f, horl="latex")
537 
538  def DIST(f):
539  return "%.1f" % float(f)
540 
541  def SDPOWRAT(f):
542  return "%d" % int(f)
543 
544  def Q22(f):
545  return exp_str(float(f), 1, "latex") # quadrupole moment
546 
547  def F0(f):
548  return "%.2f" % float(f)
549 
550  def F0ROT(f):
551  return "%.2f" % float(f)
552 
553  def F0GW(f):
554  return "%.2f" % float(f)
555 
556  def F1(f):
557  return exp_str(float(f), 1, "latex")
558 
559  def F2(f):
560  return exp_str(float(f), 1, "latex")
561 
562  def F3(f):
563  return exp_str(float(f), 1, "latex")
564 
565  def F4(f):
566  return exp_str(float(f), 1, "latex")
567 
568  def F5(f):
569  return exp_str(float(f), 1, "latex")
570 
571  def F6(f):
572  return exp_str(float(f), 1, "latex")
573 
574  def F7(f):
575  return exp_str(float(f), 1, "latex")
576 
577  def F8(f):
578  return exp_str(float(f), 1, "latex")
579 
580  def F9(f):
581  return exp_str(float(f), 1, "latex")
582 
583  def F10(f):
584  return exp_str(float(f), 1, "latex")
585 
586  def F1ROT(f):
587  return exp_str(float(f), 1, "latex")
588 
589  def F1GW(f):
590  return exp_str(float(f), 1, "latex")
591 
592  def BSN(f):
593  return dec_or_exp(f, horl="latex")
594 
595  def BCI(f):
596  return dec_or_exp(f, horl="latex")
597 
598  def BCIN(f):
599  return dec_or_exp(f, horl="latex")
600 
601  def DEFAULTSTR(f):
602  return f
603 
604 
605 class htmltag:
606  """
607  A class to create a html tag
608  """
609 
610  def __init__(
611  self, tag, tagtext="", tagclass="", tagid="", tagstyle="", newline=False
612  ):
613  self._taginfo_taginfo = {}
614  self.set_tagset_tag(tag) # the name of the tag
615  self.set_tagtextset_tagtext(tagtext) # the text to go into the tag
616  self.set_tagclassset_tagclass(tagclass) # the tag class
617  self.set_tagidset_tagid(tagid) # the tag id
618  self.set_tagstyleset_tagstyle(tagstyle) # the tag style
619  self.set_tagextraset_tagextra("") # any additional formatting
620 
621  if newline:
622  self._newline_newline = "\n" # whether to add a new line after the tag
623  else:
624  self._newline_newline = ""
625 
626  # set the tag format
627  self._tagdata_tagdata = (
628  '<{tag} class="{class}" id="{id}" style="{style}"{extra}>{text}</{tag}>'
629  + self._newline_newline
630  )
631 
632  def __iadd__(self, ttext):
633  """
634  Overload the += operator to append text to tagtext
635  """
636  if not isinstance(ttext, str) and not isinstance(ttext, unicode):
637  raise ValueError("Error... appended text must be a string.")
638  else:
639  self.set_tagtextset_tagtext(self._taginfo_taginfo["text"] + ttext)
640  return self
641 
642  @property
643  def tag(self):
644  return self._taginfo_taginfo["tag"]
645 
646  def set_tag(self, t):
647  if not isinstance(t, str) and not isinstance(t, unicode):
648  raise ValueError("Error... 'tag' must be a string.")
649  else:
650  self._taginfo_taginfo["tag"] = t
651 
652  @property
653  def tagclass(self):
654  return self._taginfo_taginfo["class"]
655 
656  def set_tagclass(self, tclass):
657  if not isinstance(tclass, str) and not isinstance(tclass, unicode):
658  raise ValueError("Error... 'class' text must be a string.")
659  else:
660  self._taginfo_taginfo["class"] = tclass
661 
662  @property
663  def tagid(self):
664  return self._taginfo_taginfo["id"]
665 
666  def set_tagid(self, tid):
667  if not isinstance(tid, str) and not isinstance(tid, unicode):
668  raise ValueError("Error... 'id' text must be a string.")
669  else:
670  self._taginfo_taginfo["id"] = tid
671 
672  @property
673  def tagstyle(self):
674  return self._taginfo_taginfo["style"]
675 
676  def set_tagstyle(self, tstyle):
677  if not isinstance(tstyle, str) and not isinstance(tstyle, unicode):
678  raise ValueError("Error... 'style' text must be a string.")
679  else:
680  self._taginfo_taginfo["style"] = tstyle
681 
682  @property
683  def tagtext(self):
684  return self._taginfo_taginfo["text"]
685 
686  def set_tagtext(self, ttext):
687  if not isinstance(ttext, str) and not isinstance(ttext, unicode):
688  raise ValueError("Error... tag text must be a string.")
689  else:
690  self._taginfo_taginfo["text"] = ttext
691 
692  @property
693  def tagextra(self):
694  return self._taginfo_taginfo["extra"]
695 
696  def set_tagextra(self, textra):
697  if not isinstance(textra, str) and not isinstance(textra, unicode):
698  raise ValueError("Error... 'extra' tag text must be a string.")
699  else:
700  space = "" # add no space
701  if len(textra) > 0:
702  space = " " # add a space
703  self._taginfo_taginfo["extra"] = space + textra
704 
705  @property
706  def taginfo(self):
707  return self._taginfo_taginfo
708 
709  @property
710  def text(self):
711  # return the full tag
712  return self._tagdata_tagdata.format(**self.taginfotaginfo)
713 
714  def __str__(self):
715  return self.texttext
716 
717 
718 class atag(htmltag):
719  """
720  Class for a link tag
721  """
722 
723  def __init__(self, link, linktext="", linkclass="", linkid="", linkstyle=""):
724  """
725  Input the link and the text that the link surrounds
726  """
727  if linktext == "": # if no link text is given then just use the link itself
728  linktext = link
729 
730  htmltag.__init__(self, "a", linktext, linkclass, linkid, linkstyle)
731  self.set_tagextraset_tagextra('href="{}"'.format(link)) # add href
732 
733 
734 class htmltable(htmltag):
735  """
736  Class to make and return a html table
737  """
738 
739  def __init__(self, tag="table", tableclass="", tableid="", tablestyle=""):
740  htmltag.__init__(
741  self, "table", tagclass=tableclass, tagid=tableid, tagstyle=tablestyle
742  )
743 
744  self._rows_rows = []
745  self._nrows_nrows = 0 # number of rows
746  self._thisrow_thisrow = -1 # the index of the current row given row additions
747 
748  @property
749  def tabletext(self):
750  innertable = ""
751  for row in self._rows_rows:
752  rowtxt = ""
753  for data in row["data"]:
754  td = "td"
755  if data["header"]:
756  td = "th" # use header <th> tags for this value
757  datatag = htmltag(
758  td, data["text"], data["class"], data["id"], data["style"]
759  )
760  datatag.set_tagextra(
761  'rowspan="{rowspan}" colspan="{colspan}"'.format(**data)
762  )
763  rowtxt += datatag.text + " " # add space between <td> elements
764  rowtag = htmltag(
765  "tr", rowtxt, row["class"], row["id"], row["style"], newline=True
766  )
767  innertable += rowtag.text
768  self.set_tagtextset_tagtext(innertable)
769  return self.texttext
770 
771  def addrow(self, rowclass="", rowid="", rowstyle=""):
772  """
773  Add a new empty row dictionary to the list and increment the current row index
774  """
775  row = {"data": [], "class": rowclass, "id": rowid, "style": rowstyle}
776  self._rows_rows.append(row)
777  self._nrows_nrows += 1 # number of rows
778  self._thisrow_thisrow += 1 # the index of the row that has just been added
779 
780  def deleterow(self, rowidx):
781  """
782  Delete a row
783  """
784  if rowidx > self._nrows_nrows - 1:
785  print(
786  "Warning... cannot delete row '%d'. Only %d row in table."
787  % (rowdix, self._nrows_nrows)
788  )
789  else:
790  self._rows_rows.pop(rowidx) # remove row
791  self._nrows_nrows -= 1
792  self._thisrow_thisrow -= 1
793 
794  def adddata(
795  self,
796  datatext,
797  dataclass="",
798  dataid="",
799  datastyle="",
800  header=False,
801  rowspan=0,
802  colspan=0,
803  rowidx=None,
804  ):
805  """
806  Add table data <td> (or <th> is header is True) tags to a given row
807  """
808  if rowidx is None:
809  rowidx = self._thisrow_thisrow
810 
811  if rowidx > len(self._rows_rows) - 1:
812  raise ValueError("Warning... row index is out of range.")
813  else:
814  td = {
815  "text": datatext,
816  "class": dataclass,
817  "id": dataid,
818  "style": datastyle,
819  "header": header,
820  "rowspan": "",
821  "colspan": "",
822  }
823  if rowspan > 0:
824  td["rowspan"] = str(int(rowspan))
825  if colspan > 0:
826  td["colspan"] = str(int(colspan))
827 
828  self._rows_rows[rowidx]["data"].append(td)
829 
830  def __str__(self):
831  return self.tabletxt
832 
833 
834 class latextable:
835  """
836  Class to make a return a LaTeX table
837  """
838 
839  def __init__(
840  self,
841  ncolumns=1,
842  columnalign="c",
843  caption="",
844  label="",
845  floatval="h",
846  preamble="",
847  postamble="",
848  ):
849  """
850  Create a table environment with `ncolumns` columns positioned with `columnpos`
851  """
852  self._tableinfo_tableinfo = {} # dictionary containing the table data
853 
854  self.set_ncolumnsset_ncolumns(ncolumns) # set number of columns
855  self.set_columnalignset_columnalign(columnalign) # set column text alignment
856  self.set_captionset_caption(caption) # set table caption
857  self.set_labelset_label(label) # set table label
858  self.set_floatvalset_floatval(floatval) # set floating environment position
859  self.set_preambleset_preamble(preamble) # set any preamble before the tabular environment
860  self.set_postambleset_postamble(
861  postamble
862  ) # set any text to go after the tabular environment
863 
864  self._rows_rows = [] # a list of row data
865  self._nrows_nrows = 0 # number of rows
866  self._thisrow_thisrow = -1 # the index of the current row given row additions
867 
868  # set the table format
869  self._tableformat_tableformat = "\\begin{{table}}{{{floatval}}}\n{preamble}\\caption{{{caption}\\label{{{label}}}}}\n\\begin{{tabular}}{{{columnalign}}}\n{table}\n\\end{{tabular}}\n{postamble}\n\\end{{table}}"
870  self._tableinfo_tableinfo["data"] = ""
871 
872  def set_ncolumns(self, ncolumns):
873  # set number of columns in table
874  self._ncolumns_ncolumns = ncolumns
875 
876  def set_columnalign(self, columnalign):
877  # set the alignment of data within each column (if a list then this should be equal in length to ncolumns)
878  if isinstance(columnalign, list):
879  if len(columnalign) != self._ncolumns_ncolumns:
880  raise ValueError(
881  "Error... number of column alignments is not equal to the number of columns."
882  )
883  else:
884  for ca in columnalign:
885  if not isinstance(ca, str) and not isinstance(ca, unicode):
886  raise TypeError(
887  "Error... columnalign must be a list of strings."
888  )
889 
890  self._columnalign_columnalign = columnalign
891  else:
892  if isinstance(columnalign, str) or isinstance(columnalign, unicode):
893  if len(columnalign) == 1:
894  self._columnalign_columnalign = [columnalign for _ in range(self._ncolumns_ncolumns)]
895  else:
896  self._columnalign_columnalign = [columnalign]
897  else:
898  raise TypeError("Error... columnalign must be a list or a string.")
899 
900  self._tableinfo_tableinfo["columnalign"] = " ".join(self._columnalign_columnalign)
901 
902  def set_label(self, label):
903  # set the label
904  self._tableinfo_tableinfo["label"] = label
905 
906  @property
907  def label(self):
908  return self._tableinfo_tableinfo["label"]
909 
910  def set_caption(self, caption):
911  # set the table caption
912  self._tableinfo_tableinfo["caption"] = caption
913 
914  @property
915  def caption(self):
916  return self._tableinfo_tableinfo["caption"]
917 
918  def set_floatval(self, floatval):
919  # set the floating environment position
920  self._tableinfo_tableinfo["floatval"] = floatval
921 
922  def set_preamble(self, preamble):
923  # set any preamble before the tabular environment
924  self._tableinfo_tableinfo["preamble"] = preamble
925 
926  @property
927  def preamble(self):
928  return self._tableinfo_tableinfo["preamble"]
929 
930  def set_postamble(self, postamble):
931  # set any text to go after the tabular environment
932  self._tableinfo_tableinfo["postamble"] = postamble
933 
934  @property
935  def postamble(self):
936  return self._tableinfo_tableinfo["postamble"]
937 
938  def addrow(self, underline=False):
939  # add an empty row (underline will add a horizontal rule below the row)
940  row = {"data": [], "underline": underline}
941  self._rows_rows.append(row)
942  self._nrows_nrows += 1 # number of rows
943  self._thisrow_thisrow += 1 # the index of the row that has just been added
944 
945  def addhrule(self, rowidx=None):
946  # add horizontal rule
947  if rowidx == None: # add horizontal rule as new row
948  self.addrowaddrow(underline=True)
949  else:
950  self._rows_rows[rowidx]["underline"] = True
951 
952  def adddata(self, datatxt, multicolumn=0, mcalign="c", rowidx=None):
953  # add data to a row
954  if rowidx is None:
955  rowidx = self._thisrow_thisrow
956 
957  if rowidx > len(self._rows_rows) - 1:
958  raise ValueError("Warning... row index is out of range.")
959  else:
960  rowdata = {"text": datatxt}
961  if multicolumn != 0:
962  rowdata["multicolumn"] = multicolumn
963  rowdata["mcalign"] = mcalign
964 
965  self._rows_rows[rowidx]["data"].append(rowdata)
966 
967  @property
968  def tabletext(self):
969  # output the full table
970  self._tableinfo_tableinfo["table"] = ""
971  for i, row in enumerate(self._rows_rows):
972  rowtxt = []
973  ncols = 0
974  for data in row["data"]:
975  val = data["text"]
976  if isinstance(val, float) or isinstance(
977  val, int
978  ): # if a number convert into a string
979  val = repr(val)
980  if "multicolumn" in data:
981  ncols += data["multicolumn"]
982  rowtxt.append(
983  "\\multicolumn{%d}{%s}{%s} "
984  % (data["multicolumn"], data["mcalign"], val)
985  )
986  else:
987  ncols += 1
988  rowtxt.append(val + " ")
989  if ncols != self._ncolumns_ncolumns and (
990  len(rowtxt) != 0 and row["underline"] == False
991  ):
992  raise ValueError("Error... too many or too few inputs in row '%d'." % i)
993  if len(rowtxt) != 0:
994  self._tableinfo_tableinfo["table"] += "&".join(rowtxt) + "\\\n"
995  if row["underline"]: # add horizontal rule below row
996  self._tableinfo_tableinfo["table"] += "\\hline\n"
997 
998  self._tabletext_tabletext = self._tableformat_tableformat.format(**self._tableinfo_tableinfo)
999  return self._tabletext_tabletext
1000 
1001 
1002 # convert a floating point number into a string in X.X x 10^Z format
1003 def exp_str(f, p=1, otype="html"):
1004  if p > 16:
1005  print("Precision must be less than 16 d.p.", file=sys.stderr)
1006  p = 16
1007 
1008  s = "%.16e" % f
1009  ssplit = s.split("e")
1010  if otype.lower() == "html": # output html format
1011  return "%.*f&times;10<sup>%d</sup>" % (p, float(ssplit[0]), int(ssplit[1]))
1012  elif otype.lower() == "latex": # output LaTeX format
1013  return "\\ensuremath{%.*f\\!\\times\\!10^{%d}}" % (
1014  p,
1015  float(ssplit[0]),
1016  int(ssplit[1]),
1017  )
1018  else:
1019  raise ValueError("Error... 'otype' must be 'html' or 'latex'.")
1020 
1021 
1022 # convert a right ascension string in format 'hh:mm:ss.s' to a html/LaTeX string like H^h M^m S^s.ss
1023 def ra_str(ra, otype="html"):
1024  if isinstance(ra, six.string_types):
1025  hms = ra.split(":")
1026  elif isinstance(ra, float):
1027  hms = [str(v) for v in rad_to_hms(ra)]
1028  else:
1029  raise ValueError("Error... ra must be a string or a float.")
1030 
1031  if len(hms) == 1:
1032  hms.append("0")
1033  hms.append("0")
1034  elif len(hms) == 2:
1035  hms.append("0")
1036 
1037  ss = ("%.2f" % float(hms[2])).split(".")
1038 
1039  if otype.lower() == "html": # return html string
1040  return "%s<sup>h</sup>%s<sup>m</sup>%s<sup>s</sup>.%s" % (
1041  hms[0].zfill(2),
1042  hms[1].zfill(2),
1043  ss[0].zfill(2),
1044  ss[1].zfill(2),
1045  )
1046  elif otype.lower() == "latex": # return LaTeX string
1047  return "$%s^{\\rm h}%s^{\\rm m}%s^{\\rm s}\\!.%s$" % (
1048  hms[0].zfill(2),
1049  hms[1].zfill(2),
1050  ss[0].zfill(2),
1051  ss[1].zfill(2),
1052  )
1053  else:
1054  raise ValueError("Error... 'otype' input must be 'html' or 'latex'")
1055 
1056 
1057 # convert a declination string in format 'dd:mm:ss.s' to a html/LaTeX string like dd^o mm' ss''.ss
1058 def dec_str(dec, otype="html"):
1059  if isinstance(dec, six.string_types):
1060  dms = dec.split(":")
1061  elif isinstance(dec, float):
1062  dms = [str(v) for v in rad_to_dms(dec)]
1063  else:
1064  raise ValueError("Error... dec must be a string or a float.")
1065 
1066  if len(dms) == 1:
1067  dms.append("0")
1068  dms.append("0")
1069  elif len(dms) == 2:
1070  dms.append("0")
1071 
1072  ss = ("%.2f" % float(dms[2])).split(".")
1073 
1074  if otype.lower() == "html": # html output
1075  return "%s&deg;%s'%s\".%s" % (
1076  (re.sub(r"\+", "", dms[0])).zfill(2),
1077  dms[1].zfill(2),
1078  ss[0].zfill(2),
1079  ss[1].zfill(2),
1080  )
1081  elif otype.lower() == "latex": # LaTeX output
1082  return "$%s^{\\circ}%s'%s''\\!.%s$" % (
1083  (re.sub(r"\+", "", dms[0])).zfill(2),
1084  dms[1].zfill(2),
1085  ss[0].zfill(2),
1086  ss[1].zfill(2),
1087  )
1088  else:
1089  raise ValueError("Error... 'otype' must be 'html' or 'latex'.")
1090 
1091 
1092 """
1093 CSS files for results pages
1094 """
1095 
1096 # css file for the standard individual pulsar result page
1097 result_page_css = """
1098 /* create body style */
1099 body {
1100  font-family: "Avant Garde", Avantegarde, Verdana, Geneva, "Trebuchet MS", sans-serif;
1101 }
1102 
1103 /* create header name style */
1104 h1 {
1105  margin: 0px 0px 0px 0px;
1106  padding: 4px 4px 8px 4px;
1107  font-size: 20px;
1108  font-weight: bold;
1109  letter-spacing: 0px;
1110  font-family: "Avant Garde", Avantegarde, Verdana, Geneva, "Trebuchet MS", Sans-Serif;
1111  background-color: darkolivegreen;
1112 }
1113 
1114 h1 > a:link {
1115  color: white;
1116  text-shadow: 2px 2px 2px #1c2310;
1117  text-decoration: none;
1118 }
1119 
1120 h1 > a:visited {
1121  color: white;
1122  text-shadow: 2px 2px 2px #1c2310;
1123  text-decoration: none;
1124 }
1125 
1126 h1 > a:hover {
1127  color: white;
1128  text-shadow: 2px 2px 2px #7fa046;
1129  text-decoration: none;
1130 }
1131 
1132 h2 {
1133  margin: 0 0 8px 0;
1134  padding: 4px 4px 8px 4px;
1135  font-size: 16px;
1136  font-weight: bold;
1137  font-family: "Avant Garde", Avantegarde, Verdana, Geneva, "Trebuchet MS", Sans-Serif;
1138  text-shadow: 1px 1px 1px #333300;
1139  background-color: #666600;
1140  color: white
1141 }
1142 
1143 /* create footer style */
1144 #footer {
1145  border-top: 1px solid #999;
1146  padding: 15px;
1147  font-family: monospace;
1148  text-align: left;
1149 }
1150 
1151 /* create a class for a posterior plot image */
1152 .posplot{
1153  height: 400px;
1154  border: 0px solid #999;
1155 }
1156 
1157 /* create a class for a full joint posterior plot image */
1158 .jointplot{
1159  height: 600px;
1160  border: 0px solid #999;
1161 }
1162 
1163 /* create a class for a background distribution plot image */
1164 .backgroundplot{
1165  height: 400px;
1166  border: 0px solid #999;
1167 }
1168 
1169 /* create a class for a Bk data plot */
1170 .dataplot{
1171  height: 275px;
1172 }
1173 
1174 /* create class for an amplitude spectral density plot */
1175 .asdplot{
1176  height: 275px;
1177 }
1178 
1179 /* create class for an MCMC chain plot */
1180 .chainplot{
1181  height: 275px;
1182 }
1183 
1184 /* style for links list */
1185 .pagelinks {
1186  background-color: darkolivegreen;
1187  font-family: "Avant Garde", Avantegarde, Verdana, Geneva, "Trebuchet MS", Sans-Serif;
1188  overflow: hidden;
1189  color: white;
1190  font-size: 16px;
1191  padding: 0px 0px 3px 3px;
1192  margin: 0px 0px 8px 0px;
1193  text-shadow: 2px 2px 2px #1c2310;
1194 }
1195 
1196 div.pagelinks a:link {
1197  color: white;
1198  text-shadow: 2px 2px 2px #1c2310;
1199  text-decoration: none;
1200 }
1201 
1202 div.pagelinks a:visited {
1203  color: white;
1204  text-shadow: 2px 2px 2px #1c2310;
1205  text-decoration: none;
1206 }
1207 
1208 div.pagelinks a:hover {
1209  color: white;
1210  text-shadow: 2px 2px 2px #7fa046;
1211  text-decoration: none;
1212 }
1213 
1214 /* pulsar parameter table class */
1215 .pulsartable {
1216  background-color: floralwhite;
1217  border: 0px solid;
1218  border-radius: 4px;
1219  box-shadow: 2px 2px 2px 2px #d8d8d8;
1220  -webkit-box-shadow: 2px 2px 2px 2px #d8d8d8;
1221  -moz-box-shadow: 2px 2px 2px 2px #d8d8d8;
1222  padding: 4px 4px 4px 4px;
1223 }
1224 
1225 /* upper limits table class */
1226 .limitstable {
1227  background-color: floralwhite;
1228  border: 0px solid;
1229  border-radius: 4px;
1230  box-shadow: 2px 2px 2px 2px #d8d8d8;
1231  -webkit-box-shadow: 2px 2px 2px 2px #d8d8d8;
1232  -moz-box-shadow: 2px 2px 2px 2px #d8d8d8;
1233  padding: 4px 4px 4px 4px;
1234 }
1235 
1236 /* background evidence table class */
1237 .evidencetable {
1238  background-color: floralwhite;
1239  border: 0px solid;
1240  border-radius: 4px;
1241  box-shadow: 2px 2px 2px 2px #d8d8d8;
1242  -webkit-box-shadow: 2px 2px 2px 2px #d8d8d8;
1243  -moz-box-shadow: 2px 2px 2px 2px #d8d8d8;
1244  padding: 4px 4px 4px 4px;
1245 }
1246 
1247 /* set defaults for table data and table headers */
1248 table{
1249  border: 0px;
1250  border-collapse: collapse;
1251 }
1252 
1253 td{
1254  padding: 0px 8px 0px 8px;
1255  border: 0px;
1256 }
1257 
1258 th{
1259  padding: 0px 8px 0px 8px;
1260  border: 0px;
1261 }
1262 
1263 .leftborder{
1264  border-left: 1px solid #000;
1265 }
1266 
1267 .rightborder{
1268  border-right: 1px solid #000;
1269 }
1270 
1271 .topborder{
1272  border-top: 1px solid #000;
1273 }
1274 
1275 .bottomborder{
1276  border-bottom: 1px solid #000;
1277 }
1278 
1279 /* set text colour classes for detectors */
1280 .H1{
1281  color: red;
1282 }
1283 
1284 .H2{
1285  color: cyan;
1286 }
1287 
1288 .L1{
1289  color: green;
1290 }
1291 
1292 .V1{
1293  color: blue;
1294 }
1295 
1296 .G1{
1297  color: magenta;
1298 }
1299 
1300 .Joint{
1301  color: black;
1302  font-weight: bold;
1303 }
1304 """
1305 
1306 
1307 # create css file text for the tables of all results
1308 results_table_css = """
1309 /* create body style */
1310 body {
1311  font-family: Verdana, Geneva, "Trebuchet MS", sans-serif;
1312 }
1313 
1314 /* create footer style */
1315 #footer {
1316  border-top: 1px solid #999;
1317  padding: 15px;
1318  font-family: monospace;
1319  text-align: left;
1320 }
1321 
1322 /* create link style */
1323 a:link{
1324  color: #000000;
1325  text-decoration: none;
1326 }
1327 
1328 a:visited{
1329  color: #000000;
1330  text-decoration: none;
1331 }
1332 
1333 a:hover{
1334  color: #000000;
1335  text-decoration: none;
1336  text-shadow: 2px 2px 2px #ccc;
1337 }
1338 
1339 /* set defaults for table data and table headers */
1340 table{
1341  border: 0px;
1342  border-collapse: collapse;
1343 }
1344 
1345 td{
1346  padding: 0px 8px 0px 8px;
1347  border: 0px;
1348 }
1349 
1350 th{
1351  padding: 0px 8px 0px 8px;
1352  border: 0px;
1353 }
1354 
1355 .leftborder{
1356  border-left: 1px solid #000;
1357 }
1358 
1359 .rightborder{
1360  border-right: 1px solid #000;
1361 }
1362 
1363 .topborder{
1364  border-top: 1px solid #000;
1365 }
1366 
1367 .bottomborder{
1368  border-bottom: 1px solid #000;
1369 }
1370 
1371 /* set text colour classes for detectors */
1372 .H1{
1373  color: red;
1374 }
1375 
1376 .H2{
1377  color: cyan;
1378 }
1379 
1380 .L1{
1381  color: green;
1382 }
1383 
1384 .V1{
1385  color: blue;
1386 }
1387 
1388 .G1{
1389  color: magenta;
1390 }
1391 
1392 .Joint{
1393  color: black;
1394  font-weight: bold;
1395 }
1396 """
def __init__(self, link, linktext="", linkclass="", linkid="", linkstyle="")
Input the link and the text that the link surrounds.
Class to make and return a html table.
def addrow(self, rowclass="", rowid="", rowstyle="")
Add a new empty row dictionary to the list and increment the current row index.
def __init__(self, tag="table", tableclass="", tableid="", tablestyle="")
def deleterow(self, rowidx)
Delete a row.
def adddata(self, datatext, dataclass="", dataid="", datastyle="", header=False, rowspan=0, colspan=0, rowidx=None)
Add table data (or is header is True) tags to a given row.
A class to create a html tag.
def __init__(self, tag, tagtext="", tagclass="", tagid="", tagstyle="", newline=False)
def __iadd__(self, ttext)
Overload the += operator to append text to tagtext.
Class to make a return a LaTeX table.
def adddata(self, datatxt, multicolumn=0, mcalign="c", rowidx=None)
def addrow(self, underline=False)
def __init__(self, ncolumns=1, columnalign="c", caption="", label="", floatval="h", preamble="", postamble="")
Create a table environment with ncolumns columns positioned with columnpos
def set_columnalign(self, columnalign)
def dec_str(dec, otype="html")
def dec_or_exp(f, dp=2, horl="html")
def ra_str(ra, otype="html")
def exp_str(f, p=1, otype="html")
def rad_to_hms(rad)
rad_to_hms(rad): Convert radians to hours, minutes, and seconds of arc.
def rad_to_dms(rad)
rad_to_dms(rad): Convert radians to degrees, minutes, and seconds of arc.