Skip to content

Commit

Permalink
Merge pull request #277 from DavidT3/unitTests/missionClasses
Browse files Browse the repository at this point in the history
Unit tests/mission classes
  • Loading branch information
jessicapilling committed May 8, 2024
2 parents 85fbc87 + 6aeeeef commit 5c55c53
Show file tree
Hide file tree
Showing 16 changed files with 1,343 additions and 18 deletions.
32 changes: 28 additions & 4 deletions daxa/mission/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1289,7 +1289,16 @@ def filter_on_positions(self, positions: Union[list, np.ndarray, SkyCoord],

# Checks to see if a list/array of coordinates has been passed, in which case we convert it to a
# SkyCoord (or a SkyCoord catalogue).
if isinstance(positions, (list, np.ndarray)):
# Firstly checking if it is a nested list or a list
if isinstance(positions, list):
if all(isinstance(i, list) for i in positions):
# Then it is a nested list
positions = SkyCoord(positions, unit=u.deg, frame=self.coord_frame)
else:
# Then it is one position in a list
positions = SkyCoord(positions[0], positions[1], unit=u.deg, frame=self.coord_frame)

if isinstance(positions, np.ndarray):
positions = SkyCoord(positions, unit=u.deg, frame=self.coord_frame)
# If the input was already a SkyCoord, we should make sure that it is in the same frame as the current
# mission's observation position information (honestly probably doesn't make that much of a difference, but
Expand Down Expand Up @@ -1753,7 +1762,7 @@ def filter_on_positions_at_time(self, positions: Union[list, np.ndarray, SkyCoor
"""
# Check that the start and end information is in the same style
if isinstance(start_datetimes, datetime) != isinstance(end_datetimes, datetime):
raise TypeError("The 'start_datetimes' and 'start_datetimes' must either both be individual datetimes, or "
raise TypeError("The 'start_datetimes' and 'end_datetimes' must either both be individual datetimes, or "
"arrays of datetimes (for multiple positions).")
# Need to make sure we make the datetimes iterable - even if there is only one position/time period being
# investigated
Expand All @@ -1766,13 +1775,28 @@ def filter_on_positions_at_time(self, positions: Union[list, np.ndarray, SkyCoor
if isinstance(positions, list) and not isinstance(positions[0], (list, SkyCoord)):
positions = [positions]

# Checking if positions is scalar or not. This is checked for np.ndarrays, lists and skycoord differently
if isinstance(positions, SkyCoord):
pos_scalar = positions.isscalar

elif isinstance(positions, list):
if len(positions) == 1:
pos_scalar = True

else:
pos_scalar = False

else:
# In this indent positions should be an np.ndarray, which should be not scalar
pos_scalar = False

# We initially check that the arguments we will be basing the time filtering on are of the right length,
# i.e. every position must have corresponding start and end times
if not positions.isscalar and (len(start_datetimes) != len(positions) or len(end_datetimes) != len(positions)):
if not pos_scalar and (len(start_datetimes) != len(positions) or len(end_datetimes) != len(positions)):
raise ValueError("The 'start_datetimes' (len={sd}) and 'end_datetimes' (len={ed}) arguments must have one "
"entry per position specified by the 'positions' (len={p}) "
"arguments.".format(sd=len(start_datetimes), ed=len(end_datetimes), p=len(positions)))
elif positions.isscalar and (len(start_datetimes) != 1 or len(end_datetimes) != 1):
elif pos_scalar and (len(start_datetimes) != 1 or len(end_datetimes) != 1):
raise ValueError("The 'start_datetimes' (len={sd}) and 'end_datetimes' (len={ed}) arguments must be "
"scalar if a single position is passed".format(sd=len(start_datetimes),
ed=len(end_datetimes)))
Expand Down
24 changes: 10 additions & 14 deletions daxa/mission/erosita.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,6 @@ def __init__(self, insts: Union[List[str], str] = None, fields: Union[List[str],
# Call the name property to set up the name and pretty name attributes
self.name

# Runs the method which fetches information on all available RASS observations and stores that
# information in the all_obs_info property
self._fetch_obs_info()
# Slightly cheesy way of setting the _filter_allowed attribute to be an array identical to the usable
# column of all_obs_info, rather than the initial None value
self.reset_filter()

# We now will read in the previous state, if there is one to be read in.
if save_file_path is not None:
Expand Down Expand Up @@ -307,7 +301,9 @@ def all_mission_fields(self) -> List[str]:
:return: A list of field names.
:rtype: List[str]
"""
return self._miss_poss_fields
# _miss_poss_fields returns all the field_name column of EROSITA_CALPV_INFO
# so set() is used to remove duplicate field names where obs_ids have the same field name
return list(set(self._miss_poss_fields))

@property
def all_mission_field_types(self) -> List[str]:
Expand Down Expand Up @@ -341,7 +337,8 @@ def chosen_fields(self, new_fields: List[str]):
be processed into the archive.
"""
self._chos_fields = self._check_chos_fields(new_fields)


# Then define user-facing methods
def _fetch_obs_info(self):
"""
This method uses the hard coded csv file to pull information on all eROSITACalPV observations.
Expand Down Expand Up @@ -399,11 +396,12 @@ def _check_chos_fields(self, fields: Union[List[str], str]):
bad_fields = [f for f in fields if f not in poss_alt_field_names and f not in self._miss_poss_fields
and f not in self._miss_poss_field_types and f != 'CRAB']
if len(bad_fields) != 0:
raise ValueError("Some field names or field types {bf} are not associated with this mission, please "
raise ValueError("Some field names or field types: {bf} are not associated with this mission, please "
"choose from the following fields; {gf} or field types; "
"{gft}".format(bf=",".join(bad_fields),
gf=",".join(self._miss_poss_fields),
gft=",".join(self._miss_poss_field_types)))
gf=",".join(list(set(self._miss_poss_fields))),
gft=",".join(self.all_mission_field_types)))


# Extracting the alt_fields from fields
alt_fields = [field for field in fields if field in poss_alt_field_names]
Expand Down Expand Up @@ -1246,13 +1244,12 @@ def _download_call(obs_id: str, raw_dir: str, download_products: bool, pipeline_
# as the user can specify the version (and as we want to use the latest version if they didn't) we need to
# see what is available
vers = list(set([td.split('_')[-1].replace('/', '') for td in top_data]))

if pipeline_version is not None and pipeline_version not in vers:
raise ValueError("The specified pipeline version ({p}) is not available for "
"{oi}".format(p=pipeline_version, oi=obs_id))
else:
pipeline_version = vers[np.argmax([int(pv) for pv in vers])]

# Final check that the online archive directory that we're pointing at does actually contain the data
# directories we expect it too. Every mission I've implemented I seem to have done this in a slightly
# different way, but as eROSITA is an active project things are more liable to change and I think this
Expand Down Expand Up @@ -1288,7 +1285,6 @@ def _download_call(obs_id: str, raw_dir: str, download_products: bool, pipeline_
# Finally we strip anything that doesn't match the file pattern defined by whether the user wants
# pre-generated products or not
to_down = [f for patt in down_patt for f in all_files if patt in f]

# Now we cycle through the files and download them
for down_file in to_down:
down_url = cur_url + down_file
Expand Down
44 changes: 44 additions & 0 deletions tests/test_asca.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import unittest

from astropy.coordinates import FK5
from astropy.units import Quantity

from daxa.mission import ASCA

# Would usually put this in a setUp() function, but it takes some time to instantiate
# Putting the mission object up here instead saves time when running the tests
defaults = ASCA()

class TestASCA(unittest.TestCase):
def test_valid_inst_selection(self):
# Checking that inst argument is working correctly
mission = ASCA(insts=['SIS0', 'SIS1'])
self.assertEqual(mission.chosen_instruments, ['SIS0', 'SIS1'])

def test_valid_inst_selection_alt_names(self):
# Alternative instrument names should be able to be parsed
with self.assertWarns(UserWarning):
mission = ASCA(insts=['S0', 'S1'])
self.assertEqual(mission.chosen_instruments, ['SIS0', 'SIS1'])

def test_wrong_insts(self):
# Shouldnt be able to declare an invalid instrument
with self.assertRaises(ValueError):
ASCA(insts=['wrong'])

# the basic properties of the class are returning what is expected
def test_name(self):
self.assertEqual(defaults.name, 'asca')

def test_coord_frame(self):
self.assertEqual(defaults.coord_frame, FK5)

def test_id_regex(self):
self.assertEqual(defaults.id_regex, '^[0-9]{8}$')

def test_fov(self):
self.assertEqual(defaults.fov['SIS0'], Quantity(11, 'arcmin'))


if __name__ == '__main__':
unittest.main()
36 changes: 36 additions & 0 deletions tests/test_chandra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import unittest

from astropy.coordinates import ICRS
from astropy.units import Quantity

from daxa.mission import Chandra

# Would usually put this in a setUp() function, but it takes some time to instantiate
# Putting the mission object up here instead saves time when running the tests
defaults = Chandra()

class TestChandra(unittest.TestCase):
def test_valid_inst_selection(self):
# Checking that inst argument is working correctly
mission = Chandra(insts=['ACIS-I', 'ACIS-S'])
self.assertEqual(mission.chosen_instruments, ['ACIS-I', 'ACIS-S'])

def test_wrong_insts(self):
# Shouldnt be able to declare an invalid instrument
with self.assertRaises(ValueError):
Chandra(insts=['wrong'])

# the basic properties of the class are returning what is expected
def test_name(self):
self.assertEqual(defaults.name, 'chandra')

def test_coord_frame(self):
self.assertEqual(defaults.coord_frame, ICRS)

def test_fov(self):
with self.assertWarns(UserWarning):
self.assertEqual(defaults.fov['ACIS-I'], Quantity(27.8, 'arcmin'))


if __name__ == '__main__':
unittest.main()
1 change: 1 addition & 0 deletions tests/test_data/html_responses/134135.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
'<!DOCTYPE html>\n<html lang="en">\n\n<head>\n <!--\n ------------------------------------------------------------\n eRODat, written by Jeremy Sanders (2022-2023)\n Please report problems at https://erosita-forum.mpe.mpg.de/\n ------------------------------------------------------------\n -->\n\n <title>Directory listing for /135/134/ | eRODat</title>\n <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">\n <link rel="stylesheet" href="/dr1/erodat/static/erodat.css">\n <script type="text/javascript" src="/dr1/erodat/static/jquery.js"></script>\n \n\n<link rel="stylesheet" type="text/css" href="/dr1/erodat/static/DataTables/datatables.min.css"/>\n<link rel="stylesheet" type="text/css" href="/dr1/erodat/static/DataTables-Select/css/select.dataTables.min.css"/>\n<script type="text/javascript" src="/dr1/erodat/static/DataTables/datatables.min.js"></script>\n<script type="text/javascript" src="/dr1/erodat/static/DataTables-Select/js/dataTables.select.min.js"></script>\n\n<script type="text/javascript" src="/dr1/erodat/static/cookie.js"></script>\n<script type="text/javascript" src="/dr1/erodat/data/add_to_basket.js"></script>\n\n<script type="text/javascript">\n\n $(document).ready( function () {\n var table = $(\'#files\').DataTable({\n columnDefs: [\n {\n // format size with commas, right-aligned\n targets: 3,\n render: $.fn.dataTable.render.number(\',\', \'.\', 0, \'\'),\n className: \'dt-body-right\',\n },\n ],\n ordering: false,\n searching: false,\n paging: false,\n select: {\n style: \'multi\',\n selector: \'tr.isfile\',\n },\n initComplete: function( settings, json ) {\n // hide table when loading and show after to avoid redraw\n $(\'#files\').show();\n },\n });\n\n // Add selected files to the basket\n $(\'#AddRows\').click( function() {\n var selected = $(\'#files\').DataTable().rows({selected: true});\n if(selected.count() == 0)\n return;\n\n const regexfn = /<a.*>(.+)<\\/a>/; // get filename from url\n var files = [];\n for(let i=0; i<selected.count(); i++) {\n var sel = selected.data()[i][0];\n var fname = "/135/134/" + sel.match(regexfn)[1];\n\n var tile = -1\n var dpart = fname.split(\'/\');\n if ( dpart.length > 2 ) {\n var tile = parseInt(dpart[2]+dpart[1]);\n }\n files.push({ptype: \'FILE\', filename: fname, \'skytile\': tile});\n }\n add_to_basket(files);\n });\n\n // enable/disable add button\n $(\'#files\').on(\'select.dt deselect.dt\', function () {\n var count = $(\'#files\').DataTable().rows( { selected: true } ).count();\n $(\'#AddRows\').prop(\'disabled\', count==0);\n });\n\n });\n\n</script>\n\n\n\n \n <!-- Matomo -->\n <script>\n var _paq = window._paq = window._paq || [];\n /* tracker methods like "setCustomDimension" should be called before "trackPageView" */\n _paq.push([\'trackPageView\']);\n _paq.push([\'enableLinkTracking\']);\n (function() {\n var u="//erosita.mpe.mpg.de/html/matomo/";\n _paq.push([\'setTrackerUrl\', u+\'matomo.php\']);\n _paq.push([\'setSiteId\', \'16\']);\n var d=document, g=d.createElement(\'script\'), s=d.getElementsByTagName(\'script\')[0];\n g.async=true; g.src=u+\'matomo.js\'; s.parentNode.insertBefore(g,s);\n })();\n </script>\n <!-- End Matomo Code -->\n \n</head>\n\n<body>\n <img src="/dr1/erodat/static/eROSITA_Logo_Blue.png" width="100%">\n <h1>eRODat: eROSITA-DE Data Release 1 archive</h1>\n\n <div class="navbar">\n <a href="https://erosita.mpe.mpg.de/dr1/">Main DR1 home</a>\n <a href="/dr1/erodat/">eRODat home</a>\n <a href="/dr1/erodat/skyview/sky/">Sky view</a>\n <div class="navdropdown">\n <button class="navdropbtn">Skytile search <i class="fa fa-caret-down"></i></button>\n <div class="navdropdown-content">\n <a href="/dr1/erodat/skyview/skytile_search/">Single position</a>\n <a href="/dr1/erodat/skyview/skytile_multi_search/">Multiple positions</a>\n <a href="/dr1/erodat/skyview/skytile_number_search/">By number</a>\n </div>\n </div>\n <div class="navdropdown">\n <button class="navdropbtn">Catalogue search <i class="fa fa-caret-down"></i></button>\n <div class="navdropdown-content">\n <a href="/dr1/erodat/catalogue/search/">Single position</a>\n <a href="/dr1/erodat/catalogue/search_by_id/">By eROSITA identifier</a>\n </div>\n </div>\n <div class="navdropdown">\n <button class="navdropbtn">Upper limits <i class="fa fa-caret-down"></i></button>\n <div class="navdropdown-content">\n <a href="/dr1/erodat/upperlimit/single/">Single position</a>\n <a href="/dr1/erodat/upperlimit/multi/">Multiple positions</a>\n </div>\n </div>\n <a href="/dr1/erodat/data/download/">Download area</a>\n <a href="/dr1/erodat/data/basket/">Basket</a>\n </div>\n\n <div id="content">\n \n\n<h2>Archive directory listing for\n <a href="../../">/</a><a href="../">135/</a><a href="./">134/</a>\n</h2>\n\n\n\n<div>\n<input title="Adds any selected data files to the basket" type="button" value="Add selected to basket" id="AddRows" style="display:inline;" disabled>\n<span id="BasketMessage"></span>\n</div>\n\n<table id="files" class="display" style="width:100%;display:none">\n <thead>\n <tr>\n <th>Name</th><th>Type</th><th>Date</th><th>Size</th>\n </tr>\n </thead>\n\n <tbody>\n\n \n <tr class="isdir"><td><a href="../">Parent directory</a></td><td>Directory</td><td>2023-11-03 12:22:30</td><td></td></tr>\n \n <tr class="isdir"><td><a href="DET_010/">DET_010/</a></td><td>Directory</td><td>2023-02-01 19:38:18</td><td></td></tr>\n \n <tr class="isdir"><td><a href="EXP_010/">EXP_010/</a></td><td>Directory</td><td>2022-11-25 14:58:38</td><td></td></tr>\n \n <tr class="isdir"><td><a href="SOU_010/">SOU_010/</a></td><td>Directory</td><td>2022-09-09 21:37:19</td><td></td></tr>\n \n <tr class="isdir"><td><a href="UPP_010/">UPP_010/</a></td><td>Directory</td><td>2023-10-10 08:07:45</td><td></td></tr>\n \n\n \n\n </tbody>\n</table>\n\n\n </div>\n\n <footer>\n <span><a href="https://www.mpe.mpg.de/impressum">Imprint</a></span>\n <span><a href="https://www.mpe.mpg.de/data-protection">Data Protection</a></span>\n <span>© eROSITA-DE, MPE</span>\n </footer>\n\n</body>\n</html>\n'

0 comments on commit 5c55c53

Please sign in to comment.