From ba25a3276242ac01d82a14bd582a6d0e30347955 Mon Sep 17 00:00:00 2001
From: Phil Evans
Date: Tue, 7 May 2024 15:14:36 +0100
Subject: [PATCH 01/22] Removed errant file
---
swifttools/ukssdc/query/Jamie_BackEnd.py | 363 -----------------------
1 file changed, 363 deletions(-)
delete mode 100644 swifttools/ukssdc/query/Jamie_BackEnd.py
diff --git a/swifttools/ukssdc/query/Jamie_BackEnd.py b/swifttools/ukssdc/query/Jamie_BackEnd.py
deleted file mode 100644
index 29107e74..00000000
--- a/swifttools/ukssdc/query/Jamie_BackEnd.py
+++ /dev/null
@@ -1,363 +0,0 @@
-from .common import TOOAPI_Baseclass, TOOAPI_ObsID
-from .too_server import Swift_TOO_User
-from .swift_status import Swift_TOO_Status
-from .swift_afst import Swift_AFST
-import requests
-from bs4 import BeautifulSoup
-import os
-
-
-class Swift_Data_File(TOOAPI_Baseclass):
- def __init__(self, filename=None, url=None, quicklook=None, fullpath=None, path=None):
- TOOAPI_Baseclass.__init__(self)
- self.filename = filename
- self.path = path
- self.fullpath = fullpath
- self.url = url
- self.quicklook = quicklook
- self._size = None
- self._type = None
- self.rows = ['filename', 'path', 'url', 'quicklook', 'type']
- self.extrarows = []
-
- @property
- def type(self):
- if 'xrt' in self.path:
- inst = 'XRT'
- elif 'bat' in self.path:
- inst = 'BAT'
- elif 'uvot' in self.path:
- inst = 'UVOT'
- elif 'auxil' in self.path:
- inst = 'Auxillary'
- elif 'log' in self.path:
- inst = "Log"
-
- if "po_" in self.filename:
- acs = "pointed"
- elif "sl_" in self.filename:
- acs = "slew"
- elif "st_" in self.filename:
- acs = "settling"
- elif "sp_" in self.filename:
- acs = "slew/point"
- else:
- acs = ''
-
- if "_uf" in self.filename:
- filter = "unfiltered"
- elif "_cl" in self.filename:
- filter = "cleaned"
- elif "_ex" in self.filename:
- filter = "exposure map"
- elif "_sk" in self.filename or "skim" in self.filename:
- filter = "sky"
- elif "_rw" in self.filename:
- filter = "raw"
- else:
- filter = ''
-
- if "xpc" in self.filename:
- mode = 'PC'
- elif 'xwt' in self.filename:
- mode = 'WT'
- elif 'xim' in self.filename:
- mode = 'Image'
- elif 'uuu' in self.filename:
- mode = 'u'
- elif 'uvv' in self.filename:
- mode = 'v'
- elif 'ubb' in self.filename:
- mode = 'b'
- elif 'uw1' in self.filename:
- mode = 'uvw1'
- elif 'uw2' in self.filename:
- mode = 'uvw2'
- elif 'um2' in self.filename:
- mode = 'uvm2'
- elif 'uwh' in self.filename:
- mode = 'white'
- else:
- mode = ''
-
- if 'hk' in self.path:
- dtype = 'housekeeping'
- elif 'event' in self.path:
- dtype = 'event'
- # elif 'image' in self.path:
- # dtype = 'image'
- # elif 'products' in self.path:
- # dtype = 'products'
- elif 'rate' in self.path:
- dtype = 'rate'
- elif 'masktag' in self.path:
- dtype = 'mask tagged'
- elif 'survey' in self.path:
- dtype = 'survey'
- else:
- dtype = ''
-
- if 'gif' in self.filename:
- filetype = 'GIF preview'
- elif 'lc' in self.filename:
- filetype = 'lightcurve'
- elif 'dph' in self.filename:
- filetype = 'DPH'
- elif 'hk' in self.filename and dtype != 'housekeeping':
- filetype = 'housekeeping'
- elif '.cat' in self.filename:
- filetype = 'catalog'
- elif 'img' in self.filename:
- filetype = 'image'
- elif 'at.fits' in self.filename:
- filetype = 'attitude file'
- else:
- filetype = 'file'
- return f"{inst} {acs} {filter} {mode} {dtype} {filetype}".strip().replace(" ", " ")
-
- @property
- def fullpath(self):
- return os.path.join(self.path, self.filename)
-
- @fullpath.setter
- def fullpath(self, fp):
- if fp is not None:
- self.path, self.filename = os.path.split(fp)
-
- @property
- def size(self):
- if self._size is None:
- self._size = int(requests.get(
- self.url, stream=True).headers['Content-length'])
- return self._size
-
-
-class Swift_Data(TOOAPI_Baseclass, TOOAPI_ObsID):
- '''
- Class to download Swift data from the UK or US SDC for a given observation ID.
- '''
-
- # obsid=None,bat=None,xrt=None,uvot=None,uksdc=False,quicklook=False,outdir=".",all=None,clobber=False):
- def __init__(self, **kwargs):
- '''
- Construct the Swift_Data class, and download data if required parameters
- are supplied.
-
- Parameters
- ----------
- obsid : str / int
- the observation ID of the data to download. Can be in SDC or
- spacecraft format. Note: can also parse target_id and segment.
- auxil : boolean
- set to True to download Auxil data (default = True)
- bat : boolean
- set to True to download BAT data
- xrt : boolean
- set to True to download XRT data
- uvot : boolean
- set to True to download UVOT data
- log : boolean
- set to True to download SDC processing logs
- all : boolean
- set to True to download all data products
- quicklook : boolean
- set to True to only search Quicklook data. Default searches archive,
- then quicklook
- clobber : boolean
- overwrite existing data on disk (default: False)
- outdir : str
- directory where data should be downloaded to
- '''
- TOOAPI_Baseclass.__init__(self)
- TOOAPI_ObsID.__init__(self)
- self.obsid = None
- # Only look in quicklook
- self.quicklook = False
- self.uksdc = None
- # Two data locations "obs" = archive, "ql" = quicklook
- # Data types to download. Always download auxil as default
- self.auxil = True
- self.bat = None
- self.uvot = None
- self.xrt = None
- self.log = None
- self.clobber = False
- self.outdir = "."
- # Parse keywords
- for key in kwargs.keys():
- setattr(self, key, kwargs[key])
- # The files downloaded and the directories they live in
- self._files = list()
- self.directories = None
- self.datadir = None
- self.year = None
- self.month = None
- if self.obsid is not None:
- self.download()
- self.fileobj = []
- self.username = None
- self.subclasses = [Swift_Data_File]
- self.rows = ['username', 'obsid', 'quicklook',
- 'auxil', 'bat', 'xrt', 'uvot', 'log', 'uksdc']
- self.extrarows = ['entries', 'status']
- self.status = Swift_TOO_Status()
- self.user = Swift_TOO_User()
-
- def __getitem__(self, i):
- return self.entries[i]
-
- @property
- def table(self):
- header = ['Path', 'Filename', 'Size', 'Description']
- tabdata = []
- lastpath = ''
- for file in self.entries:
- if file.path != lastpath:
- path = file.path
- lastpath = path
- else:
- path = "''"
- tabdata.append([path, file.filename, file.size, file.type])
- return header, tabdata
-
- @property
- def all(self):
- if self.xrt and self.uvot and self.bat and self.log:
- return True
- return False
-
- @all.setter
- def all(self, bool):
- if self.all is not None:
- self.xrt = self.bat = self.uvot = self.log = self.auxil = bool
-
- def __find_latest_ql(self):
- '''For US SDC, look for the latest version of data available'''
- qldataurl = "https://swift.gsfc.nasa.gov/data/swift/"
- qldata = requests.get(qldataurl)
- ql = BeautifulSoup(qldata.text, 'html.parser')
- available = ql.find_all('pre')[0].text
- for line in available.splitlines():
- if self.obsid in line:
- return line.strip()
- return False
-
- @property
- def __datatype(self):
- '''For UK SDC, this is the difference between QL and Archive data'''
- if self.quicklook:
- return 'ql'
- else:
- return 'obs'
-
- @property
- def __top_dirs(self):
- '''Return the directories we want to download, based on requested'''
- dirs = []
- if self.auxil:
- dirs.append('auxil/')
- if self.xrt:
- dirs.append('xrt/')
- if self.bat:
- dirs.append('bat/')
- if self.uvot:
- dirs.append('uvot/')
- if self.log:
- dirs.append('log/')
- return dirs
-
- @property
- def dataurl(self):
- '''The URL for data, depending on which SDC you selected'''
- if self.uksdc:
- # UK SDC URLs are simples
- dataurl = f"http://www.swift.ac.uk/archive/{self.__datatype}/{self.obsid}"
- return dataurl
- else:
- # US SDC URLs are not
- if self.quicklook:
- # Quicklook data has different versions - figure out which version we need.
- if self.datadir is None:
- self.datadir = self.__find_latest_ql()
- if self.datadir is False:
- return False
- else:
- # If we found the data, construct the URL and return it
- dataurl = f"https://swift.gsfc.nasa.gov/data/swift/{self.datadir}"
- return dataurl
- else:
- # US SDC puts data in directories by month, so we have to figure out what directory it's
- # going to be in first
- if self.year is None:
- afst = Swift_AFST(obsnum=self.obsid)
- afst.query()
- if len(afst.entries) > 0:
- self.year, self.month = afst.entries[0].begin.year, afst.entries[0].begin.month
- else:
- return False
- # Return the base URL for the data
- dataurl = f"https://heasarc.gsfc.nasa.gov/FTP/swift/data/obs/{self.year}_{self.month:02d}/{self.obsid}"
- return dataurl
-
- def scanfiles(self):
- '''Make a list of the available files and directories'''
- directories = self.__top_dirs
-
- if self.dataurl is False:
- return False
- # Scan through SDC web pages looking for files associated with ObsID
- i = 0
- subdir = requests.get(self.dataurl)
- while(i < len(directories)):
- url = f"{self.dataurl}/{directories[i]}"
-
- subdir = requests.get(url)
-
- if "Error 404" not in subdir.text and "Sorry" not in subdir.text:
- soup = BeautifulSoup(subdir.text, 'html.parser')
- links = soup.find_all('a')
- newdirs = [ld.text.strip() for ld in links if "/" in ld.text]
- newfiles = [lf.text.strip() for lf in links if "/" not in lf.text]
- [directories.append(f"{directories[i]}{ddir}")
- for ddir in newdirs if ddir not in directories]
- self._files += [Swift_Data_File(fullpath=os.path.join(self.obsid, directories[i], dfile),
- url=f"{url}{dfile}", quicklook=self.quicklook) for dfile in newfiles if 'SWIFT_TLE_ARCHIVE' in dfile or self.obsid in dfile]
- i += 1
-
- self.directories = directories
- if len(self._files) > 0:
- return len(self._files)
- else:
- # Return False if nothing was found
- return False
-
- @property
- def files(self):
- '''A list of files associated with this obsid data'''
- # If no files, run scanfiles.
- if len(self._files) == 0:
- if not self.scanfiles() and self.quicklook is False:
- # If no files found in scan, and quicklook is not set, look in quicklook
- self.quicklook = True
- self.scanfiles()
- return self._files
-
- def validate(self):
- if self.obsid is None:
- self.status.error("Must supply Observation ID")
- if self.auxil is not True and self.xrt is not True and self.log is not True and self.bat is not True and self.uvot is not True:
- self.status.error("No data products selected")
- if len(self.status.errors) > 0:
- return False
- else:
- return True
-
- def query(self):
- if self.validate():
- self.entries = self.files
- if len(self.entries) == 0:
- self.status.error(
- f"No data found for observation ID {self.obsid}")
- self.status.status = 'Accepted'
- else:
- self.status.status = 'Rejected'
From 5975d090c6e84b6ffcda3d115b74b8cf34d23b91 Mon Sep 17 00:00:00 2001
From: Phil Evans
Date: Tue, 7 May 2024 15:41:57 +0100
Subject: [PATCH 02/22] Pulled in some formatting a doc changes that should
have been synced previously
---
swifttools/ukssdc/data/datarequest_base.py | 1016 ++++++++++++++++++++
swifttools/ukssdc/data/download.py | 12 +-
swifttools/ukssdc/query/GRB.py | 12 +-
3 files changed, 1028 insertions(+), 12 deletions(-)
create mode 100644 swifttools/ukssdc/data/datarequest_base.py
diff --git a/swifttools/ukssdc/data/datarequest_base.py b/swifttools/ukssdc/data/datarequest_base.py
new file mode 100644
index 00000000..d2fc6286
--- /dev/null
+++ b/swifttools/ukssdc/data/datarequest_base.py
@@ -0,0 +1,1016 @@
+# import json
+from . import allowedConeRadiusUnits
+from .. import main as base
+from ..access import downloadObsData
+import pandas as pd
+from .DBFilter import filter
+
+if base.HAS_ASTROPY:
+ import astropy.coordinates
+
+
+# allowedConeRadiusUnits = ("arcsec", "arcmin", "degree", "deg")
+class dataRequest:
+ """The base case for UKSSDC data requests. A 'virtual' class.
+
+ This class should never in itself be instantiated, only those
+ dervied from it. In fact, it will contain enough functionality to
+ run for the most basic of cases, except that it will never select
+ which table / catalogue is being searched.
+
+ MORE DOCS - maybe give a summary of the methods here.
+ """
+
+ def __init__(self, silent=True, verbose=False):
+ """Create a dataRequest instance.
+
+ Parameters
+ ----------
+ silent : bool Whether to suppress all console output
+ (default: True).
+
+ verbose : bool Whether to give verbose output for everything
+ (default: False; overridden by silent).
+
+ """
+
+ # This 'abstract' class has no table defined.
+ self._dbName = None
+ self._table = None
+ self._silent = silent
+ self._verbose = verbose
+ self._metadata = None
+ self._colsToGet = None
+ self._defaultCols = None
+ self._defaultColSets = None
+ self._tables = []
+ self._coneRA = None
+ self._coneDec = None
+ self._coneName = None
+ self._coneRadius = None
+ self._coneUnits = "arcsec"
+ self._doConeSearch = False
+ self._filters = []
+ self._sortCol = None
+ self._sortDir = "ASC"
+ self._results = None
+ # numRows and firstRows, if None, means no limits
+ self._maxRows = 5000
+ self._firstRow = None
+ self._numRows = None
+ self._resolverDetails = None
+ self._resolvedRA = None
+ self._resolvedDec = None
+ self._locked = False
+ self._raw = None # DEBUG PROPERTY, DELETE LATER
+ self._obsCol = None
+ self._targetCol = None
+
+ if self._verbose:
+ self._silent = False
+ print("Disabling silent mode, verbose mode was requested.")
+
+ # End of __init__
+
+ # -----------------------------------------------------------------
+ # Now set up variable access, via properties to control read/write
+ # etc.
+
+ # Silent
+ @property
+ def silent(self):
+ """Whether to suppress output."""
+ return self._silent
+
+ @silent.setter
+ def silent(self, silent):
+ if not isinstance(silent, bool):
+ raise ValueError("Silent must be a bool")
+ self._silent = silent
+
+ # Verbose
+ @property
+ def verbose(self):
+ """Whether to write extra output."""
+ return self._verbose
+
+ @verbose.setter
+ def verbose(self, verbose):
+ if not isinstance(verbose, bool):
+ raise ValueError("Verbose must be a bool")
+ self._verbose = verbose
+
+ # make setting table unimplemented here, allowing the subclasses to
+ # decide whether you can change table mid query; also some classes
+ # may only support one table so they would want these functions not
+ # to work (i.e. set self._table in constructor.
+ # table
+ @property
+ def table(self):
+ return self._table
+
+ @table.setter
+ def table(self, table):
+ self.checkLock()
+ if table not in self._tables:
+ raise ValueError(f"{table} is not a valid table. The list `tables` shows valid values.")
+ self._table = table
+ self._metadata = None
+ # if (self._defaultColSets is not None) and table in (self._defaultColSets):
+ # self._defaultCols = self._defaultColSets[table]
+ # If unlocking the table, we have to forget results as various results handling functions are tied to the table
+ # that was used.
+ self.reset()
+ if self.verbose:
+ print(f"Selecting table `{table}`")
+
+ # dbName is also unset here, may not be changeable depending on sub-class.
+ # dbName
+ @property
+ def dbName(self):
+ """Which database is to be queried."""
+ if self._dbName is None:
+ raise NotImplementedError
+ return self._dbName
+
+ @dbName.setter
+ def dbName(self, dbName):
+ raise NotImplementedError
+
+ # coneRA
+ @property
+ def coneRA(self):
+ """The RA on which to centre a cone search (J2000)."""
+ return self._coneRA
+
+ @coneRA.setter
+ def coneRA(self, RA):
+ self.checkLock()
+ gotRA = False
+ if base.HAS_ASTROPY:
+ if isinstance(RA, astropy.coordinates.Angle):
+ RA = float(RA.deg)
+ gotRA = True
+ elif isinstance(RA, str):
+ tmp = astropy.coordinates.Angle(RA)
+ RA = float(tmp.deg)
+ gotRA = True
+ # If we don't have astropy, or it wasn't something astropy has
+ # parsed, then it must be int or float and we will parse it
+ if not gotRA:
+ if not isinstance(RA, (int, float)):
+ raise ValueError("RA must be int or float")
+ self._coneRA = float(RA)
+
+ # coneDEC
+ @property
+ def coneDec(self):
+ """The Dec on which to centre a cone search (J2000)."""
+ return self._coneDec
+
+ @coneDec.setter
+ def coneDec(self, Dec):
+ self.checkLock()
+ gotDec = False
+ if base.HAS_ASTROPY:
+ if isinstance(Dec, astropy.coordinates.Angle):
+ Dec = float(Dec.deg)
+ gotDec = True
+ elif isinstance(Dec, str):
+ tmp = astropy.coordinates.Angle(Dec)
+ Dec = float(tmp.deg)
+ gotDec = True
+ # If we don't have astropy, or it wasn't something astropy has
+ # parsed, then it must be int or float and we will parse it
+ if not gotDec:
+ if not isinstance(Dec, (int, float)):
+ raise ValueError("Dec must be int or float")
+ self._coneDec = float(Dec)
+
+ # coneName
+ @property
+ def coneName(self):
+ """The name of the object on which to centre a cone search."""
+ return self._coneName
+
+ @coneName.setter
+ def coneName(self, name):
+ self.checkLock()
+ if not isinstance(name, str):
+ raise ValueError("Name must be a string")
+ self._coneName = name
+
+ # coneRadius
+ @property
+ def coneRadius(self):
+ """The Radius on which to centre a cone search."""
+ return self._coneRadius
+
+ @coneRadius.setter
+ def coneRadius(self, Radius, Units=None):
+ self.checkLock()
+ if not isinstance(Radius, (int, float)):
+ raise ValueError("Radius must be int or float")
+ self._coneRadius = float(Radius)
+ if Units is not None:
+ self.coneUnits = Units
+
+ # coneUnits
+ @property
+ def coneUnits(self):
+ """The units of the cone-search radius."""
+ return self._coneUnits
+
+ @coneUnits.setter
+ def coneUnits(self, Units):
+ self.checkLock()
+ if not isinstance(Units, str):
+ raise ValueError(f"Units must be a string, one of: {', '.join(allowedConeRadiusUnits)}")
+ if Units in allowedConeRadiusUnits:
+ self._coneUnits = Units
+ else:
+ raise ValueError(f"Units must be one of: {', '.join(allowedConeRadiusUnits)}")
+
+ # maxRows
+ @property
+ def maxRows(self):
+ """How many rows to retrieve. None=all."""
+ self.checkLock()
+ return self._maxRows
+
+ @maxRows.setter
+ def maxRows(self, num):
+ if not isinstance(num, (int, float)) and (num is not None):
+ raise ValueError("num must be a number or None")
+ self._maxRows = num
+
+ # firstRow
+ @property
+ def firstRow(self):
+ """The first row to retrieve. None=auto."""
+ return self._firstRow
+
+ @firstRow.setter
+ def firstRow(self, num):
+ self.checkLock()
+ if (not isinstance(num, (int, float))) and (num is not None):
+ raise ValueError("num must be a number or None")
+ self._firstRow = num
+
+ # ---------------------------
+ # And some which we want to be read-only when accessed as a variable.
+
+ # doConeSearch
+ @property
+ def doConeSearch(self):
+ """Whether a cone search will be done."""
+ return self._doConeSearch
+
+ # allowedConeRadiusUnits
+ @property
+ def allowedConeRadiusUnits(self):
+ """The allowedConeRadiusUnits for with the selected table. Read-only."""
+ return allowedConeRadiusUnits
+
+ # metadata
+ @property
+ def metadata(self):
+ """The metadata for with the selected table. Read-only."""
+ # Need to get metadata if we don't have it:
+ if self._metadata is None:
+ if not self.silent:
+ print("Need to get the metadata.")
+ self.getMetadata()
+
+ return self._metadata
+
+ # colsToGet
+ @property
+ def colsToGet(self):
+ """The columns selected for retrieval."""
+ return self._colsToGet
+
+ # filters
+ @property
+ def filters(self):
+ """The filters that will be applied."""
+ return self._filters
+
+ # Results
+ @property
+ def results(self):
+ """The results of the query."""
+ return self._results
+
+ # haveResults
+ @property
+ def haveResults(self):
+ """Whether we have results from this query."""
+ return self._results is not None
+
+ @property
+ def locked(self):
+ """Is this object locked?"""
+ return self._locked
+
+ @property
+ def numRows(self):
+ """The number of rows returned by the query."""
+ return self._numRows
+
+ @property
+ def resolverDetails(self):
+ """The ouput of the name resolver."""
+ return self._resolverDetails
+
+ @property
+ def resolvedRA(self):
+ """The RA returned by the name resolver."""
+ return self._resolvedRA
+
+ @property
+ def resolvedDec(self):
+ """The dec returned by the name resolver."""
+ return self._resolvedDec
+
+ @property
+ def tables(self):
+ """The tables that can be selected."""
+ return self._tables
+
+ @property
+ def defaultCols(self):
+ """The columns that are retrieved if no selection is created."""
+
+ if self._defaultCols is None:
+ # self._checkMetaData()
+ if "Class" in self.metadata.columns:
+ # Can do this all in one line, but it's a bit hard to read, so lets be nice:
+ # First, filter the metadata on cases where Class has "BASIC" in it
+ tmp = self.metadata.loc[self.metadata["Class"].str.contains("BASIC")]
+ # Now get the columns in this:
+ self._defaultCols = tmp["ColName"].tolist()
+
+ return self._defaultCols
+
+ @property
+ def cols(self):
+ """Columns in this table."""
+ return self.metadata["ColName"].values
+
+ @property
+ def obsColumn(self):
+ """Which column contains the observation identifier."""
+ return self._obsCol
+
+ @property
+ def targetColumn(self):
+ """Which column contains the target identifier."""
+ return self._targetCol
+
+ # ---------------------------------------------------------------
+ # Functions. First some standard things:
+
+ def __str__(self):
+ str = f"PRINTING AN {type(self)} object."
+ return str
+
+ def __repr__(self):
+ str = f"I AM AN {type(self)} object."
+ return str
+
+ def checkLock(self):
+ """Check if this object is locked."""
+ if self._locked:
+ raise RuntimeError("Cannot edit this request as it is locked.")
+
+ def unlock(self):
+ """Unlock the object for editing."""
+ self._locked = False
+
+ # ---------------------------------------------------------------
+ # Metadata
+ def getMetadata(self):
+ """Retrieve the metadata for this catalogue from the server.
+
+ This queries the server for the metadata associated with the
+ database/table of the current object, and saves it into the
+ metadata variable as a pandas object? Or a dict? TBC
+
+ """
+ self.checkLock()
+ sendData = {"database": self.dbName, "table": self.table}
+ if self.verbose:
+ print(f"Getting metadata for {self.dbName}.{self.table}")
+
+ ret = base.submitAPICall("getMetadata", sendData, minKeys=["metadata"], verbose=self._verbose)
+
+ # metadata should have two entries: 'columns' and 'data'
+ self._metadata = pd.DataFrame(ret["metadata"]["metadata"], columns=ret["metadata"]["columns"])
+
+ self._obsCol = None
+ self._targetCol = None
+ if "IsObsCol" in self._metadata:
+ self._metadata["IsObsCol"] = pd.to_numeric(self._metadata["IsObsCol"])
+ tmp = self._metadata.loc[self._metadata["IsObsCol"] == 1]["ColName"]
+ if len(tmp) > 0:
+ self._obsCol = tmp.iloc[0]
+ if len(tmp) > 1 and not self.silent:
+ print(
+ "WARNING: Metadata contains TWO obs columns! This may be a bug; "
+ "please notify swift-help@leicester.ac.uk"
+ )
+
+ if "IsTargetCol" in self._metadata:
+ self._metadata["IsTargetCol"] = pd.to_numeric(self._metadata["IsTargetCol"])
+ tmp = self._metadata.loc[self._metadata["IsTargetCol"] == 1]["ColName"]
+ if len(tmp) > 0:
+ self._targetCol = tmp.iloc[0]
+ if len(tmp) > 1 and not self.silent:
+ print(
+ "WARNING: Metadata contains TWO target columns! This may be a bug; "
+ "please notify swift-help@leicester.ac.uk"
+ )
+
+ # --------------------------------------------------------------
+ # Misc
+ def reset(self):
+ """Remove all results - reset this object."""
+
+ if not self.silent:
+ print("Resetting query details")
+ self.unlock()
+ self.removeAllFilters()
+ self.removeConeSearch()
+ self._colsToGet = None
+ self._resolverDetails = None
+ self._resolvedRA = None
+ self._resolvedDec = None
+ self._results = None
+ self.sortCol = None
+ self._metadata = None
+ self._defaultCols = None
+
+ self._raw = None # TEMP LINE
+
+ # ---------------------------------------------------------------
+ # Cone search functions
+ # These allow a cone search to be build, or requested.
+ # Note that simply setting coneRA/Dec above on their own doesn't force
+ # a cone search to run unless doConeSearch is set. These methods are
+ # preferred ways of managing the cone search.
+
+ def addConeSearch(self, ra=None, dec=None, radius=None, name=None, coords=None, units="arcsec"):
+ """Include a cone search filter on the dataRequest.
+
+ This function adds a cone search to the set of filters to be
+ applied to your request. If a cone serach already existed, this
+ will overwrite it; you cannot (at present) have multiple cone
+ searches combined with an OR function; you will have to do
+ multiple cone searches instead.
+
+ Note: name, or ra & dec must be specified, but they cannot
+ both be specified. If you supply both, an error will be raised.
+
+ Parameters
+ ----------
+ ra : Union[float, astropy.coordiantes.Angle] Central RA
+ of the cone, in J2000.
+
+ dec : Union[float, astropy.coordiantes.Angle] Central Dec
+ of the cone, in J2000.
+
+ name: str The name of an object to centre on. Will be resolved.
+
+ coords : str Free-form coordinates to attempt to parse
+
+ radius : float Radius of the cone search.
+
+ units : str The units of coneRadius. Default 'arcsec', permitted
+ values are given in the allowedConeRadiusUnits variable.
+
+ """
+ self.checkLock()
+ # Set mainly via @property functions so that checks on values are done.
+ if name is not None:
+ if (ra is not None) or (dec is not None) or (coords is not None):
+ raise ValueError("You must supply name OR position, not both.")
+ self._coneRA = None
+ self._coneDec = None
+ self.coneName = name
+ elif coords is not None:
+ self.coneName = coords
+ elif (ra is None) or (dec is None):
+ raise ValueError("You must supply name or position.")
+ else:
+ self.coneRA = ra
+ self.coneDec = dec
+ self._coneName = None
+
+ if radius is None:
+ raise ValueError("radius must be supplied")
+
+ self.coneRadius = radius
+ self.coneUnits = units
+
+ self._doConeSearch = True
+
+ def editConeSearch(self, coneRA, coneDec, coneRadius, coneUnits="arcsec"):
+ """Changes the cone search. Just a wrapper to addConeSearch."""
+ self.addConeSearch(coneRA, coneDec, coneRadius, coneUnits)
+
+ def removeConeSearch(self):
+ """Remove the cone search from the filters to apply."""
+ self.checkLock()
+ self._coneRA = None
+ self._coneDec = None
+ self._coneRadius = None
+ self._coneUnits = "arcsec"
+ self._doConeSearch = False
+
+ def isValid(self):
+ """Whether the current request is valid and can be submitted."""
+
+ # Need to get metadata if we don't have it:
+ if self._metadata is None:
+ if not self.silent:
+ print("Need to get the metadata to check the query is valid.")
+ self.getMetadata()
+
+ # This needs to check:
+ # All columns are permissable
+ # If not set, check defaults as user could have been naughty.
+ if self.verbose:
+ print("Checking requested columns...")
+ tmp = self._colsToGet
+ if tmp is None:
+ tmp = self.defaultCols
+ if tmp is None:
+ print("No columns selected to retrieve!")
+ return False
+ if tmp != "*":
+ for c in tmp:
+ if c not in self.metadata["ColName"].values:
+ if not self.silent:
+ print(f"Requested column {c} does not exist.")
+ return False
+
+ # Now check filters
+ # Filters
+ if self.verbose:
+ print("Checking filters...")
+ for f in self._filters:
+ if not f.isValid(self.metadata):
+ return False
+
+ # And check the cone search
+ if self._doConeSearch:
+ if self.verbose:
+ print("Checking cone search parameters...")
+ # Need a radius
+ if self.coneRadius is None:
+ if not self.silent:
+ print("A cone search is selected, but radius is not set")
+ return False
+ # Need name or ra AND dec
+ if (self.coneName is None) and ((self.coneRA is None) or (self.coneDec is None)):
+ if not self.silent:
+ print("A cone search is selected, but neither name, nor RA & Dec are set.")
+ return False
+ if (self.coneName is not None) and ((self.coneRA is not None) or (self.coneDec is not None)):
+ if not self.silent:
+ print("You should supply name OR position for a cone search.")
+ return False
+
+ return True
+
+ # ---------------------------------------------------------------
+ # Column functions
+
+ def _addCol(self, colName):
+ """Internal function to add a column to the list.
+
+ This is called by addCol() (which can support strings or lists)
+ but must recieve a string. It adds the item to _colsToGet, after
+ verifying it.
+
+ The table metadata must be known, so it will retrieve it if it
+ doesn't exist.
+
+ Will raise ValueError if the supplied parameter is not a string,
+ or is not a valid column (or '*').
+
+ Parameters
+ ----------
+
+ colName : str The column to add.
+
+ """
+ self.checkLock()
+ # First, check whether the metadata is retrieved and up to date.
+ if not isinstance(colName, str):
+ raise ValueError("colName should be a string")
+
+ if colName == "*":
+ if self.verbose:
+ print("Setting to retrieve all columns.")
+ self._colsToGet = self.metadata["ColName"].values.tolist()
+ else:
+ # Is the column name valid?
+ if colName not in self.metadata["ColName"].values:
+ raise ValueError(f"`{colName}` is not a valid column name.")
+ # If previously we had selected all, then warn if not silent.
+ if self._colsToGet == "*":
+ if not self.silent:
+ print("WARNING: previously you were selecting all data; you are now requesting specific columns.")
+ self._colsToGet = [
+ colName,
+ ]
+
+ # If this is the first column, create the list
+ if self._colsToGet is None:
+ self._colsToGet = [
+ colName,
+ ]
+ if self.verbose:
+ print(f"Will retrieve column {colName}")
+ else:
+ # This 'else' assumes a list, i.e. colsToGet is either '*', None or a list.
+ # If it is not, then likely this will throw an error. That's OK, since if it is
+ # not one of these then a user has edited this "hidden" field directly, and they
+ # deserve what they get.
+ if colName in self._colsToGet:
+ if not self.silent:
+ print(f"Cannot add column {colName}; it is already selected.")
+ else:
+ self._colsToGet.append(colName)
+ if self.verbose:
+ print(f"Will retrieve column {colName}")
+
+ def addCol(self, colName):
+ """Add a column/columns to the list of those to retrieve.
+
+ This can receive either a string, which is a column name or '*',
+ or a list of column names to add to the list to retrieve.
+ Note: '*' cannot appear in a list.
+
+ The names are checked against valid column names, so if it has
+ not already been called, getMetadata() will run first.
+
+ Parameters
+ ----------
+
+ colName : Union[str,list] The column(s) to add.
+
+ """
+ self.checkLock()
+ if isinstance(colName, str):
+ self._addCol(colName)
+ elif isinstance(colName, (list, tuple)):
+ if "*" in colName:
+ raise ValueError("You cannot include '*' in a list of columns.")
+ for c in colName:
+ self._addCol(c)
+ else:
+ raise ValueError("colName must be a string or list")
+
+ if self.verbose:
+ print(f"Set to retrieve columns: {self._colsToGet}")
+
+ def removeAllCols(self):
+ """Empty the list of defined columns to retrieve."""
+ self.checkLock()
+ self._colsToGet = None
+
+ def _removeCol(self, colName):
+ """Internal function to remove a column from the list.
+
+ This is called by removeCol() (which supports strings or lists)
+ but must recieve a string. It removes the item to _colsToGet.
+
+ Parameters
+ ----------
+
+ colName : str The column to remove.
+
+ """
+ self.checkLock()
+ if not isinstance(colName, str):
+ raise ValueError("colName should be a string")
+
+ self.colsToGet.remove(colName)
+
+ def removeCol(self, colName):
+ """Remove a column/columns to the list of those to retrieve.
+
+ This can receive either a string, which is a column name or a
+ list of column names to add to the list to retrieve.
+
+ Parameters
+ ----------
+
+ colName : Union[str,list] The column(s) to add.
+
+ """
+ self.checkLock()
+ if isinstance(colName, str):
+ self._removeCol(colName)
+ elif isinstance(colName, (list, tuple)):
+ for c in colName:
+ self._removeCol(c)
+ else:
+ raise ValueError("colName must be a string or list")
+
+ if len(self._colsToGet) == 0:
+ self._colsToGet = None
+
+ if self.verbose:
+ print(f"Will retrieve columns: {self._colsToGet}")
+
+ # ----------------------
+ # Filter functions
+
+ # addFilter
+ # editFilter
+ # removeFilter
+ # showFilters (maybe with option for 'as SQL' vs as Dict)
+
+ def removeAllFilters(self):
+ """Remove all defined search filters."""
+ self.checkLock()
+ self._filters = []
+
+ def addFilter(self, filterDef):
+ """Add a filter to the query.
+
+ This can be done two ways:
+
+ 1) By passing a dict with the following keys --
+ some are optional:
+
+ colName: The name of the column to filter on
+
+ filter: The filter to apply ('<', '>', 'IS NULL' etc)
+
+ val: The value to apply to the filter, if appropriate. e.g.
+ if the filter is '<', val may be 3.1234. If the filter
+ takes no arguments (IS [NOT] NULL) this is ignored.
+ If the filter takes two arguments (BETWEEN) this should
+ be a list.
+
+ combiner: OPTIONAL: This can be AND or OR, if this filter
+ has to components.
+
+ filter2: As filter, for the second clause.
+
+ val2: As val, for the second clause.
+
+ 2) By passing a list, whose values are the above keys, in order,
+ e.g. [
+ 'foo',
+ '<',
+ 3,
+ 'OR',
+ 'foo',
+ 'BETWEEN',
+ [7,10]
+ ]
+
+ Parameters
+ ----------
+
+ filterDef : Union[string,list,tuple] The filter definition
+
+ """
+ self.checkLock()
+ # colname is
+ self._filters.append(filter(filterDef, self.metadata))
+
+ def showFilters(self):
+ """List all filters currently applied"""
+ i = 0
+ for f in self._filters:
+ print(f"{i}:\t{f}")
+ i = i + 1
+
+ def removeFilter(self, ix):
+ """Remove a filter, by index.
+
+ To see filters and their indices, call showFilters
+
+ Parameters
+ ----------
+
+ ix : int Index of filter to remove
+
+ """
+ self.checkLock()
+ if not isinstance(ix, int):
+ raise ValueError("ix must be an int")
+ if (ix < 0) or (ix >= len(self._filters)):
+ raise ValueError(f"ix must be between 0 and {len(self._filters)-1}")
+ del self._filters[ix]
+ if not self._silent:
+ self.showFilters()
+
+ # ---------------------------------------------------------------
+ # Actual search functions
+
+ def submit(self):
+ """Submit the query."""
+ self.checkLock()
+
+ # First, check validity. Do this by function call
+ if not self.isValid():
+ if not self.silent:
+ print("Cannot submit query - it is not valid.")
+ return False
+
+ # Build the API request dict:
+ sendData = {
+ "database": self.dbName,
+ "table": self.table,
+ "adUnits": self._coneUnits,
+ "sortDir": self._sortDir,
+ "numRows": base.MAXROWS,
+ }
+
+ # Specify columns, if we can
+ if self._colsToGet is not None:
+ sendData["cols"] = self._colsToGet
+ elif self.defaultCols is not None:
+ sendData["cols"] = self.defaultCols
+
+ # And the sort Col:
+ if self._sortCol is not None:
+ sendData["sortBy"] = self._sortCol
+
+ # Add filters
+ if len(self._filters) > 0:
+ sendData["constraints"] = []
+ for f in self._filters:
+ sendData["constraints"].append(f.data)
+
+ # Add cone information
+ if self._doConeSearch:
+ if self._coneName is None:
+ if (self._coneRA is None) or (self._coneDec is None):
+ raise RuntimeError("You have requested a cone search but without specifying name/position")
+ sendData["searchRA"] = self._coneRA
+ sendData["searchDec"] = self._coneDec
+ # print("SETTING CONE BY POSITION")
+ else:
+ sendData["searchName"] = self._coneName
+ # print("SETTING CONE BY NAME")
+
+ sendData["searchRad"] = self._coneRadius
+
+ # Now do the actual work. Note - the server can only return so
+ # many rows at once because of memory constraints. I will limit
+ # the max in one go to base.MAXROWS. NB, if you try to return
+ # more than this and the allocated memory is overrun you just
+ # get a 500 error.
+
+ fR = 0 # First row from this query
+ if self._firstRow is not None:
+ fR = int(self._firstRow)
+
+ maxRows = 1e80 # i.e. BIG
+ if self._maxRows is not None:
+ maxRows = int(self._maxRows)
+ # If we want < the maximum in one go, need to ammend numRows
+ if maxRows < base.MAXROWS:
+ sendData["numRows"] = maxRows
+
+ # Create a local variable for the result for now, I don't want
+ # to update self._results until the query has definitely succeeded.
+ result = None
+
+ done = False
+ while not done:
+ # Update the first row to get
+ sendData["firstRow"] = fR
+
+ if not self._silent:
+ print(f"Calling DB look-up for rows {fR} -- {sendData['numRows']+fR}")
+
+ ret = base.submitAPICall(
+ "queryDB",
+ sendData,
+ minKeys=["Results", "NumRows"],
+ verbose=self._verbose,
+ )
+ if result is None:
+ result = ret
+ else:
+ result["NumRows"] = result["NumRows"] + ret["NumRows"]
+ result["Results"].extend(ret["Results"])
+
+ # Are we done? If we did not get as many rows as was requested then we are
+ if ret["NumRows"] < sendData["numRows"]:
+ done = True
+ if self._verbose:
+ print(f"Received {ret['NumRows']} rows / {sendData['numRows']} requested. Query complete.")
+ # If we have now hit the user-set limit, then we are done:
+ elif (self._maxRows is not None) and (result["NumRows"] >= self._maxRows):
+ done = True
+ if self._verbose:
+ print(f"{result['NumRows']} rows retrieved in total. Query complete.")
+ else:
+ # Increase the start row for the next call
+ fR = fR + sendData["numRows"]
+ # We may need to decrease the number of rows
+ if (self._maxRows is not None) and (self._maxRows < result["NumRows"] + sendData["numRows"]):
+ sendData["numRows"] = self._maxRows - result["NumRows"]
+ if self._verbose:
+ print(f"Reducing the number of rows requested to {sendData['numRows']}.")
+ # End of while not Done
+ # We now should have our results. Maybe do one sanity check:
+ if result["NumRows"] != len(result["Results"]):
+ raise RuntimeError(f"Should have {result['NumRows']} rows, but have {len(result['Results'])}!")
+
+ if (self._doConeSearch) and (not self.silent) and ("ResolvedInfo" in result):
+ print(result["ResolvedInfo"])
+
+ self._numRows = result["NumRows"]
+ if (self._doConeSearch) and ("ResolvedInfo" in result):
+ self._resolverDetails = result["ResolvedInfo"]
+ self._resolvedRA = result["ResolvedRA"]
+ self._resolvedDec = result["ResolvedDec"]
+ self._results = pd.DataFrame(result["Results"])
+
+ useAst = None
+ if base.HAS_ASTROPY:
+ useAst = "_apy"
+ if not self.silent:
+ print(f"Received {self.numRows} rows.")
+ base.manageResults(self._results, self._metadata, "_s", useAst, self.silent, self.verbose)
+ self._locked = True
+
+ self._raw = result # TEMPORARY LINE
+
+ # ---------------------------------------------------------------
+ # Data retrieval
+
+ def downloadObsData(self, subset=None, **kwargs):
+ """Download data for the observations returned by the query.
+
+ If the excuted query returned a column which includes
+ observation identifiers, then this function will download the
+ data for those columns. This function essentially wraps
+ ukssdc.access.downloadObsData(), so for valid values of the
+ **kwargs, see the documentation for that function.
+
+ The subset parameter is optional, and can be used to apply
+ a filter to the results this query has obtained. The easiest way
+ to generate this is using the pandas syntax for filtering on
+ value.
+
+ e.g. if your datarequest object is called 'req' then:
+
+ => req.downloadObsData(subset=req.results['xrt_expo_pc']<1000)
+
+ would request a download of all observations that the request
+ found, which had a value of less than 1000 in the 'xrt_expo_pc'
+ column.
+
+ Obviously, this function cannot be called before this request
+ has been submitted and has completed; if you try, your computer
+ will explode and your eyeballs will be eaten by ants (just
+ kidding; you'll get a RuntimeError).
+
+ Parameters
+ ----------
+
+ subset : pandas.Series OPTIONAL: A pandas series defining a
+ subset of rows to download.
+
+ """
+
+ if not self.haveResults:
+ raise RuntimeError("This query has not been executed, cannot download!")
+
+ if self._obsCol is None:
+ raise RuntimeError("These is no column containing observation ID, cannot download.")
+
+ if self._obsCol not in self.results.columns:
+ raise RuntimeError(
+ f"The column {self._obsCol} was not retrieved as part of your query. Cannot download. "
+ "You may need to unlock this object, add {self._obsCol} to those to retrieve, and repeat the query."
+ )
+
+ obslist = []
+ if subset is not None:
+ if not isinstance(subset, pd.core.series.Series):
+ raise ValueError("Subset parameter must be a pandas series")
+ obslist = self._results.loc[subset][self._obsCol].tolist()
+ else:
+ obslist = self.results[self._obsCol].tolist()
+
+ # # DEBUG LINE:
+ # print(f"Downloading obs: {obslist}")
+ # return
+
+ downloadObsData(obslist, silent=self.silent, verbose=self.verbose, **kwargs)
+
+
\ No newline at end of file
diff --git a/swifttools/ukssdc/data/download.py b/swifttools/ukssdc/data/download.py
index d5863ea1..62b38b3e 100644
--- a/swifttools/ukssdc/data/download.py
+++ b/swifttools/ukssdc/data/download.py
@@ -882,15 +882,15 @@ def _getLightCurve(
if isinstance(incbad, bool):
if incbad:
- incbad="yes"
+ incbad = "yes"
else:
- incbad="no"
+ incbad = "no"
if isinstance(nosys, bool):
if nosys:
- nosys="yes"
+ nosys = "yes"
else:
- nosys="no"
+ nosys = "no"
sendData = {"type": type, "objectID": objectID, "incbad": incbad, "nosys": nosys}
@@ -1041,9 +1041,9 @@ def _saveLightCurveFromDict(
if suff is None:
if asQDP:
- suff='qdp'
+ suff = "qdp"
else:
- suff='dat'
+ suff = "dat"
for c in theseCurves:
fname = f"{destDir}/{prefix}{c}"
diff --git a/swifttools/ukssdc/query/GRB.py b/swifttools/ukssdc/query/GRB.py
index 73696ed0..c9f89d07 100644
--- a/swifttools/ukssdc/query/GRB.py
+++ b/swifttools/ukssdc/query/GRB.py
@@ -679,10 +679,10 @@ def getBurstAnalyser(self, subset=None, byName=False, byID=False, returnData=Fal
found when this query was executed. Of course, this means you
have to have executed the query first!
- The light curve data will be stored in the ``lightCurves``
+ The burst analyser data will be stored in the ``burstAnalyser``
variable of this object, and optionally returned as well. The
- light curves files can also be downloaded directly from the
- website to disk.
+ data files can also be downloaded directly from the website to
+ disk.
Parameters
----------
@@ -699,12 +699,12 @@ def getBurstAnalyser(self, subset=None, byName=False, byID=False, returnData=Fal
Requires that column to have been retrieved by your query.
returnData : bool, optional
- Whether the light curve data should be returned by this
- function, as well as saved in the "lightCurves"
+ Whether the burst analyser data should be returned by this
+ function, as well as saved in the "burstAnalyser"
variable.
**kwargs : dict, optional
- Parameters to pass to ukssdc.data.GRB.getSpectra()
+ Parameters to pass to ukssdc.data.GRB.getBurstAnalyser()
"""
if not self.haveResults:
From 5536c235c90b4f97d36dffe0cf4a280c12495e3d Mon Sep 17 00:00:00 2001
From: Phil Evans
Date: Tue, 7 May 2024 16:32:09 +0100
Subject: [PATCH 03/22] Pulled in some small documentation fixes
---
swifttools/ukssdc/APIDocs/index.php | 2 +-
swifttools/ukssdc/APIDocs/ukssdc/query.md | 7 -------
.../APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md | 2 +-
.../ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md | 10 +++-------
4 files changed, 5 insertions(+), 16 deletions(-)
diff --git a/swifttools/ukssdc/APIDocs/index.php b/swifttools/ukssdc/APIDocs/index.php
index 1d646005..88919280 100644
--- a/swifttools/ukssdc/APIDocs/index.php
+++ b/swifttools/ukssdc/APIDocs/index.php
@@ -24,7 +24,7 @@
The swifttools API
Version 3.0 of swifttools
- was released on XXXX. This major release introduces the swifttools.ukssdc module.
+ was released on 2022 August 31. This major release introduces the swifttools.ukssdc module.
Various aspects of working with Swift can now be done via the swifttools Python module. This is available through pip:
diff --git a/swifttools/ukssdc/APIDocs/ukssdc/query.md b/swifttools/ukssdc/APIDocs/ukssdc/query.md
index 2986876a..dc8c81fa 100644
--- a/swifttools/ukssdc/APIDocs/ukssdc/query.md
+++ b/swifttools/ukssdc/APIDocs/ukssdc/query.md
@@ -934,17 +934,10 @@ myTargs = (81445, 45767, 81637)
subset=q.results['target_id'].isin(myTargs)
q.results.loc[q.results['target_id'].isin(myTargs)]
# q.downloadObsData(destDir='/tmp/APIDemo_download4',
-
-[Jupyter notebook version of this page](query.ipynb)
# subset=q.results['target_id'].isin(myTargs),
-
-[Jupyter notebook version of this page](query.ipynb)
# instruments=('XRT',),
-
-[Jupyter notebook version of this page](query.ipynb)
# getAuxil=False)
-[Jupyter notebook version of this page](query.ipynb)
```
diff --git a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md
index 656da86a..255c43bc 100644
--- a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md
+++ b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/ReleaseNotes_v110.md
@@ -1,6 +1,6 @@
# swifttools.ukssdc.xrt_prods v1.10 Release Notes
-Version 1.10 of the `xrt_prods` module was released as part of `swifttools v3.0` on XXXXXX
+Version 1.10 of the `xrt_prods` module was released as part of `swifttools v3.0` on 2022 August 31.
This release features a number of important changes mainly relating to the way in which data products
are returned and formatted. We have made significant efforts to support backwards compatibility, so **all of your
diff --git a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md
index fe4cdb91..295769a6 100644
--- a/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md
+++ b/swifttools/ukssdc/APIDocs/ukssdc/xrt_prods/RequestJob.md
@@ -332,12 +332,8 @@ Global parameters are set/retrieved with the `setGlobalPars()` and `getGlobalPar
| getT0 | No** | bool | Whether to ask the server to complete the `T0` field automatically | False |
| getCoords | No*** | bool | Whether to ask the server to complete the `RA` and `Dec` fields automatically | False |
-*: The targetID(s) must be supplied; these tell the server which sets of data to include in your products, and it can be
-a comma-separated list if more than one targetID corresponds to your object. You can either supply the targetIDs in the
-`targ` field, or set `getTargs=True`. In the latter case, the server will do a cone search around the position
-you supplied in the `RA` and `Dec` fields and select all targets within 12′ of the position. If you did
-not supply a position it will attempt to determine it by resolving the supplied name. If the name cannot be resolved,
-than instead all targetIDs in the database where the object name matches that in the `name` field, will be selected.
+*: The targetID(s) must be supplied; these tell the server which sets of data to include in your products, and it can be a comma-separated list if more than one targetID corresponds to your object. You can either supply the targetIDs in the `targ` field, or set `getTargs=True`. In the latter case, the server will select all targetIDs in the database where the object name matches that in the `name` field, **and** targets in the database where the XRT field of view will overlap the position in the (`RA`, `Dec`) fields. If `getCoords=True` then the targetID determination is carried out **after** the name has been resolved
+into a position.
**: A start time is not mandatory, but is helpful, particularly to zero the time axis on light curve, but it can also be used as a reference point for all other input times. You can either supply it in the `T0` field, or set `getT0=True`. In the latter case the server will try to work it out, either as the trigger time (if the object is a GRB), or as the start time of the first observation of the object. In this case `T0` will be set in the [data returned by the server](ReturnData.md)
@@ -447,7 +443,7 @@ an absorbed "APEC+blackbody".
#### If defining specific sub-spectra
-If timeslice=="user" then you must define the spectra you wish to create, giving each one a label (alphanumeric characters only) and a GTI interval, as defined in [the product generator documentation](https://www.swift.ac.uk/user_objects/docs.php). You can specify between 1 and 4 spectra, using these parameters:
+If timeslice=="user" then you must define the spectra you wish to create, giving each one a label (alphanumeric characters only) and a GTI interval, as defined in [the product generator documentation](https://www.swift.ac.uk/user_objects/docs.php#timeformat). You can specify between 1 and 4 spectra, using these parameters:
| Parameter | Mandatory? | Type | Description | Default |
| :---- | :----: | :---: | :----- | :----: |
From dc348b0e7f70b841340ae42eb07b75518e8c4d54 Mon Sep 17 00:00:00 2001
From: Jamie Kennea
Date: Tue, 7 May 2024 15:59:38 -0400
Subject: [PATCH 04/22] Add deploy GitHub workflow
---
.github/workflows/deploy.yml | 61 ++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
create mode 100644 .github/workflows/deploy.yml
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 00000000..b99b8357
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,61 @@
+name: Deploy to PyPI
+on:
+ push:
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: Build distribution 📦
+ if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout the repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.11"
+
+ - name: Git describe
+ id: ghd
+ uses: proudust/gh-describe@v2
+
+ - name: Install pypa/build
+ run: >-
+ python3 -m
+ pip install
+ build
+ --user
+
+ - name: Build a binary wheel and a source tarball
+ run: python3 -m build
+
+ - name: Store the distribution packages
+ uses: actions/upload-artifact@v4
+ with:
+ name: python-package-distributions
+ path: dist/
+
+ publish-to-pypi:
+ name: >-
+ Publish Python 🐍 distribution 📦 to PyPI
+ if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
+ needs:
+ - build
+ runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ url: https://pypi.org/p/swifttools
+ permissions:
+ id-token: write # IMPORTANT: mandatory for trusted publishing
+ steps:
+ - name: Download all the dists
+ uses: actions/download-artifact@v4
+ with:
+ name: python-package-distributions
+ path: dist/
+ - name: Publish distribution 📦 to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
From 6c1ce25b066722a6e5d7ac4f8d0e9ea232852db7 Mon Sep 17 00:00:00 2001
From: Phil Evans
Date: Tue, 7 May 2024 16:10:40 +0100
Subject: [PATCH 05/22] Modifying this to be the latest version of docs
---
swifttools/ukssdc/APIDocs/index.php | 65 ------------------
swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md | 12 ++++
swifttools/ukssdc/APIDocs/ukssdc/README.md | 29 +++++---
swifttools/ukssdc/APIDocs/ukssdc/ukssdc.zip | Bin 95423 -> 0 bytes
.../APIDocs/ukssdc/xrt_prods/ChangeLog.md | 5 +-
.../ukssdc/APIDocs/ukssdc/xrt_prods/README.md | 3 +-
6 files changed, 33 insertions(+), 81 deletions(-)
delete mode 100644 swifttools/ukssdc/APIDocs/index.php
create mode 100644 swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md
delete mode 100644 swifttools/ukssdc/APIDocs/ukssdc/ukssdc.zip
diff --git a/swifttools/ukssdc/APIDocs/index.php b/swifttools/ukssdc/APIDocs/index.php
deleted file mode 100644
index 88919280..00000000
--- a/swifttools/ukssdc/APIDocs/index.php
+++ /dev/null
@@ -1,65 +0,0 @@
-setStyle("/style/LSXPS_docs.css");
- $head=$page->Head();
- $head->Title("UKSSDC | Swifttools API");
- $head->Description("Documentation supporting the swifttools API");
- $head->Keywords("Swift, GRB, XRT, light curve, documentation, API, swifttools");
- $head->AddScriptRef("/scripts/centre.js");
- $head->AddScriptRef("/scripts/jquery.min.js");
- $head->AddScriptRef("/scripts/jquery_scrollto.js");
- $head->AddScriptRef("/scripts/autoproc_base.js");
- $head->AddScriptRef("/scripts/jquery-ui.js");
-
- $page->PageFoot()->W3C( True );
- $page->PageFoot()->WAI( 2 );
- $page->Begin();
- print "Home > > API Documentation
\n";
-// print "Help.
\n";
-?>
- The swifttools API
-
- Version 3.0 of swifttools
- was released on 2022 August 31. This major release introduces the swifttools.ukssdc module.
-
- Various aspects of working with Swift can now be done via the swifttools Python module. This is available through pip:
-
-
- pip install [--upgrade] swifttools
-
-
- (The --upgrade is only needed if you have an older version of this module installed).
-
- This module has 2 sub-modules:
-
-
-
- swifttools.swift_too [Docs]
- - This module includes access to Swift data, the ability to query observability of a source,
- get an object's observing history, and submit ToO requests. It is maintained by Jamie Kennea, and is fully
- documented on the PSU web site.
-
- swifttools.ukssdc [Docs]
- - This module provides access to Swift data, the XRT GRB products, the
- LSXPS catalogue, the on-demand data-analysis tools and more. It is
- maintained by Phil Evans and is fully documented here.
-
-
-
-
-
-
- Although we recommend installing via pip the source code is also available via
- a public GitLab repository, which is also where you
- can report issues / bugs.
-
-
-
-End();
-
-
diff --git a/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md b/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md
new file mode 100644
index 00000000..d0bbbdf8
--- /dev/null
+++ b/swifttools/ukssdc/APIDocs/ukssdc/ChangeLog.md
@@ -0,0 +1,12 @@
+# Change history for the `swifttools.ukssdc` module
+
+Changes made to this module after its original release will be documented here.
+
+* 2023 May 24: Modification to the back end, light curve `dict` now includes an "Exposure" column in the hard/soft/ratio datasets.
+* 2022 September 06. v1.0.3 relased as part of `swifttools` v3.0.5
+ * Further minor bugfixes in `mergeUpperLimits()`.
+ * Fixed an issue only affecting Python >=3.9: the `math.factorial()` function now requires the argument
+ to be an integer, it does not accept floats such as "3.0". This caused en error in `bayesRate()` as called
+ by `mergeUpperLimits()` and `mergeLightCurveBins()`.
+* 2022 September 01. v1.0.2 relased as part of `swifttools` v3.0.2
+ * Minor bugfixes in `mergeUpperLimits()`
\ No newline at end of file
diff --git a/swifttools/ukssdc/APIDocs/ukssdc/README.md b/swifttools/ukssdc/APIDocs/ukssdc/README.md
index aa237c4c..f7013238 100644
--- a/swifttools/ukssdc/APIDocs/ukssdc/README.md
+++ b/swifttools/ukssdc/APIDocs/ukssdc/README.md
@@ -1,6 +1,6 @@
# The `swifttools.ukssdc` module.
-Quick links: [`xrt_prods`](xrt_prods.md) | [`data`](data.md) | [`query`](query.md) | [Set of Jupyter notebooks](ukssdc.zip)
+Quick links: [Usage policy](#usage) | [`xrt_prods`](xrt_prods.md) | [`data`](data.md) | [`query`](query.md) | [ChangeLog](ChangeLog.md) | [Set of Jupyter notebooks](ukssdc.zip)
## Quick start
@@ -55,20 +55,29 @@ lots of output. The demarcation between non-silent, and verbose output is somewh
## Usage
-This Python module is provided free to use, but please do remember that in any publication you should cite the source of any data obtained.
-References are given in the various product documentation pages, and please always include, in the acknowledgements:
+This Python module is provided free to use, but if you use it for work leading to a publication,
+please do acknowledge this with a footnote pointing to (https://www.swift.ac.uk/API). The standard usage
+policy for any of the tools accessed via the API remains in force as well. That is, please incude this
+in your acknowledgements:
+
+
This work made use of data supplied by the UK Swift Science Data Centre at the University of Leicester.
+
+
+and cite the appropriate paper(s) relating to the data source or algorithms you accessed through the API.
+Details for each of these can be found on their relevant pages; below are links to those pages and
+the citations requested.
-The most common references to use are
-* GRB light curves: [Evans et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...469..379E/abstract),
+* [GRB light curves](/xrt_curves/docs.php#usage): [Evans et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...469..379E/abstract),
[Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract)
-* GRB enhanced positions: [Goad et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...476.1401G/abstract), [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract)
-* GRB spectra: [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract)
-* The burst analyser: [Evans et al., (2010)](https://ui.adsabs.harvard.edu/abs/2010A%26A...519A.102E/abstract)
-* On-demand products: [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract)
-* LSXPS: Evans et al., (2022)
+* [GRB enhanced positions](/xrt_positions/docs.php#usage): [Goad et al., (2007)](https://ui.adsabs.harvard.edu/abs/2007A%26A...476.1401G/abstract), [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract)
+* [GRB spectra](/xrt_spectra/docs.php#usage): [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract)
+* [The burst analyser](/burst_analyser/docs.php#usage): [Evans et al., (2010)](https://ui.adsabs.harvard.edu/abs/2010A%26A...519A.102E/abstract)
+* [On-demand products](/user_objects/docs.php#usage): [Evans et al., (2009)](https://ui.adsabs.harvard.edu/abs/2009MNRAS.397.1177E/abstract): **please see the [documentation](/user_objects/docs.php#usage) for citations for the different products**.
+* [2SXPS](/2SXPS/docs.php#access): [Evans et al., (2020)](https://ui.adsabs.harvard.edu/abs/2020ApJS..247...54E/abstract)
+* [LSXPS](/LSXPS/docs.php#access): [Evans et al., (2023)](https://ui.adsabs.harvard.edu/abs/2023MNRAS.518..174E/abstract)
## About this documentation
diff --git a/swifttools/ukssdc/APIDocs/ukssdc/ukssdc.zip b/swifttools/ukssdc/APIDocs/ukssdc/ukssdc.zip
deleted file mode 100644
index f8fb79dc01d4f6e0f692a558d0ab46da22b0acf0..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 95423
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