<resource schema="rome" resdir=".">
	<meta name="creationDate">2024-10-31T12:55:21Z</meta>

	<meta name="title">ROME/REA Timeseries Photometry Data Release 1</meta>
	<meta name="description">
The ROME/REA (Robotic Observations of Microlensing Events/Reactive Event
Assessment) Survey was a Key Project at Las Cumbres Observatory (hereafter LCO)
which continuously monitored 20 selected fields (3.76 sq.deg.) in the Galactic
Bulge throughout their seasonal visibility window over a three-year period,
between March 2017 and March 2020.	Observations were made in three optical
passbands (SDSS g', r', i'), and LCO's multi-site telescope network enabled
the survey to achieve a typical cadence of ~10hrs in i' and ~15hrs in g' and
r'.	In addition, intervals of higher cadence (&lt;1hr) data were obtained during
monitoring of key microlensing events within the fields.	This catalog includes
the full timeseries photometry for all ~8 million stars, down to a limiting
magnitude of i~18mag.
	</meta>

	<meta name="_longdoc" format="rst">
		As a short example for how to explore this data using VO protocols,
		try this:

		* Open TOPCAT and it's VO/TAP window.  Select "GAVO DC TAP" from
		  the server selection and hit "use service".
		* We would like to find "unusual" time series; perhaps ones with
		  a large range.  To get a feeling for what "unusual" might mean,
		  just fetch a few metadata rows like with a TAP query like this::

		  	SELECT TOP 7000 * FROM rome.time_series
		  	WHERE np_i>100 AND np_g>100
		* Do a plane plot of that data, and ask TOPCAT to plot
		  ``mag_i_max-mag_i_min`` against ``mag_g_max-mag_g_min``.
		* When you open a table view, you can inspect the metadata
		  for the time series when clicking on a point in the plot.
		* Let's inspect the actual data: In TOPCAT's main window,
		  do Views/Activation Actions.  If you check "Plot Table",
		  TOPCAT will download and plot the time series; it will only
		  plot the first lightcurve, though.  To get all bands, at this point you
		  have to manually fetch accref from the table and use File -> Load Table.
		  Sorry about this.
		* Using datalink, you can also view three-band previews with one click on
		  a plot point.  To configure this, check "Invoke Service" in the
		  Activation Actions, then use "Invoke now".  In the dialog that
		  pops up then, select the row with #preview and check Auto-Invoke.
		  You will then see a light rendition of the light curves in (up to)
		  three colours.

		Your desktop could then look somewhat like this:

		.. image:: /\rdId/sdl/static/rome-action.jpeg
	</meta>

	<meta name="subject">broad-band-photometry</meta>
	<meta name="subject">ccd-photometry</meta>
	<meta name="subject">light-curves</meta>
	<meta name="subject">multi-color-photometry</meta>
	<meta name="subject">photometry</meta>
	<meta name="subject">sloan-photometry</meta>
	<meta name="subject">stellar-photometry</meta>
	<meta name="subject">gravitational-microlensing</meta>
	<meta name="subject">variable-stars</meta>

	<meta name="schema-rank">50</meta>

	<meta name="creator">
		Street, R.A.; Bachelet, E.; Tsapras, Y.; Hundertmark, M.P.G.; Bozza, V.;
		Bramich, D.M.; Cassan, A.; Dominik, M.; Figuera Jaimes, R.; Horne, K.; Mao,
		S.; Saha, A.; Wambsganss, J.; Zang, W.</meta>

	<meta name="instrument">Sinistro cameras</meta>
	<meta name="facility">Las Cumbres Observatory Telescope Network</meta>

	<meta name="source">2024PASP..136f4501S</meta>
	<meta name="contentLevel">Research</meta>
	<meta name="type">Survey</meta>

	<meta name="coverage.waveband">Optical</meta>

	<meta name="ssap.dataSource">survey</meta>
	<meta name="ssap.creationType">archival</meta>
	<meta name="productType">timeseries</meta>
	<meta name="ssap.testQuery">MAXREC=1</meta>

	<FEED source="//procs#license-cc-by-sa" what="The ROME/REA data"/>

	<meta name="rights" format="rst">
		This data release is a public product of the ROME/REA Key Project survey
		conducted on the robotic telescope network of the Las Cumbres Observatory
		(LCO).  Individual images used in the data reduction are freely available
		for download on the LCO archive (https://archive.lco.global/).

		It is important to clarify that the light curves available through this
		data release are not optimized photometric reductions of individual
		objects.  Researchers intending to utilize this data set for publications
		are kindly requested to acknowledge this source by citing the present paper
		(Street et al 2024 :bibcode:`2024PASP..136f4501S`) along with the original
		work by Tsapras et al 2019 :bibcode:`2019PASP..131l4401T`.

		For research work that requires enhanced photometry for specific targets
		within this catalog, we encourage reaching out to us directly.
		Collaboration opportunities are sincerely welcomed, and we look forward to
		supporting further inquiries and investigations.
	</meta>

	<macDef name="banddefs">
		filter, filterHuman, ucd
		i, SDSS i', em.opt.i
		g, SDSS g', em.opt.v
		r, SDSS r', em.opt.r
	</macDef>

	<execute on="loaded" title="define functions"><job>
		<setup imports="h5py, numpy, functools"/>
		<code>
		def get_photpoints(field, quadrant, quadrant_id):
			"""returns the photometry points for the specified time series
			from the HDF5 as a numpy array.

			Regrettably, null value handling is a pain.  We replace 0 and
			-9999 with NaNs.  You will get back everything, which for most time
			series is basically only junk.  Do throw away everything
			that does not have isnan(rec[-1]).  We don't do that here because
			the array indexes are significant.  And sorry for a NaN flag.  The
			input is slightly insane.
			"""
			dest_path = "data/ROME-FIELD-{:02d}_quad{:d}_photometry.hdf5".format(
				field, quadrant)
			srchdf = h5py.File(rd.getAbsPath(dest_path))
			_, arr = next(iter(srchdf.items()))

			try:
				photpoints = arr[quadrant_id-1]
			except IndexError:
				# test instrumentation: test set construction currently is
				# such that index 1 in the test hdf5 is 70001 in the original.
				# Yeah, it's a pain, but so is the upstream format
				base.ui.notifyWarning("Rome: test data kicking in")
				photpoints = arr[quadrant_id-70002]
		
			photpoints = numpy.array(photpoints)
			photpoints[photpoints==0] = numpy.nan
			photpoints[photpoints==-9999.99] = numpy.nan

			return photpoints

		@functools.cache
		def get_photpoint_char(field):
			with base.getTableConn() as conn:
				return list(conn.queryToDicts(
					"select filename, hjd, filter"
					" from \schema.images"
						" where field=%(field)s"
						" order by image_index", locals()))


		def get_photpoints_with_bands(field, quadrant, quadrant_id):
			"""yields a sequence of (bandname, hdf5row, src-image) tuples.

			Completely broken hdf5rows are already swallowed here.
			It's a shame this function is even necessary.

			Bandname is one of i, r, or g.
			"""
			photpoints = rd.get_photpoints(field, quadrant, quadrant_id)
			images = get_photpoint_char(field)

			for rec, imgrow in zip(photpoints, images, strict=True):
				# Since we replace 0 with NaN, ok data points now have NaN
				# in their qc_flag.  Sigh.
				if numpy.isnan(rec[-1]):
					continue

				# chuck out all records without usable photometry, too.
				if numpy.all(numpy.isnan(rec[1:9])):
					continue
				assert abs(rec[0]-imgrow["hjd"])&lt;1e-7

				yield imgrow["filter"], rec, imgrow["filename"]


		def parse_rome_id(rome_id):
			"""returns a tuple of field, quadrant, quadrant id from one of
			our internal rome_ids.
			"""
			return rome_id//10000000, (rome_id//1000000)%10, (rome_id%1000000)
	

		rd.get_photpoints = get_photpoints
		rd.get_photpoints_with_bands = get_photpoints_with_bands
		rd.parse_rome_id = parse_rome_id
	</code></job></execute>

	<table id="images" onDisk="True" adql="True" mixin="//products#table">

		<column name="rome_id" type="integer" required="True"
			ucd="meta.id;meta.main"
			tablehead="Image Id"
			description="Identifier for the ROME/REA image.	This is
				field*100000+per-field-index"
			verbLevel="1"/>
		<column name="field" type="smallint" required="True"
			ucd="meta.id;obs.field"
			description="ROME field the object was observed in"
			verbLevel="15"/>
		<column name="image_index" type="smallint" required="True"
			ucd="meta.id"
			description="Internal index of the image within a dataset, starting from
				0.  This correspondds to the index of a photometry row in the
				photometry hdf5s"
			verbLevel="15"/>

		<column name="filename" type="text"
			tablehead="File Name"
			description="Upstream file name for this image"
			verbLevel="25"/>
		<column name="dataset_code" type="text"
			tablehead="Dataset Id"
			description="Dataset this image belongs to (a dataset in ROME/REA
				is the collection of all images from a given instrument)"
			verbLevel="25"/>
		<column name="filter" type="text"
			ucd="meta.id;instr.filter"
			tablehead="Filter"
			description="Filter this image was taken with"
			verbLevel="1"/>
		<column name="hjd" type="double precision"
			ucd="time.epoch" unit="d"
			description="HJD (i.e., UTC with light-time corrections for the solar
				centre) of the midpoint of exposure"
			verbLevel="1"/>
		<column name="exposure" type="double precision"
			ucd="time.duration;obs.exposure" unit="s"
			description="Exposure time for this image"
			verbLevel="1"/>
		<!-- TODO: WCS -->

		<column name="moon_ang_separation"
			ucd="instr.param;pos.angDistance" unit="deg"
			tablehead="Moon dist."
			description="Angular Distance to the moon"
			verbLevel="25"/>
		<column name="moon_fraction" type="double precision"
			ucd="instr.param"
			description="Moon phase, 0=new moon"
			verbLevel="25"/>
		<column name="airmass" type="double precision"
			ucd="obs.airMass"
			description="Airmass for this observation"
			verbLevel="25"/>
		<column name="nstars" type="integer" required="True"
			ucd="meta.number"
			tablehead="N"
			description="Number of photometry points extracted from this image"
			verbLevel="25"/>
		<column name="frac_sat_pix" type="double precision"
			ucd="phot.count;arith.ratio"
			tablehead="|Sat|/pix"
			description="Fraction of saturated pixels on this image"
			verbLevel="25"/>
		<column name="qc_flag" type="smallint"
			ucd="meta.code.qual"
			tablehead="Flags"
			description="Quality flags; null means a good image, for other
				codes see table note"
			verbLevel="15" note="q">
			<values nullLiteral="-1"/>
		</column>

		<meta name="note" tag="q">
			qc_flag is a bitmap, where 0 means a good image.	 In the light curves
			we serve, only points with qc_flag=0 are retained, and you should only
			use other data if you really know what you are doing.  In case you
			get ROME data from other sources, here is what the flags mean:

			==== =================================================================
			 1   No valid photometric measurement possible by stage 6
			 2   Image photometry displayed excessive scatter (mostly bad seeing)
			 4   Data point failed phot scale factor/exposure time metric
			 8   Low quality image resampling
			 16  Difference image exhibited high residuals
			==== =================================================================
		</meta>
	</table>

	<STREAM id="parse-from-fits">
		<doc>Elements to arrange for parsing from the 5-ext FITSes.  Pass
		hdu to select the extension, as well as accref,
		table, fsize, mime, and path for the products rowfilter.  All the
		latter are python expressions.  And there's rome_id, a python
		expression to compute a value ending up in a field rome_id.</doc>

		<sources pattern="data/*crossmatch.fits"/>
		<fitsTableGrammar hdu="\hdu">
			<sourceFields>
				<setup imports="re"/>
				<code>
					mat = re.search(r"([0-9]+)_field", sourceToken)
					return {
						"field": int(mat.group(1))}
				</code>
			</sourceFields>

			<rowfilter name="define_id">
				<code>
				@rome_id = \rome_id
				yield row
				</code>
			</rowfilter>

			<rowfilter procDef="//products#define">
				<bind key="accref">\accref</bind>
				<bind key="table">\table</bind>
				<bind key="fsize">\fsize</bind>
				<bind key="mime">\mime</bind>
				<bind key="path">\path</bind>
				<bind name="preview">\preview</bind>
				<bind name="preview_mime">\preview_mime</bind>
			</rowfilter>
		</fitsTableGrammar>
	</STREAM>

	<data id="import-images">
		<FEED source="parse-from-fits" hdu="4"
			table="'\schema.images'"
			rome_id="@field*100000+@index"
			accref="'/rome/images/{}'.format(@field*100000+@index)"
			path="'https://archive-api.lco.global/frames/?limit=1&amp;proposal_id=KEY2017AB-004&amp;basename_exact='+urllib.parse.quote(@filename[:-5])"
			fsize="None"
			mime="'application/fits'"
			preview="None"
			preview_mime="None"/>
		<make table="images">
			<rowmaker idmaps="*" simplemaps="image_index: index">
				<apply procDef="//procs#dictMap">
					<bind name="key">"filter"</bind>
					<bind name="mapping">{
						"ip": "SDSS i'",
						"rp": "SDSS r'",
						"gp": "SDSS g'",}</bind>
				</apply>
				<map key="hjd" nullExpr="0.0"/>
				<map key="exposure" nullExpr="0.0"/>
			</rowmaker>
		</make>
	</data>

	<table id="raw_series" onDisk="True" adql="hidden" mixin="//products#table">
		<meta name="description">Time series observed with ROME/REA
			in a custom scheme.</meta>
		<mixin>//ssap#plainlocation</mixin>
		<mixin>//ssap#simpleCoverage</mixin>
		<primary>rome_id</primary>

		<index columns="mag_i_min"/>
		<index columns="mag_i_max"/>
		<index columns="upstream_name"/>

		<column name="rome_id" type="integer" required="True"
			ucd="meta.id;meta.main"
			tablehead="Id"
			description="Local unique identifier for this object within ROME/REA:
				field*10'000'000+quadrant*1'000'000'+quadrant_id"
			verbLevel="1"/>
		<column name="upstream_name" type="text"
			ucd="meta.id"
			tablehead="ROME/REA"
			description="Upstream has a field/field_id naming scheme skipping
				the quadrants.  This column can be used to go from our data to
				upstream's."
			verbLevel="15"/>
		<column name="ra" type="double precision"
			ucd="meta.id;meta.main" unit="deg"
			tablehead="RA"
			description="ICRS right ascension of this object"
			verbLevel="1"/>
		<column name="dec" type="double precision"
			ucd="meta.id;meta.main" unit="deg"
			tablehead="Dec"
			description="ICRS declination of this object"
			verbLevel="1"/>
		<column name="hjd_min" type="double precision"
			ucd="time.epoch;stat.min" unit="d"
			tablehead="min(T)"
			description="Earliest observation represented in the time series"
			verbLevel="15"
			displayHint="sf=0"/>
		<column name="hjd_max" type="double precision"
			ucd="time.epoch;stat.max" unit="d"
			tablehead="max(T)"
			description="Latest observation represented in the time series"
			verbLevel="15"
			displayHint="sf=0"/>
		<column name="ssa_dateObs" type="double precision"
			unit="d" ucd="time.epoch"
			tablehead="Mean Date"
			description="Mean MJD of observations"
			verbLevel="15"
			displayHint="type=humanDate"/>

		<LOOP>
			<csvItems>\banddefs</csvItems>
			<events>
				<column name="mag_\filter\+_min"
					ucd="phot.mag;\ucd;stat.min" unit="mag"
					tablehead="min(mag_\filter)"
					description="Minimal (brightest) magnitude measured in
						the \filterHuman band."
					verbLevel="15" displayHint="sf=3"/>
				<column name="mag_\filter\+_max"
					ucd="phot.mag;\ucd;stat.max" unit="mag"
					tablehead="max(mag_\filter)"
					description="Maximal (faintest) magnitude measured
						the \filterHuman band."
					verbLevel="15" displayHint="sf=3"/>
				<column name="mag_\filter\+_mean"
					ucd="phot.mag;\ucd;stat.mean" unit="mag"
					tablehead="&lt;mag_\filter>"
					description="Mean magnitude of the \filterHuman lightcurve."
					verbLevel="15" displayHint="sf=3"/>
				<column name="np_\filter" type="smallint" required="True"
					ucd="meta.number;obs"
					tablehead="N(\filter)"
					description="Number of measurements in the \filterHuman lightcurve."
					verbLevel="15"/>
			</events>
		</LOOP>

		<column name="field" original="images.field"/>
		<column name="quadrant" type="smallint" required="True"
			ucd="meta.id;obs.field"
			description="Quadrant of this object (for purely technical reasons,
				ROME has split each field into four quadrants)."
			verbLevel="15"/>
		<column name="quadrant_id" type="integer" required="True"
			ucd="meta.id"
			tablehead="Id(Q)"
			description="Identifier for this object within its quadrant"
			verbLevel="15"/>
		<column name="gaia_source_id" type="bigint"
			ucd="meta.id.cross"
			tablehead="DR3 source_id"
			description="Gaia DR3 source_id for this object"
			verbLevel="1">
			<values nullLiteral="-1"/>
		</column>
	</table>

	<data id="import-raw-series">
		<recreateAfter>make-view</recreateAfter>

		<FEED source="parse-from-fits" hdu="1"
			rome_id="@field*10000000+@quadrant*1000000+@quadrant_id"
			accref="'rome/ts/'+str(@rome_id)"
			table="'\schema.raw_series'"
			fsize="30000"
			mime="'application/x-votable+xml'"
			path="base.makeAbsoluteURL('\rdId/sdl/dlget?ID=rome/ts/%s'%row['rome_id'])"
			preview="base.makeAbsoluteURL('\rdId/preview/qp/'+str(row['rome_id']))"
			preview_mime="'image/png'"
			/>
		<make table="raw_series">
			<rowmaker idmaps="*">
				<apply procDef="//ssap#fill-plainlocation">
					<bind key="aperture">0.0002</bind>
					<bind key="ra">@ra</bind>
					<bind key="dec">@dec</bind>
				</apply>
				<map key="gaia_source_id" nullExcs="ValueError"/>
				<var name="upstream_name">"ROME-FIELD-{:02d}_{:06d}".format(
					@field, @field_id)</var>

				<apply name="characterise_ts">
					<setup>
						<par name="stats_template">{
							"np": 0,
							"min_mag": 1000,
							"max_mag": -1000,
							"sum": 0}</par>
					</setup>
					<code>
					stats_by_band = {
						"SDSS r'": stats_template.copy(),
						"SDSS i'": stats_template.copy(),
						"SDSS g'": stats_template.copy(),}
					hjd_min, hjd_max = 1e10, 0

					for filter, row, _ in rd.get_photpoints_with_bands(
							@field, @quadrant, @quadrant_id):
						hjd, mag = row[0], row[7]
						if mag!=mag:
							continue
						stats = stats_by_band[filter]
						stats["np"] += 1
						stats["min_mag"] = min(stats["min_mag"], mag)
						stats["max_mag"] = max(stats["max_mag"], mag)
						stats["sum"] += mag

						hjd_min = min(hjd_min, hjd)
						hjd_max = max(hjd_max, hjd)

					if hjd_min==1e10:
						raise IgnoreThisRow()

					@hjd_min = hjd_min
					@hjd_max = hjd_max
					@ssa_dateObs = (hjd_max+hjd_min)/2-JD_MJD
					@ssa_timeExt = (hjd_max-hjd_min)*86400
				
					for filter in ["SDSS r'", "SDSS i'", "SDSS g'"]:
						stats = stats_by_band[filter]
						fchar = filter[5]
						if stats["np"]:
							vars[f"mag_{fchar}_min"] = stats["min_mag"]
							vars[f"mag_{fchar}_max"] = stats["max_mag"]
							vars[f"mag_{fchar}_mean"] = stats["sum"]/stats["np"]
							vars[f"np_{fchar}"] = stats["np"]
						else:
							vars[f"mag_{fchar}_min"] = \
								vars[f"mag_{fchar}_max"] = \
								vars[f"mag_{fchar}_mean"] = None
							vars[f"np_{fchar}"] = 0

					</code>
				</apply>
			</rowmaker>
		</make>
	</data>

	<table id="time_series" onDisk="True" adql="hidden">
		<meta name="_associatedDatalinkService">
			<meta name="serviceId">sdl</meta>
			<meta name="idColumn">ssa_pubDID</meta>
		</meta>

		<mixin
			sourcetable="raw_series"
			copiedcolumns="*"
			ssa_aperture="0.4/3600."
			ssa_fluxunit="'mag'"
			ssa_spectralunit="''"
			ssa_bandpass="'Optical'"
			ssa_collection="'ROME/REA time series'"
			ssa_fluxcalib="'ABSOLUTE'"
			ssa_fluxucd="'phot.mag;em.opt'"
			ssa_speccalib="''"
			ssa_spectralucd="''"
			ssa_targclass="'star'"
			ssa_specstart="8.6e-7"
			ssa_specend="3.6e-7"
			ssa_specmid="6.1e-7"
			ssa_specext="5e-7"
		>//ssap#view</mixin>

		<mixin
			calibLevel="2"
			coverage="ssa_region"
			sResolution="ssa_spaceRes"
			oUCD="ssa_fluxucd"
			>//obscore#publishSSAPMIXC</mixin>
	</table>

	<data id="make-view" auto="False">
		<make table="time_series"/>
	</data>

	<coverage>
		<updater sourceTable="time_series"/>
	</coverage>

	<LOOP>
		<csvItems>\banddefs</csvItems>
		<events>
			<table id="instance-\filter" onDisk="False">
				<meta name="description"
					>ROME/REA time series in the \filterHuman band.
				</meta>
				<mixin	
					filterIdentifier='"\filterHuman"'
					magnitudeSystem="Vega"
					effectiveWavelength="0.00001"
					longitude="@ra"
					latitude="@dec"
					phot_description="VPHAS+-calibrated magnitude in \filterHuman"
					phot_ucd='phot.mag;\ucd;meta.main'
					phot_unit="mag"
					pos_epoch="J2015.5"
					refframe="ICRS"
					refposition="HELIOCENTER"
					time0="0"
					time_description="HJD of observation"
					timescale="UTC"
				>//timeseries#phot-0</mixin>

				<param original="raw_series.accref"/>
				<param name="title" type="text"
					ucd="meta.title;obs"
					description="Publisher-assigned title of the data set"/>
				<param name="ra" type="double precision"
					ucd="pos.eq.ra"
					description="ROME RA of source object"/>
				<param name="dec" type="double precision"
					ucd="pos.eq.dec"
					description="ROME Dec of source object"/>
				<param name="field" type="smallint"
					ucd="meta.id;obs.field"
					description="Field observed."/>

				<column name="phot_err"
					unit="mag" ucd="stat.error;phot.mag;\ucd;meta.main"
					description="Error in the normalised magnitude (the phot column)"/>

				<column name="calibrated_mag"
					unit="mag" ucd="phot.mag;\ucd"
					description="Calibrated reference image magnitude"/>
				<column name="calibrated_mag_err"
					unit="mag" ucd="phot.mag;\ucd"
					description="Uncertainty in instrumental magnitude"/>

				<column name="image_link" type="text"
					ucd="meta.ref.url;obs.image"
					description="Link to JSON metadata of the image this
						photometry point was extracted from."/>
			</table>
		</events>
	</LOOP>

	<data id="build_ts">
		<embeddedGrammar isDispatching="True">
			<iterator>
				<setup imports="h5py, functools, numpy">
					<par key="column_labels">[
						# this is headings for the HDF5 fields.  Sigh!
						"hjd",
						"instrumental_mag",
						"instrumental_mag_err",
						"calibrated_mag",
						"calibrated_mag_err",
						"corrected_mag",
						"corrected_mag_err",
						"normalized_mag",
						"normalized_mag_err",
						"phot_scale_factor",
						"phot_scale_factor_err",
						"stamp_index",
						"sub_image_sky_bkgd",
						"sub_image_sky_bkgd_err",
						"residual_x",
						"residual_y",
						"qc_flag"]</par>
					<par name="table_code">{
							"SDSS g'": "g",
							"SDSS r'": "r",
							"SDSS i'": "i",}</par>
				</setup>
				<code>
					d = self.sourceToken
					for filter, rec, filename in rd.get_photpoints_with_bands(
							d["field"], d["quadrant"], d["quadrant_id"]):
						row = dict(zip(column_labels, rec))
						row["image_link"] = ("https://archive-api.lco.global"
							"/frames/?limit=1&amp;proposal_id=KEY2017AB-004&amp;basename_exact="
							+urllib.parse.quote(filename[:-5]))
						yield (table_code[filter], row)
				</code>
			</iterator>
		</embeddedGrammar>

		<STREAM id="make-ts">
			<doc>We're building three tables in parallel; this is the common
				mapping stuff.</doc>
			<parmaker>
				<apply procDef="//ssap#feedSSAToSDM"/>
				<map key="title">vars["parser_"].sourceToken["upstream_name"]</map>
			</parmaker>
			<rowmaker idmaps="*">
				<simplemaps>obs_time: hjd, phot: normalized_mag,
					phot_err: normalized_mag_err</simplemaps>
			</rowmaker>
		</STREAM>

		<make table="instance-i" role="i">
			<FEED source="make-ts"/>
		</make>
		<make table="instance-r" role="r">
			<FEED source="make-ts"/>
		</make>
		<make table="instance-g" role="g">
			<FEED source="make-ts"/>
		</make>
	</data>

	<service id="sdl" allowed="dlget,dlmeta,static">
		<meta name="title">ROME/REA Datalink Service</meta>
		<property name="staticData">data</property>

		<datalinkCore>
			<descriptorGenerator procDef="//soda#sdm_genDesc">
				<bind key="ssaTD">"\rdId#time_series"</bind>
				<bind key="contentQualifier">"#timeseries"</bind>
			</descriptorGenerator>
			<dataFunction procDef="//soda#sdm_genData">
				<bind key="builder">"\rdId#build_ts"</bind>
			</dataFunction>
			<dataFormatter>
				<code>
					from gavo.protocols import sdm
					return sdm.formatSDMData(descriptor.data,
						"application/x-votable+xml;serialization=TABLEDATA;version=1.6")
				</code>
			</dataFormatter>

			<metaMaker semantics="#progenitor"><code>
				field = descriptor.ssaRow["field"]
				quadrant = descriptor.ssaRow["quadrant"]

				yield descriptor.makeLinkFromFile(
					f"data/ROME-FIELD-{field:02d}_field_crossmatch.fits",
					description="FITS tables containing metadata for the observations"
						" of this field.")
				yield descriptor.makeLinkFromFile(
					f"data/ROME-FIELD-{field:02d}_quad{quadrant}_photometry.hdf5",
					contentType="application/x-hdf",
					description="An HDF5 file with photometry for this time series'"
						" quadrant of the field (you have to consult the documentation"
						" to understand this).")
			</code></metaMaker>

			<metaMaker semantics="#documentation"><code>
				yield descriptor.makeLink(
					"https://github.com/rachel3834/romerea_toolkit",
					contentType="text/html",
					description="Tools for handling the ROME/REA survey data.")
				yield descriptor.makeLink(
					"https://ui.adsabs.harvard.edu/link_gateway/2024PASP..136f4501S/PUB_PDF",
					contentType="application/pdf",
					description="The data release paper describing the upstream data"
						" products linked to here.")
			</code></metaMaker>
		</datalinkCore>
	</service>

	<service id="preview" allowed="qp">
		<meta name="title">ROME/REA preview maker</meta>
		<property name="queryField">rome_id</property>
		<pythonCore>
			<inputTable>
				<inputKey name="rome_id" type="text" required="True"
					description="ID in the form of field*1e7+quadrant*1e6+quadrant_id"/>
			</inputTable>
			<coreProc>
				<setup imports="io, h5py, gavo.svcs, numpy,
					gavo.helpers.processing, matplotlib.figure"/>
				<code>
					try:
						photpoints = rd.get_photpoints_with_bands(
							*rd.parse_rome_id(int(inputTable.args["rome_id"])))
					except Exception as exc:
						raise svcs.UnknownURI(f"No such preview ({exc})")

					series = {"SDSS r'": [], "SDSS i'": [], "SDSS g'": []}
					for filter, row, _ in photpoints:
						series[filter].append((row[0], row[7]))

					with processing.matplotlibLock():
						fig = figure.Figure(figsize=(4,2))
						ax = fig.add_axes([0,0,1,1], frameon=False)
						for band, color in [
								("SDSS i'", "red"),
								("SDSS r'", "green"),
								("SDSS g'", "blue")]:
							points = series[band]
							ax.plot(
								[p[0] for p in points],
								[p[1] for p in points],
								"o",
								color=color)

						rendered = io.BytesIO()
						fig.savefig(rendered, format="png", dpi=50)

					return ("image/png", rendered.getvalue())
				</code>
			</coreProc>
		</pythonCore>
	</service>

	<service id="web" allowed="form">
		<meta name="shortName">\schema Web</meta>
		<meta name="title">ROME/REA DR1 Web</meta>

		<dbCore queriedTable="time_series">
			<condDesc buildFrom="ssa_location"/>
			<condDesc buildFrom="mag_i_min"/>
			<condDesc buildFrom="mag_i_max"/>
			<condDesc buildFrom="upstream_name"/>
			<condDesc buildFrom="rome_id"/>
		</dbCore>

		<outputTable>
			<autoCols>accref, hjd_min, hjd_max, mag_i_min, mag_i_max, np_i,
				upstream_name</autoCols>
			<FEED source="//ssap#atomicCoords"/>
		<outputField name="datalink" type="text"
			ucd="meta.ref.url"
			select="'\getConfig{web}{serverURL}/\rdId/sdl/dlmeta?ID='
				|| gavo_urlescape(ssa_pubdid)"
			tablehead="DL"
			description="URL of a datalink document for this dataset."
			displayHint="type=url"/>
		</outputTable>
	</service>

	<service id="ssa" allowed="form,ssap.xml">
		<meta name="shortName">\schema SSAP</meta>
		<meta name="ssap.complianceLevel">full</meta>

		<publish render="ssap.xml" sets="ivo_managed"/>
		<publish render="form" sets="ivo_managed,local" service="web"/>

		<ssapCore queriedTable="time_series">
			<property key="previews">auto</property>
			<FEED source="//ssap#hcd_condDescs"/>
		</ssapCore>
	</service>

	<regSuite title="rome regression">
		<regTest title="rome SSAP serves some data">
			<url REQUEST="queryData"
				PUBDID="ivo://org.gavo.dc/~?rome/ts/11070003"
				>ssa/ssap.xml</url>
			<code>
				row = self.getFirstVOTableRow()
				self.assertAlmostEqual(
					row["location_ra"],
					267.5934298564758)
				self.assertEqual(row["rome_id"], 11070003)
				self.assertTrue(row["accref"].endswith("/getproduct/rome/ts/11070003"))
				# TODO: Test for datalink presence
			</code>
		</regTest>

		<regTest title="rome Datalink metadata looks about right.">
			<url ID="ivo://org.gavo.dc/~?rome/ts/11070003"
				>sdl/dlmeta</url>
			<code>
				by_sem = self.datalinkBySemantics()
				self.assertTrue(by_sem["#this"][0]["access_url"].endswith(
					"/getproduct/rome/ts/11070003"))
				self.assertEqual(
					by_sem["#this"][0]["content_qualifier"],
					"#timeseries")
				self.assertEqual(
					by_sem["#documentation"][0]["access_url"],
					"https://github.com/rachel3834/romerea_toolkit")
				self.assertEqual(
					by_sem["#progenitor"][0]["description"],
					"FITS tables containing metadata for the observations of this field.")
				self.assertEqual(
					by_sem["#progenitor"][1]["access_url"][-10:],
					"_photometry.hdf5"[-10:])
			</code>
		</regTest>

		<regTest title="rome delivers some data.">
			<url ID="ivo://org.gavo.dc/~?rome/ts/11070003"
				>sdl/dlget</url>
			<code><![CDATA[
				self.assertHasStrings(
					"ROME/REA time series in the SDSS i' band",
					"<TD>2458586.175836738</TD>", # first HJD
					"<TD>17.482026942113468</TD>", #first phot
					"basename_exact=coj1m011-fa12-20190412-0164-e91</TD>" #image name
				)
			]]></code>
		</regTest>

		<regTest title="rome has previews">
			<url httpHonorRedirects="True"
				>/getproduct/rome/ts/11070003?preview=true</url>
			<code>
				self.assertHasStrings("PNG", "Matplotlib v")
			</code>
		</regTest>

		<regTest title="rome web looks credible">
			<url parSet="form" pos_ssa_location="267.6673494 -30.1745036"
				sr_ssa_location="0.01"
				mag_i_min="15.7 .. 15.8">/rome/q/web/form</url>
			<code>
				self.assertHasStrings(
					"15.705",  #min mag or 01_287895
					"235", # N(i)
					"/q/sdl/dlmeta?ID=ivo://org.gavo.dc/~%3Frome/ts/11070002")
			</code>
		</regTest>
	</regSuite>
</resource>
