files: # Licensed under a 3-clause BSD style license - see LICENSE.rst
  
  # 1. standard library imports
  from io import BytesIO
  import os
  from urllib.parse import unquote, urlparse
  import time
  from xml.etree import ElementTree
  from datetime import datetime, timezone
  
  # 2. third party imports
  import astropy.units as u
  import astropy.coordinates as coord
  from astropy.table import Table
  from astropy.io.votable import parse
  from astroquery import log
  import numpy as np
  
  # 3. local imports - use relative imports
  # commonly required local imports shown below as example
  # all Query classes should inherit from BaseQuery.
  from ..query import BaseQuery
  # has common functions required by most modules
  from ..utils import commons
  # prepend_docstr is a way to copy docstrings between methods
  from ..utils import prepend_docstr_nosections
  # async_to_sync generates the relevant query tools from _async methods
  from ..utils import async_to_sync
  # import configurable items declared in __init__.py
  from . import conf
  
  # export all the public classes and methods
  __all__ = ['Casda', 'CasdaClass']
  
  
  @async_to_sync
  class CasdaClass(BaseQuery):
  
      """
      Class for accessing ASKAP data through the CSIRO ASKAP Science Data Archive (CASDA). Typical usage:
  
      result = Casda.query_region('22h15m38.2s -45d50m30.5s', radius=0.5 * u.deg)
      """
      # use the Configuration Items imported from __init__.py to set the URL,
      # TIMEOUT, etc.
      URL = conf.server
      TIMEOUT = conf.timeout
      POLL_INTERVAL = conf.poll_interval
      _soda_base_url = conf.soda_base_url
      _uws_ns = {'uws': 'http://www.ivoa.net/xml/UWS/v1.0'}
  
      def __init__(self, user=None, password=None):
          super(CasdaClass, self).__init__()
          if user is None:
              self._authenticated = False
          else:
              self._authenticated = True
              # self._user = user
              # self._password = password
              self._auth = (user, password)
  
      def query_region_async(self, coordinates, radius=1*u.arcmin, height=None, width=None,
                             get_query_payload=False, cache=True):
          """
          Queries a region around the specified coordinates. Either a radius or both a height and a width must be provided.
  
          Parameters
          ----------
          coordinates : str or `astropy.coordinates`.
              coordinates around which to query
          radius : str or `astropy.units.Quantity`, optional
              the radius of the cone search
          height : str or `astropy.units.Quantity`, optional
              the height for a box region
          width : str or `astropy.units.Quantity`, optional
              the width for a box region
          get_query_payload : bool, optional
              Just return the dict of HTTP request parameters.
          cache: bool, optional
              Use the astroquery internal query result cache
  
          Returns
          -------
          response : `requests.Response`
              The HTTP response returned from the service.
              All async methods should return the raw HTTP response.
          """
          request_payload = self._args_to_payload(coordinates=coordinates, radius=radius, height=height,
                                                  width=width)
          if get_query_payload:
              return request_payload
  
          response = self._request('GET', self.URL, params=request_payload,
                                   timeout=self.TIMEOUT, cache=cache)
  
          # result = self._parse_result(response)
          return response
  
      # Create the dict of HTTP request parameters by parsing the user
      # entered values.
      def _args_to_payload(self, radius=1*u.arcmin, **kwargs):
          request_payload = dict()
  
          # Convert the coordinates to FK5
          coordinates = kwargs.get('coordinates')
          if coordinates is not None:
              fk5_coords = commons.parse_coordinates(coordinates).transform_to(coord.FK5)
  
              if kwargs.get('width') is not None and kwargs.get('height') is not None:
                  width = u.Quantity(kwargs['width']).to(u.deg).value
                  height = u.Quantity(kwargs['height']).to(u.deg).value
                  top = fk5_coords.dec.degree + (height/2)
                  bottom = fk5_coords.dec.degree - (height/2)
                  left = fk5_coords.ra.degree - (width/2)
                  right = fk5_coords.ra.degree + (width/2)
                  pos = f'RANGE {left} {right} {bottom} {top}'
              else:
                  radius = u.Quantity(radius).to(u.deg)
                  pos = f'CIRCLE {fk5_coords.ra.degree} {fk5_coords.dec.degree} {radius.value}'
  
              request_payload['POS'] = pos
  
          band = kwargs.get('band')
          channel = kwargs.get('channel')
          if band is not None:
              if channel is not None:
                  raise ValueError("Either 'channel' or 'band' values may be provided but not both.")
  
              if (not isinstance(band, (list, tuple, np.ndarray))) or len(band) != 2 or \
                      (band[0] is not None and not isinstance(band[0], u.Quantity)) or \
                      (band[1] is not None and not isinstance(band[1], u.Quantity)):
                  raise ValueError("The 'band' value must be a list of 2 wavelength or frequency values.")
  
              bandBoundedLow = band[0] is not None
              bandBoundedHigh = band[1] is not None
              if bandBoundedLow and bandBoundedHigh and band[0].unit.physical_type != band[1].unit.physical_type:
                  raise ValueError("The 'band' values must have the same kind of units.")
              if bandBoundedLow or bandBoundedHigh:
                  unit = band[0].unit if bandBoundedLow else band[1].unit
                  if unit.physical_type == 'length':
                      min_band = '-Inf' if not bandBoundedLow else band[0].to(u.m).value
                      max_band = '+Inf' if not bandBoundedHigh else band[1].to(u.m).value
                  elif unit.physical_type == 'frequency':
                      # Swap the order when changing frequency to wavelength
                      min_band = '-Inf' if not bandBoundedHigh else band[1].to(u.m, equivalencies=u.spectral()).value
                      max_band = '+Inf' if not bandBoundedLow else band[0].to(u.m, equivalencies=u.spectral()).value
                  else:
                      raise ValueError("The 'band' values must be wavelengths or frequencies.")
                  # If values were provided in the wrong order, swap them
                  if bandBoundedLow and bandBoundedHigh and min_band > max_band:
                      temp_val = min_band
                      min_band = max_band
                      max_band = temp_val
  
                  request_payload['BAND'] = f'{min_band} {max_band}'
  
          if channel is not None:
              if not isinstance(channel, (list, tuple, np.ndarray)) or len(channel) != 2 or \
                      not isinstance(channel[0], (int, np.integer)) or not isinstance(channel[1], (int, np.integer)):
                  raise ValueError("The 'channel' value must be a list of 2 integer values.")
              if channel[0] <= channel[1]:
                  request_payload['CHANNEL'] = f'{channel[0]} {channel[1]}'
              else:
                  # If values were provided in the wrong order, swap them
                  request_payload['CHANNEL'] = f'{channel[1]} {channel[0]}'
  
          return request_payload
  
      # the methods above implicitly call the private _parse_result method.
      # This should parse the raw HTTP response and return it as
      # an `astropy.table.Table`.
      def _parse_result(self, response, verbose=False):
          # if verbose is False then suppress any VOTable related warnings
          if not verbose:
              commons.suppress_vo_warnings()
          # try to parse the result into an astropy.Table, else
          # return the raw result with an informative error message.
          try:
              # do something with regex to get the result into
              # astropy.Table form. return the Table.
              data = BytesIO(response.content)
              table = Table.read(data)
              return table
          except ValueError as e:
              # catch common errors here, but never use bare excepts
              # return raw result/ handle in some way
              log.info("Failed to convert query result to table", e)
              return response
  
      def filter_out_unreleased(self, table):
          """
          Return a subset of the table which only includes released (public) data.
  
          Parameters
          ----------
          table: `astropy.table.Table`
              A table of results as returned by query_region. Must include an obs_release_date column.
  
          Returns
          -------
          table : `astropy.table.Table`
              The table with all unreleased (non public) data products filtered out.
          """
          now = str(datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f'))
          return table[(table['obs_release_date'] != '') & (table['obs_release_date'] < now)]
  
      def _create_job(self, table, service_name, verbose):
          # Use datalink to get authenticated access for each file
          tokens = []
          soda_url = None
          for row in table:
              access_url = row['access_url']
              if access_url:
                  response = self._request('GET', access_url, auth=self._auth,
                                          timeout=self.TIMEOUT, cache=False)
                  response.raise_for_status()
                  service_url, id_token = self._parse_datalink_for_service_and_id(response, service_name)
                  if id_token:
                      tokens.append(id_token)
                      soda_url = service_url
  
          # Trap a request with no allowed data
          if not soda_url:
              raise ValueError('You do not have access to any of the requested data files.')
  
          # Create job to stage all files
          job_url = self._create_soda_job(tokens, soda_url=soda_url)
          if verbose:
              log.info("Created data staging job " + job_url)
  
          return job_url
  
      def _complete_job(self, job_url, verbose):
          # Wait for job to be complete
          final_status = self._run_job(job_url, verbose, poll_interval=self.POLL_INTERVAL)
          if final_status != 'COMPLETED':
              if verbose:
                  log.info("Job ended with status " + final_status)
              raise ValueError('Data staging job did not complete successfully. Status was ' + final_status)
  
          # Build list of result file urls
          job_details = self._get_job_details_xml(job_url)
          fileurls = []
          for result in job_details.find("uws:results", self._uws_ns).findall("uws:result", self._uws_ns):
              file_location = unquote(result.get("{http://www.w3.org/1999/xlink}href"))
              fileurls.append(file_location)
  
          return fileurls
  
      def stage_data(self, table, verbose=False):
          """
          Request access to a set of data files. All requests for data must use authentication. If you have access to the
          data, the requested files will be brought online and a set of URLs to download the files will be returned.
  
          Parameters
          ----------
          table: `astropy.table.Table`
              A table describing the files to be staged, such as produced by query_region. It must include an
              access_url column.
          verbose: bool, optional
              Should
  
  [... the rest is omitted ...]
files: astroquery/casda/core.py
files: # Licensed under a 3-clause BSD style license - see LICENSE.rst
  
  # 1. standard library imports
  from io import BytesIO
  import os
  from urllib.parse import unquote, urlparse
  import time
  from xml.etree import ElementTree
  from datetime import datetime, timezone
  
  # 2. third party imports
  import astropy.units as u
  import astropy.coordinates as coord
  from astropy.table import Table
  from astropy.io.votable import parse
  from astroquery import log
  
  # 3. local imports - use relative imports
  # commonly required local imports shown below as example
  # all Query classes should inherit from BaseQuery.
  from ..query import BaseQuery
  # has common functions required by most modules
  from ..utils import commons
  # prepend_docstr is a way to copy docstrings between methods
  from ..utils import prepend_docstr_nosections
  # async_to_sync generates the relevant query tools from _async methods
  from ..utils import async_to_sync
  # import configurable items declared in __init__.py
  from . import conf
  
  # export all the public classes and methods
  __all__ = ['Casda', 'CasdaClass']
  
  
  @async_to_sync
  class CasdaClass(BaseQuery):
  
      """
      Class for accessing ASKAP data through the CSIRO ASKAP Science Data Archive (CASDA). Typical usage:
  
      result = Casda.query_region('22h15m38.2s -45d50m30.5s', radius=0.5 * u.deg)
      """
      # use the Configuration Items imported from __init__.py to set the URL,
      # TIMEOUT, etc.
      URL = conf.server
      TIMEOUT = conf.timeout
      POLL_INTERVAL = conf.poll_interval
      _soda_base_url = conf.soda_base_url
      _uws_ns = {'uws': 'http://www.ivoa.net/xml/UWS/v1.0'}
  
      def __init__(self, user=None, password=None):
          super(CasdaClass, self).__init__()
          if user is None:
              self._authenticated = False
          else:
              self._authenticated = True
              # self._user = user
              # self._password = password
              self._auth = (user, password)
  
      def query_region_async(self, coordinates, radius=1*u.arcmin, height=None, width=None,
                             get_query_payload=False, cache=True):
          """
          Queries a region around the specified coordinates. Either a radius or both a height and a width must be provided.
  
          Parameters
          ----------
          coordinates : str or `astropy.coordinates`.
              coordinates around which to query
          radius : str or `astropy.units.Quantity`, optional
              the radius of the cone search
          height : str or `astropy.units.Quantity`, optional
              the height for a box region
          width : str or `astropy.units.Quantity`, optional
              the width for a box region
          get_query_payload : bool, optional
              Just return the dict of HTTP request parameters.
          cache: bool, optional
              Use the astroquery internal query result cache
  
          Returns
          -------
          response : `requests.Response`
              The HTTP response returned from the service.
              All async methods should return the raw HTTP response.
          """
          request_payload = self._args_to_payload(coordinates=coordinates, radius=radius, height=height,
                                                  width=width)
          if get_query_payload:
              return request_payload
  
          response = self._request('GET', self.URL, params=request_payload,
                                   timeout=self.TIMEOUT, cache=cache)
  
          # result = self._parse_result(response)
          return response
  
      # Create the dict of HTTP request parameters by parsing the user
      # entered values.
      def _args_to_payload(self, radius=1*u.arcmin, **kwargs):
          request_payload = dict()
  
          # Convert the coordinates to FK5
          coordinates = kwargs.get('coordinates')
          if coordinates is not None:
              fk5_coords = commons.parse_coordinates(coordinates).transform_to(coord.FK5)
  
              if kwargs.get('width') is not None and kwargs.get('height') is not None:
                  width = u.Quantity(kwargs['width']).to(u.deg).value
                  height = u.Quantity(kwargs['height']).to(u.deg).value
                  top = fk5_coords.dec.degree + (height/2)
                  bottom = fk5_coords.dec.degree - (height/2)
                  left = fk5_coords.ra.degree - (width/2)
                  right = fk5_coords.ra.degree + (width/2)
                  pos = f'RANGE {left} {right} {bottom} {top}'
              else:
                  radius = u.Quantity(radius).to(u.deg)
                  pos = f'CIRCLE {fk5_coords.ra.degree} {fk5_coords.dec.degree} {radius.value}'
  
              request_payload['POS'] = pos
  
          band = kwargs.get('band')
          channel = kwargs.get('channel')
          if band is not None:
              if channel is not None:
                  raise ValueError("Either 'channel' or 'band' values may be provided but not both.")
  
              if (not isinstance(band, (list, tuple))) or len(band) != 2 or \
                      (band[0] is not None and not isinstance(band[0], u.Quantity)) or \
                      (band[1] is not None and not isinstance(band[1], u.Quantity)):
                  raise ValueError("The 'band' value must be a list of 2 wavelength or frequency values.")
  
              bandBoundedLow = band[0] is not None
              bandBoundedHigh = band[1] is not None
              if bandBoundedLow and bandBoundedHigh and band[0].unit.physical_type != band[1].unit.physical_type:
                  raise ValueError("The 'band' values must have the same kind of units.")
              if bandBoundedLow or bandBoundedHigh:
                  unit = band[0].unit if bandBoundedLow else band[1].unit
                  if unit.physical_type == 'length':
                      min_band = '-Inf' if not bandBoundedLow else str(band[0].to(u.m).value)
                      max_band = '+Inf' if not bandBoundedHigh else str(band[1].to(u.m).value)
                  elif unit.physical_type == 'frequency':
                      # Swap the order when changing frequency to wavelength
                      min_band = '-Inf' if not bandBoundedHigh else str(band[1].to(u.m, equivalencies=u.spectral()).value)
                      max_band = '+Inf' if not bandBoundedLow else str(band[0].to(u.m, equivalencies=u.spectral()).value)
                  else:
                      raise ValueError("The 'band' values must be wavelengths or frequencies.")
  
                  request_payload['BAND'] = f'{min_band} {max_band}'
  
          if channel is not None:
              if not isinstance(channel, (list, tuple)) or len(channel) != 2 or \
                      not isinstance(channel[0], int) or not isinstance(channel[1], int):
                  raise ValueError("The 'channel' value must be a list of 2 integer values.")
              request_payload['CHANNEL'] = f'{channel[0]} {channel[1]}'
  
          return request_payload
  
      # the methods above implicitly call the private _parse_result method.
      # This should parse the raw HTTP response and return it as
      # an `astropy.table.Table`.
      def _parse_result(self, response, verbose=False):
          # if verbose is False then suppress any VOTable related warnings
          if not verbose:
              commons.suppress_vo_warnings()
          # try to parse the result into an astropy.Table, else
          # return the raw result with an informative error message.
          try:
              # do something with regex to get the result into
              # astropy.Table form. return the Table.
              data = BytesIO(response.content)
              table = Table.read(data)
              return table
          except ValueError as e:
              # catch common errors here, but never use bare excepts
              # return raw result/ handle in some way
              log.info("Failed to convert query result to table", e)
              return response
  
      def filter_out_unreleased(self, table):
          """
          Return a subset of the table which only includes released (public) data.
  
          Parameters
          ----------
          table: `astropy.table.Table`
              A table of results as returned by query_region. Must include an obs_release_date column.
  
          Returns
          -------
          table : `astropy.table.Table`
              The table with all unreleased (non public) data products filtered out.
          """
          now = str(datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f'))
          return table[(table['obs_release_date'] != '') & (table['obs_release_date'] < now)]
  
      def _create_job(self, table, service_name, verbose):
          # Use datalink to get authenticated access for each file
          tokens = []
          soda_url = None
          for row in table:
              access_url = row['access_url']
              if access_url:
                  response = self._request('GET', access_url, auth=self._auth,
                                          timeout=self.TIMEOUT, cache=False)
                  response.raise_for_status()
                  service_url, id_token = self._parse_datalink_for_service_and_id(response, service_name)
                  if id_token:
                      tokens.append(id_token)
                      soda_url = service_url
  
          # Trap a request with no allowed data
          if not soda_url:
              raise ValueError('You do not have access to any of the requested data files.')
  
          # Create job to stage all files
          job_url = self._create_soda_job(tokens, soda_url=soda_url)
          if verbose:
              log.info("Created data staging job " + job_url)
  
          return job_url
  
      def _complete_job(self, job_url, verbose):
          # Wait for job to be complete
          final_status = self._run_job(job_url, verbose, poll_interval=self.POLL_INTERVAL)
          if final_status != 'COMPLETED':
              if verbose:
                  log.info("Job ended with status " + final_status)
              raise ValueError('Data staging job did not complete successfully. Status was ' + final_status)
  
          # Build list of result file urls
          job_details = self._get_job_details_xml(job_url)
          fileurls = []
          for result in job_details.find("uws:results", self._uws_ns).findall("uws:result", self._uws_ns):
              file_location = unquote(result.get("{http://www.w3.org/1999/xlink}href"))
              fileurls.append(file_location)
  
          return fileurls
  
      def stage_data(self, table, verbose=False):
          """
          Request access to a set of data files. All requests for data must use authentication. If you have access to the
          data, the requested files will be brought online and a set of URLs to download the files will be returned.
  
          Parameters
          ----------
          table: `astropy.table.Table`
              A table describing the files to be staged, such as produced by query_region. It must include an
              access_url column.
          verbose: bool, optional
              Should status message be logged periodically, defaults to False
  
          Returns
          -------
          A list of urls of both the requested files/cutouts and the checksums for the files/cutouts
          """
          if not self._authenticated:
              raise ValueError("Credentials must be supplied to download CASDA image data")
  
          if table is None or len(table) == 0:
              return []
  
          job_url = self._create_job(table, 'async_service', verbose)
  
          return self._complete_job(job_url, verbose)
  
      def cutout(self, table, *, coordinates=None,
  
  [... the rest is omitted ...]
files: astroquery/casda/core.py
files: # -*- coding: utf-8 -*
  
  # Licensed under a 3-clause BSD style license - see LICENSE.rst
  
  
  import pytest
  import requests
  import os
  
  from astropy.coordinates import SkyCoord
  import astropy.units as u
  from astropy.table import Table, Column
  from astropy.io.votable import parse
  from astroquery import log
  import numpy as np
  
  from astroquery.casda import Casda
  
  try:
      from unittest.mock import Mock, patch, MagicMock
  except ImportError:
      pytest.skip("Install mock for the casda tests.", allow_module_level=True)
  
  DATA_FILES = {'CIRCLE': 'cone.xml', 'RANGE': 'box.xml', 'DATALINK': 'datalink.xml', 'RUN_JOB': 'run_job.xml',
                'COMPLETED_JOB': 'completed_job.xml', 'DATALINK_NOACCESS': 'datalink_noaccess.xml',
                'cutout_CIRCLE_333.9092_-45.8418_0.5000': 'cutout_333.9092_-45.8418_0.5000.xml'}
  
  
  class MockResponse:
  
      def __init__(self, content):
          self.content = content
          self.text = content.decode()
  
      def raise_for_status(self):
          return
  
  
  def get_mockreturn(self, method, url, data=None, timeout=10,
                     files=None, params=None, headers=None, **kwargs):
      log.debug("get_mockreturn url:{} params:{} kwargs:{}".format(url, params, kwargs))
      if kwargs and 'auth' in kwargs:
          auth = kwargs['auth']
          if auth and (auth[0] != 'user' or auth[1] != 'password'):
              log.debug("Rejecting credentials")
              return create_auth_failure_response()
  
      if 'data/async' in str(url):
          # Responses for an asynchronous SODA job
          if str(url).endswith('data/async'):
              self.first_job_pass = True
              self.completed_job_key = "COMPLETED_JOB"
              return create_soda_create_response('111-000-111-000')
          elif str(url).endswith('/phase') and method == 'POST':
              key = "RUN_JOB"
          elif str(url).endswith('111-000-111-000/parameters') and method == 'POST':
              assert "POS" in data
              print(data['POS'])
              pos_parts = data['POS'].split(' ')
              assert len(pos_parts) == 4
              self.completed_job_key = 'cutout_{}_{:.4f}_{:.4f}_{:.4f}'.format(pos_parts[0], float(pos_parts[1]),
                  float(pos_parts[2]), float(pos_parts[3]))
              return create_soda_create_response('111-000-111-000')
          elif str(url).endswith('111-000-111-000') and method == 'GET':
              key = "RUN_JOB" if self.first_job_pass else self.completed_job_key
              self.first_job_pass = False
          else:
              raise ValueError("Unexpected SODA async {} call to url {}".format(method, url))
      elif 'datalink' in str(url):
          if 'cube-244' in str(url):
              key = 'DATALINK'
          else:
              key = 'DATALINK_NOACCESS'
      else:
          key = params['POS'].split()[0] if params['POS'] else None
      filename = data_path(DATA_FILES[key])
      log.debug('providing ' + filename)
      content = open(filename, 'rb').read()
      return MockResponse(content)
  
  
  def create_soda_create_response(jobid):
      job_url = 'https://casda.csiro.au/casda_data_access/data/async/' + jobid
      create_response_headers = [
          ['location', job_url]
      ]
  
      create_response = Mock(spec=requests.Response)
      create_response.configure_mock(status_code=303, message='OK', headers=create_response_headers, url=job_url)
      return create_response
  
  
  def create_auth_failure_response():
      unauthenticated_headers = [
          ['WWW-Authenticate', 'Basic realm="ATNF OPAL Login"']
      ]
      create_response = MagicMock(spec=requests.Response)
      attrs = {'raise_for_status.side_effect': requests.exceptions.HTTPError()}
      create_response.configure_mock(status_code=401, message='OK', headers=unauthenticated_headers, **attrs)
      return create_response
  
  
  @pytest.fixture
  def patch_get(request):
      mp = request.getfixturevalue("monkeypatch")
  
      mp.setattr(requests.Session, 'request', get_mockreturn)
      return mp
  
  
  def data_path(filename):
      data_dir = os.path.join(os.path.dirname(__file__), 'data')
      return os.path.join(data_dir, filename)
  
  
  def isclose(value1, value2, abs_tol=1e-09):
      return abs(value1 - value2) < abs_tol
  
  
  def test_query_region_text_radius(patch_get):
      ra = 333.9092
      dec = -45.8418
      radius = 0.5
      query_payload = Casda.query_region('22h15m38.2s -45d50m30.5s', radius=radius * u.deg, cache=False,
                                         get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('CIRCLE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'CIRCLE'
      assert isclose(float(pos_parts[1]), ra, abs_tol=1e-4)
      assert isclose(float(pos_parts[2]), dec, abs_tol=1e-4)
      assert isclose(float(pos_parts[3]), radius)
      assert len(pos_parts) == 4
  
      responses = Casda.query_region('22h15m38.2s -45d50m30.5s', radius=0.5 * u.deg, cache=False)
      assert isinstance(responses, Table)
      assert len(responses) == 3
  
  
  def test_query_region_radius(patch_get):
      ra = 333.9092
      dec = -45.8418
      radius = 0.5
      centre = SkyCoord(ra, dec, unit=('deg', 'deg'))
      query_payload = Casda.query_region(centre, radius=radius * u.deg, cache=False, get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('CIRCLE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'CIRCLE'
      assert isclose(float(pos_parts[1]), ra, abs_tol=1e-5)
      assert isclose(float(pos_parts[2]), dec, abs_tol=1e-5)
      assert isclose(float(pos_parts[3]), radius)
      assert len(pos_parts) == 4
  
      responses = Casda.query_region(centre, radius=0.5 * u.deg, cache=False)
      assert isinstance(responses, Table)
      assert len(responses) == 3
  
  
  def test_query_region_async_radius(patch_get):
      ra = 333.9092
      dec = -45.8418
      radius = 0.5
      centre = SkyCoord(ra, dec, unit=('deg', 'deg'))
      query_payload = Casda.query_region_async(centre, radius=radius * u.deg, cache=False, get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('CIRCLE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'CIRCLE'
      assert isclose(float(pos_parts[1]), ra, abs_tol=1e-5)
      assert isclose(float(pos_parts[2]), dec, abs_tol=1e-5)
      assert isclose(float(pos_parts[3]), radius)
      assert len(pos_parts) == 4
  
      responses = Casda.query_region_async(centre, radius=0.5 * u.deg, cache=False)
      assert isinstance(responses, MockResponse)
  
  
  def test_query_region_box(patch_get):
      ra = 333.9092
      dec = -45.8418
      width = 0.5
      height = 0.2
      centre = SkyCoord(ra, dec, unit=('deg', 'deg'))
      query_payload = Casda.query_region(centre, width=width * u.deg, height=height * u.deg, cache=False,
                                         get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('RANGE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'RANGE'
      assert isclose(float(pos_parts[1]), ra - width / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[2]), ra + width / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[3]), dec - height / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[4]), dec + height / 2, abs_tol=1e-5)
      assert len(pos_parts) == 5
  
      responses = Casda.query_region(centre, width=width * u.deg, height=height * u.deg, cache=False)
      assert isinstance(responses, Table)
      assert len(responses) == 2
  
  
  def test_query_region_async_box(patch_get):
      ra = 333.9092
      dec = -45.8418
      width = 0.5
      height = 0.2
      centre = SkyCoord(ra, dec, unit=('deg', 'deg'))
      query_payload = Casda.query_region_async(centre, width=width * u.deg, height=height * u.deg, cache=False,
                                               get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('RANGE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'RANGE'
      assert isclose(float(pos_parts[1]), ra - width / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[2]), ra + width / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[3]), dec - height / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[4]), dec + height / 2, abs_tol=1e-5)
      assert len(pos_parts) == 5
  
      responses = Casda.query_region_async(centre, width=width * u.deg, height=height * u.deg, cache=False)
      assert isinstance(responses, MockResponse)
  
  
  def test_filter_out_unreleased():
      all_records = parse(data_path('partial_unreleased.xml'), verify='warn').get_first_table().to_table()
      assert all_records[0]['obs_release_date'] == '2017-08-02T03:51:19.728Z'
      assert all_records[1]['obs_release_date'] == '2218-01-02T16:51:00.728Z'
      assert all_records[2]['obs_release_date
  
  [... the rest is omitted ...]
files: astroquery/casda/tests/test_casda.py
files: # -*- coding: utf-8 -*
  
  # Licensed under a 3-clause BSD style license - see LICENSE.rst
  
  
  import pytest
  import requests
  import os
  
  from astropy.coordinates import SkyCoord
  import astropy.units as u
  from astropy.table import Table, Column
  from astropy.io.votable import parse
  from astroquery import log
  
  from astroquery.casda import Casda
  
  try:
      from unittest.mock import Mock, patch, MagicMock
  except ImportError:
      pytest.skip("Install mock for the casda tests.", allow_module_level=True)
  
  DATA_FILES = {'CIRCLE': 'cone.xml', 'RANGE': 'box.xml', 'DATALINK': 'datalink.xml', 'RUN_JOB': 'run_job.xml',
                'COMPLETED_JOB': 'completed_job.xml', 'DATALINK_NOACCESS': 'datalink_noaccess.xml',
                'cutout_CIRCLE_333.9092_-45.8418_0.5000': 'cutout_333.9092_-45.8418_0.5000.xml'}
  
  
  class MockResponse:
  
      def __init__(self, content):
          self.content = content
          self.text = content.decode()
  
      def raise_for_status(self):
          return
  
  
  def get_mockreturn(self, method, url, data=None, timeout=10,
                     files=None, params=None, headers=None, **kwargs):
      log.debug("get_mockreturn url:{} params:{} kwargs:{}".format(url, params, kwargs))
      if kwargs and 'auth' in kwargs:
          auth = kwargs['auth']
          if auth and (auth[0] != 'user' or auth[1] != 'password'):
              log.debug("Rejecting credentials")
              return create_auth_failure_response()
  
      if 'data/async' in str(url):
          # Responses for an asynchronous SODA job
          if str(url).endswith('data/async'):
              self.first_job_pass = True
              self.completed_job_key = "COMPLETED_JOB"
              return create_soda_create_response('111-000-111-000')
          elif str(url).endswith('/phase') and method == 'POST':
              key = "RUN_JOB"
          elif str(url).endswith('111-000-111-000/parameters') and method == 'POST':
              assert "POS" in data
              print(data['POS'])
              pos_parts = data['POS'].split(' ')
              assert len(pos_parts) == 4
              self.completed_job_key = 'cutout_{}_{:.4f}_{:.4f}_{:.4f}'.format(pos_parts[0], float(pos_parts[1]),
                  float(pos_parts[2]), float(pos_parts[3]))
              return create_soda_create_response('111-000-111-000')
          elif str(url).endswith('111-000-111-000') and method == 'GET':
              key = "RUN_JOB" if self.first_job_pass else self.completed_job_key
              self.first_job_pass = False
          else:
              raise ValueError("Unexpected SODA async {} call to url {}".format(method, url))
      elif 'datalink' in str(url):
          if 'cube-244' in str(url):
              key = 'DATALINK'
          else:
              key = 'DATALINK_NOACCESS'
      else:
          key = params['POS'].split()[0] if params['POS'] else None
      filename = data_path(DATA_FILES[key])
      log.debug('providing ' + filename)
      content = open(filename, 'rb').read()
      return MockResponse(content)
  
  
  def create_soda_create_response(jobid):
      job_url = 'https://casda.csiro.au/casda_data_access/data/async/' + jobid
      create_response_headers = [
          ['location', job_url]
      ]
  
      create_response = Mock(spec=requests.Response)
      create_response.configure_mock(status_code=303, message='OK', headers=create_response_headers, url=job_url)
      return create_response
  
  
  def create_auth_failure_response():
      unauthenticated_headers = [
          ['WWW-Authenticate', 'Basic realm="ATNF OPAL Login"']
      ]
      create_response = MagicMock(spec=requests.Response)
      attrs = {'raise_for_status.side_effect': requests.exceptions.HTTPError()}
      create_response.configure_mock(status_code=401, message='OK', headers=unauthenticated_headers, **attrs)
      return create_response
  
  
  @pytest.fixture
  def patch_get(request):
      mp = request.getfixturevalue("monkeypatch")
  
      mp.setattr(requests.Session, 'request', get_mockreturn)
      return mp
  
  
  def data_path(filename):
      data_dir = os.path.join(os.path.dirname(__file__), 'data')
      return os.path.join(data_dir, filename)
  
  
  def isclose(value1, value2, abs_tol=1e-09):
      return abs(value1 - value2) < abs_tol
  
  
  def test_query_region_text_radius(patch_get):
      ra = 333.9092
      dec = -45.8418
      radius = 0.5
      query_payload = Casda.query_region('22h15m38.2s -45d50m30.5s', radius=radius * u.deg, cache=False,
                                         get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('CIRCLE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'CIRCLE'
      assert isclose(float(pos_parts[1]), ra, abs_tol=1e-4)
      assert isclose(float(pos_parts[2]), dec, abs_tol=1e-4)
      assert isclose(float(pos_parts[3]), radius)
      assert len(pos_parts) == 4
  
      responses = Casda.query_region('22h15m38.2s -45d50m30.5s', radius=0.5 * u.deg, cache=False)
      assert isinstance(responses, Table)
      assert len(responses) == 3
  
  
  def test_query_region_radius(patch_get):
      ra = 333.9092
      dec = -45.8418
      radius = 0.5
      centre = SkyCoord(ra, dec, unit=('deg', 'deg'))
      query_payload = Casda.query_region(centre, radius=radius * u.deg, cache=False, get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('CIRCLE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'CIRCLE'
      assert isclose(float(pos_parts[1]), ra, abs_tol=1e-5)
      assert isclose(float(pos_parts[2]), dec, abs_tol=1e-5)
      assert isclose(float(pos_parts[3]), radius)
      assert len(pos_parts) == 4
  
      responses = Casda.query_region(centre, radius=0.5 * u.deg, cache=False)
      assert isinstance(responses, Table)
      assert len(responses) == 3
  
  
  def test_query_region_async_radius(patch_get):
      ra = 333.9092
      dec = -45.8418
      radius = 0.5
      centre = SkyCoord(ra, dec, unit=('deg', 'deg'))
      query_payload = Casda.query_region_async(centre, radius=radius * u.deg, cache=False, get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('CIRCLE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'CIRCLE'
      assert isclose(float(pos_parts[1]), ra, abs_tol=1e-5)
      assert isclose(float(pos_parts[2]), dec, abs_tol=1e-5)
      assert isclose(float(pos_parts[3]), radius)
      assert len(pos_parts) == 4
  
      responses = Casda.query_region_async(centre, radius=0.5 * u.deg, cache=False)
      assert isinstance(responses, MockResponse)
  
  
  def test_query_region_box(patch_get):
      ra = 333.9092
      dec = -45.8418
      width = 0.5
      height = 0.2
      centre = SkyCoord(ra, dec, unit=('deg', 'deg'))
      query_payload = Casda.query_region(centre, width=width * u.deg, height=height * u.deg, cache=False,
                                         get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('RANGE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'RANGE'
      assert isclose(float(pos_parts[1]), ra - width / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[2]), ra + width / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[3]), dec - height / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[4]), dec + height / 2, abs_tol=1e-5)
      assert len(pos_parts) == 5
  
      responses = Casda.query_region(centre, width=width * u.deg, height=height * u.deg, cache=False)
      assert isinstance(responses, Table)
      assert len(responses) == 2
  
  
  def test_query_region_async_box(patch_get):
      ra = 333.9092
      dec = -45.8418
      width = 0.5
      height = 0.2
      centre = SkyCoord(ra, dec, unit=('deg', 'deg'))
      query_payload = Casda.query_region_async(centre, width=width * u.deg, height=height * u.deg, cache=False,
                                               get_query_payload=True)
      assert isinstance(query_payload, dict)
      assert 'POS' in query_payload
      assert query_payload['POS'].startswith('RANGE 333')
      pos_parts = query_payload['POS'].split(' ')
      assert pos_parts[0] == 'RANGE'
      assert isclose(float(pos_parts[1]), ra - width / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[2]), ra + width / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[3]), dec - height / 2, abs_tol=1e-5)
      assert isclose(float(pos_parts[4]), dec + height / 2, abs_tol=1e-5)
      assert len(pos_parts) == 5
  
      responses = Casda.query_region_async(centre, width=width * u.deg, height=height * u.deg, cache=False)
      assert isinstance(responses, MockResponse)
  
  
  def test_filter_out_unreleased():
      all_records = parse(data_path('partial_unreleased.xml'), verify='warn').get_first_table().to_table()
      assert all_records[0]['obs_release_date'] == '2017-08-02T03:51:19.728Z'
      assert all_records[1]['obs_release_date'] == '2218-01-02T16:51:00.728Z'
      assert all_records[2]['obs_release_date'] == ''
      assert
  
  [... the rest is omitted ...]
files: astroquery/casda/tests/test_casda.py
files: .. _astroquery.casda:
  
  **********************************
  CASDA Queries (`astroquery.casda`)
  **********************************
  
  The CSIRO ASKAP Science Data Archive (CASDA) provides access to science-ready data products
  from observations at the `Australian Square Kilometre Array Pathfinder (ASKAP) <https://www.atnf.csiro.au/projects/askap/index.html>`_ telescope.
  These data products include source catalogues, images, spectral line and polarisation cubes, spectra and visbilities.
  This package allows querying of the data products available in CASDA (`<https://casda.csiro.au/>`_).
  
  Listing Data Products
  =====================
  
  The metadata for data products held in CASDA may be queried using the :meth:`~astroquery.casda.CasdaClass.query_region` method.
  The results are returned in a `~astropy.table.Table`.
  The method takes a location and either a radius or a height and width of the region to be queried.
  The location should be specified in ICRS coordinates or an `astropy.coordinates.SkyCoord` object.
  For example:
  
  .. doctest-remote-data::
  
      >>> from astroquery.casda import Casda
      >>> from astropy.coordinates import SkyCoord
      >>> from astropy import units as u
      >>> centre = SkyCoord.from_name('NGC 7232')
      >>> result_table = Casda.query_region(centre, radius=30*u.arcmin)
      >>> print(result_table['obs_publisher_did','s_ra', 's_dec', 'obs_release_date'][:5])
      obs_publisher_did       s_ra           s_dec           obs_release_date
                              deg             deg
      ----------------- --------------- ---------------- ------------------------
             cube-11460 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11461 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11462 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11463 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11464 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
  
  
  In most cases only public data is required. While most ASKAP data is public, some data products may not be released for quality reasons.
  Some derived data produced by science teams may also be embargoed to the science team for a period of team.
  To filter down to just the public data you can use the :meth:`~astroquery.casda.CasdaClass.filter_out_unreleased` method.
  
  For example to filter out the 30 non-public results from the above data set:
  
  .. doctest-remote-data::
  
      >>> public_results = Casda.filter_out_unreleased(result_table)
      >>> print(public_results['obs_publisher_did','s_ra', 's_dec', 'obs_release_date'][:5])
      obs_publisher_did       s_ra           s_dec           obs_release_date
                              deg             deg
      ----------------- --------------- ---------------- ------------------------
             cube-11460 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11461 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11462 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11463 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11464 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
  
  
  Authentication
  ==============
  
  User authentication is required to access data files from CASDA, including
  calibrated visibilities, images and image cubes.
  Authentication is made with OPAL credentials.
  To register with OPAL, go to https://opal.atnf.csiro.au/ and click on the
  link to 'Register'. Enter your email address, name, affiliation and a password.
  The OPAL application will register you straight away.
  
  OPAL user accounts are self-managed. Please keep your account details up to
  date. To change user-registration details, or to request a new OPAL password,
  use the link to 'Log in or reset password'.
  
  To use download tasks, you should create an instance of the ``Casda`` class
  with a username and password. e.g.:
  
  .. doctest-skip::
  
      >>> from astroquery.casda import Casda
      >>> import getpass
      >>> username = 'email@somewhere.edu.au'
      >>> password = getpass.getpass(str("Enter your OPAL password: "))
      >>> casda = Casda(username, password)
  
  
  Data Access
  ===========
  
  In order to access data in CASDA you must first stage the data using the :meth:`~astroquery.casda.CasdaClass.stage_data` 
  method.
  This is because only some of the data in CASDA is held on disc at any particular time.
  The :meth:`~astroquery.casda.CasdaClass.stage_data` method should be passed an astropy Table object containing an 
  'access_url' column.
  This column should contain the datalink address of the data product.
  
  Once the data has been assembled you can then download the data using the :meth:`~astroquery.casda.CasdaClass.download_files` 
  method, or using tools such as wget.
  Authentication is required when staging the data, but not for the download.
  
  An example script to download public continuum images of the NGC 7232 region
  taken in scheduling block 2338 is shown below:
  
  .. doctest-skip::
  
      >>> from astropy import coordinates, units as u, wcs
      >>> from astroquery.casda import Casda
      >>> import getpass
      >>> centre = coordinates.SkyCoord.from_name('NGC 7232')
      >>> username = 'email@somewhere.edu.au'
      >>> password = getpass.getpass(str("Enter your OPAL password: "))
      >>> casda = Casda(username, password)
      >>> result = Casda.query_region(centre, radius=30*u.arcmin)
      >>> public_data = Casda.filter_out_unreleased(result)
      >>> subset = public_data[(public_data['dataproduct_subtype']=='cont.restored.t0') & (public_data['obs_id']=='2338')]
      >>> url_list = casda.stage_data(subset)
      >>> filelist = casda.download_files(url_list, savedir='/tmp')
  
  
  Cutouts
  =======
  
  As well as accessing full data products, the CASDA service can produce cutout images and cubes from larger data products.
  The cutout support in AstroQuery allows both spatial and spectral cutouts.
  To produce a spatial cutout, pass in a coordinate and either a radius or a height and a width to the 
  :meth:`~astroquery.casda.CasdaClass.cutout` method.
  To produce a spectral cutout, pass in either a band or a channel value. 
  For band, the value must be a list or tuple of two `astropy.units.Quantity` objects specifying a low and high frequency
  or wavelength. For an open ended range use `None` as the open value.
  For channel, the value must be a list or tuple of two integers specifying the low and high channels (i.e. planes of a
  cube) inclusive.
  Spatial and spectral parameters can be combined to produce sub-cubes.
  
  Once completed, the cutouts can be downloaded as described in the section above.
  
  An example script to download a 2D cutout from the Rapid ASKAP Continuum Survey (RACS) at a specified position is shown
  below:
  
  .. doctest-skip::
  
      >>> from astropy import coordinates, units as u, wcs
      >>> from astroquery.casda import Casda
      >>> import getpass
      >>> centre = coordinates.SkyCoord.from_name('2MASX J08161181-7039447')
      >>> username = 'email@somewhere.edu.au'
      >>> password = getpass.getpass(str("Enter your OPAL password: "))
      >>> casda = Casda(username, password)
      >>> result = Casda.query_region(centre, radius=30*u.arcmin)
      >>> public_data = Casda.filter_out_unreleased(result)
      >>> subset = public_data[((public_data['obs_collection'] == 'The Rapid ASKAP Continuum Survey') & #
                        (np.char.startswith(public_data['filename'], 'RACS-DR1_')) & #
                        (np.char.endswith(public_data['filename'], 'A.fits'))
                       )]
      >>> url_list = casda.cutout(subset[:1], coordinates=centre, radius=14*u.arcmin)
      >>> filelist = casda.download_files(url_list, savedir='/tmp')
  
  An example script to download a 3D cutout from the WALLABY Pre-Pilot Eridanus cube at a specified position and velocity
  is shown below:
  
  .. doctest-skip::
  
      >>> from astropy import coordinates, units as u, wcs
      >>> from astroquery.casda import Casda
      >>> import getpass
      >>> centre = coordinates.SkyCoord.from_name('NGC 1371')
      >>> username = 'email@somewhere.edu.au'
      >>> password = getpass.getpass(str("Enter your OPAL password: "))
      >>> casda = Casda(username, password)
      >>> result = Casda.query_region(centre, radius=30*u.arcmin)
      >>> public_data = Casda.filter_out_unreleased(result)
      >>> eridanus_cube = public_data[public_data['filename'] == 'Eridanus_full_image_V3.fits']
      >>> vel = np.array([1250, 1600])*u.km/u.s
      >>> freq = vel.to(u.Hz, equivalencies=u.doppler_radio(1.420405751786*u.GHz))
      >>> url_list = casda.cutout(eridanus_cube, coordinates=centre, radius=9*u.arcmin, band=freq)
      >>> filelist = casda.download_files(url_list, savedir='/tmp')
  
  
  Reference/API
  =============
  
  .. automodapi:: astroquery.casda
      :no-inheritance-diagram:
  
  .. _IAU format: http://cdsweb.u-strasbg.fr/Dic/iau-spec.html.
  
  
  [... the rest is omitted ...]
files: docs/casda/casda.rst
files: .. _astroquery.casda:
  
  **********************************
  CASDA Queries (`astroquery.casda`)
  **********************************
  
  The CSIRO ASKAP Science Data Archive (CASDA) provides access to science-ready data products
  from observations at the `Australian Square Kilometre Array Pathfinder (ASKAP) <https://www.atnf.csiro.au/projects/askap/index.html>`_ telescope.
  These data products include source catalogues, images, spectral line and polarisation cubes, spectra and visbilities.
  This package allows querying of the data products available in CASDA (`<https://casda.csiro.au/>`_).
  
  Listing Data Products
  =====================
  
  The metadata for data products held in CASDA may be queried using the :meth:`~astroquery.casda.CasdaClass.query_region` method.
  The results are returned in a `~astropy.table.Table`.
  The method takes a location and either a radius or a height and width of the region to be queried.
  The location should be specified in ICRS coordinates or an `astropy.coordinates.SkyCoord` object.
  For example:
  
  .. doctest-remote-data::
  
      >>> from astroquery.casda import Casda
      >>> from astropy.coordinates import SkyCoord
      >>> from astropy import units as u
      >>> centre = SkyCoord.from_name('NGC 7232')
      >>> result_table = Casda.query_region(centre, radius=30*u.arcmin)
      >>> print(result_table['obs_publisher_did','s_ra', 's_dec', 'obs_release_date'][:5])
      obs_publisher_did       s_ra           s_dec           obs_release_date
                              deg             deg
      ----------------- --------------- ---------------- ------------------------
             cube-11460 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11461 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11462 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11463 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11464 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
  
  
  In most cases only public data is required. While most ASKAP data is public, some data products may not be released for quality reasons.
  Some derived data produced by science teams may also be embargoed to the science team for a period of team.
  To filter down to just the public data you can use the :meth:`~astroquery.casda.CasdaClass.filter_out_unreleased` method.
  
  For example to filter out the 30 non-public results from the above data set:
  
  .. doctest-remote-data::
  
      >>> public_results = Casda.filter_out_unreleased(result_table)
      >>> print(public_results['obs_publisher_did','s_ra', 's_dec', 'obs_release_date'][:5])
      obs_publisher_did       s_ra           s_dec           obs_release_date
                              deg             deg
      ----------------- --------------- ---------------- ------------------------
             cube-11460 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11461 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11462 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11463 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
             cube-11464 326.52250211117 -43.891190432671 2020-11-18T09:49:27.134Z
  
  
  Authentication
  ==============
  
  User authentication is required to access data files from CASDA, including
  calibrated visibilities, images and image cubes.
  Authentication is made with OPAL credentials.
  To register with OPAL, go to https://opal.atnf.csiro.au/ and click on the
  link to 'Register'. Enter your email address, name, affiliation and a password.
  The OPAL application will register you straight away.
  
  OPAL user accounts are self-managed. Please keep your account details up to
  date. To change user-registration details, or to request a new OPAL password,
  use the link to 'Log in or reset password'.
  
  To use download tasks, you should create an instance of the ``Casda`` class
  with a username and password. e.g.:
  
  .. doctest-skip::
  
      >>> from astroquery.casda import Casda
      >>> import getpass
      >>> username = 'email@somewhere.edu.au'
      >>> password = getpass.getpass(str("Enter your OPAL password: "))
      >>> casda = Casda(username, password)
  
  
  Data Access
  ===========
  
  In order to access data in CASDA you must first stage the data using the :meth:`~astroquery.casda.CasdaClass.stage_data` 
  method.
  This is because only some of the data in CASDA is held on disc at any particular time.
  The :meth:`~astroquery.casda.CasdaClass.stage_data` method should be passed an astropy Table object containing an 
  'access_url' column.
  This column should contain the datalink address of the data product.
  
  Once the data has been assembled you can then download the data using the :meth:`~astroquery.casda.CasdaClass.download_files` 
  method, or using tools such as wget.
  Authentication is required when staging the data, but not for the download.
  
  An example script to download public continuum images of the NGC 7232 region
  taken in scheduling block 2338 is shown below:
  
  .. doctest-skip::
  
      >>> from astropy import coordinates, units as u, wcs
      >>> from astroquery.casda import Casda
      >>> import getpass
      >>> centre = coordinates.SkyCoord.from_name('NGC 7232')
      >>> username = 'email@somewhere.edu.au'
      >>> password = getpass.getpass(str("Enter your OPAL password: "))
      >>> casda = Casda(username, password)
      >>> result = Casda.query_region(centre, radius=30*u.arcmin)
      >>> public_data = Casda.filter_out_unreleased(result)
      >>> subset = public_data[(public_data['dataproduct_subtype']=='cont.restored.t0') & (public_data['obs_id']=='2338')]
      >>> url_list = casda.stage_data(subset)
      >>> filelist = casda.download_files(url_list, savedir='/tmp')
  
  
  Cutouts
  =======
  
  As well as accessing full data products, the CASDA service can produce cutout images and cubes from larger data products.
  The cutout support in AstroQuery allows both spatial and spectral cutouts.
  To produce a spatial cutout, pass in a coordinate and either a radius or a height and a width to the 
  :meth:`~astroquery.casda.CasdaClass.cutout` method.
  To produce a spectral cutout, pass in either a band or a channel value. 
  For band, the value must be a list or tuple of two `astropy.units.Quantity` objects specifying a low and high frequency
  or wavelength. For an open ended range use `None` as the open value.
  For channel, the value must be a list or tuple of two integers specifying the low and high channels (i.e. planes of a
  cube) inclusive.
  Spatial and spectral parameters can be combined to produce sub-cubes.
  
  Once completed, the cutouts can be downloaded as described in the section above.
  
  An example script to download a cutout from the Rapid ASKAP Continuum Survey (RACS) at a specified position is shown
  below:
  
  .. doctest-skip::
  
      >>> from astropy import coordinates, units as u, wcs
      >>> from astroquery.casda import Casda
      >>> import getpass
      >>> centre = coordinates.SkyCoord.from_name('2MASX J08161181-7039447')
      >>> username = 'email@somewhere.edu.au'
      >>> password = getpass.getpass(str("Enter your OPAL password: "))
      >>> casda = Casda(username, password)
      >>> result = Casda.query_region(centre, radius=30*u.arcmin)
      >>> public_data = Casda.filter_out_unreleased(result)
      >>> subset = public_data[((public_data['obs_collection'] == 'The Rapid ASKAP Continuum Survey') & #
                        (np.char.startswith(public_data['filename'], 'RACS-DR1_')) & #
                        (np.char.endswith(public_data['filename'], 'A.fits'))
                       )]
      >>> url_list = casda.cutout(subset[:1], coordinates=centre, radius=14*u.arcmin)
      >>> filelist = casda.download_files(url_list, savedir='/tmp')
  
  
  Reference/API
  =============
  
  .. automodapi:: astroquery.casda
      :no-inheritance-diagram:
  
  .. _IAU format: http://cdsweb.u-strasbg.fr/Dic/iau-spec.html.
  
  
  [... the rest is omitted ...]
files: docs/casda/casda.rst