def test_jwst_x1d_single_reader_fail_on_multi(tmpdir, x1d_multi): """Make sure Spectrum1D.read on JWST x1d with many spectra errors out""" tmpfile = str(tmpdir.join('jwst.fits')) x1d_multi.writeto(tmpfile) with pytest.raises(IORegistryError): Spectrum1D.read(tmpfile)
def test_jwst_reader_warning_stddev(tmpdir, x1d_single): """Check that the reader raises warning when stddev is zeros""" tmpfile = str(tmpdir.join('jwst.fits')) hdulist = x1d_single # Put zeros in ERROR column hdulist["EXTRACT1D"].data["ERROR"] = 0 hdulist.writeto(tmpfile) with pytest.warns(Warning) as record: Spectrum1D.read(tmpfile) for r in record: if r.message is AstropyUserWarning: assert "Standard Deviation has values of 0" in r.message
def load_data(self, file_path, file_loader, display=False): """ Load spectral data given file path and loader. Parameters ---------- file_path : str Path to location of the spectrum file. file_loader : str Format specified for the astropy io interface. display : bool Automatically add the loaded spectral data to the plot. Returns ------- : :class:`~specviz.core.items.DataItem` The `DataItem` instance that has been added to the internal model. """ try: spec = Spectrum1D.read(file_path, format=file_loader) name = file_path.split('/')[-1].split('.')[0] data_item = self.model.add_data(spec, name=name) return data_item except: message_box = QMessageBox() message_box.setText("Error loading data set.") message_box.setIcon(QMessageBox.Critical) message_box.setInformativeText("{}\n{}".format( sys.exc_info()[0], sys.exc_info()[1])) message_box.exec()
def load_data(self, file_path, file_loader, display=False): """ Load spectral data given file path and loader. Parameters ---------- file_path : str Path to location of the spectrum file. file_loader : str Format specified for the astropy io interface. display : bool Automatically add the loaded spectral data to the plot. Returns ------- : :class:`~specviz.core.items.DataItem` The `DataItem` instance that has been added to the internal model. """ spec = Spectrum1D.read(file_path, format=file_loader) name = file_path.split('/')[-1].split('.')[0] data_item = self.model.add_data(spec, name=name) # print(self.proxy_model._items.keys()) # if display: # idx = data_item.index() # plot_item = self.proxy_model.item_from_index(idx) # plot_item.visible = True return data_item
def test_jwst_s2d_reader(tmpdir, s2d_single): path = str(tmpdir.join("test.fits")) model = s2d_single model.save(path) spec = Spectrum1D.read(path) assert hasattr(spec, "spectral_axis") assert spec.unit == u.dimensionless_unscaled
def test_jwst_x1d_single_reader(tmpdir, x1d_single): """Test Spectrum1D.read for JWST x1d data""" tmpfile = str(tmpdir.join('jwst.fits')) x1d_single.writeto(tmpfile) data = Spectrum1D.read(tmpfile, format='JWST x1d') assert type(data) is Spectrum1D assert data.shape == (100, )
def test_jwst_x1d_reader_meta(tmpdir, x1d_single): """Test that the Primary and EXTRACT1D extension headers are merged in meta""" tmpfile = str(tmpdir.join('jwst.fits')) x1d_single.writeto(tmpfile) data = Spectrum1D.read(tmpfile) assert ('TELESCOP', 'JWST') in data.meta.items() assert ('SRCTYPE', 'POINT') in data.meta.items()
def test_jwst_x1d_single_reader_no_format(tmpdir, x1d_single): """Test Spectrum1D.read for JWST x1d data without format arg""" tmpfile = str(tmpdir.join('jwst.fits')) x1d_single.writeto(tmpfile) data = Spectrum1D.read(tmpfile) assert type(data) is Spectrum1D assert data.shape == (100, ) assert data.unit == u.Jy assert data.spectral_axis.unit == u.um
def specviz_spectrum1d_parser(app, data, data_label=None, format=None, show_in_viewer=True): """ Loads a data file or `~specutils.Spectrum1D` object into SpecViz. Parameters ---------- data : str or `~specutils.Spectrum1D` Spectrum1D spectra, or path to compatible data file. data_label : str The Glue data label found in the ``DataCollection``. format : str Loader format specification used to indicate data format in `~specutils.Spectrum1D.read` io method. """ # If no data label is assigned, give it a unique identifier if not data_label: data_label = "specviz_data|" + str( base64.b85encode(uuid.uuid4().bytes), "utf-8") # If data provided is a path, try opening into a Spectrum1D object try: path = pathlib.Path(data) if path.is_file(): data = Spectrum1D.read(str(path), format=format) else: raise FileNotFoundError("No such file: " + str(path)) # If not, it must be a Spectrum1D object. Otherwise, it's unsupported except TypeError: if type(data) is SpectrumCollection: raise TypeError( "SpectrumCollection detected. Please provide a Spectrum1D") elif type(data) is not Spectrum1D: raise TypeError( "Data is not a Spectrum1D object or compatible file") # If there's already data in the viewer, convert units if needed current_spec = app.get_data_from_viewer("spectrum-viewer", data_label=data_label) if current_spec != {} and current_spec is not None: spec_key = list(current_spec.keys())[0] current_unit = current_spec[spec_key].spectral_axis.unit if data.spectral_axis.unit != current_unit: data = Spectrum1D( flux=data.flux, spectral_axis=data.spectral_axis.to(current_unit)) app.add_data(data, data_label) if show_in_viewer: app.add_data_to_viewer("spectrum-viewer", data_label)
def test_jwst_s3d_single(tmpdir, cube): """Test Spectrum1D.read for JWST x1d data""" tmpfile = str(tmpdir.join('jwst_s3d.fits')) cube.writeto(tmpfile) data = Spectrum1D.read(tmpfile, format='JWST s3d') assert type(data) is Spectrum1D assert data.shape == (10, 10, 30) assert data.uncertainty is not None assert data.mask is not None assert data.uncertainty.unit == 'MJy'
def test_jwst_srctpye_defaults(tmpdir, x1d_single, srctype): """ Test """ tmpfile = str(tmpdir.join('jwst.fits')) # Add a spectrum with missing or UNKNOWN SRCTYPE (mutate the fixture) x1d_single['EXTRACT1D'].header['SRCTYPE'] == srctype x1d_single.writeto(tmpfile) data = Spectrum1D.read(tmpfile, format='JWST x1d') assert type(data) is Spectrum1D assert data.shape == (100,) assert x1d_single['EXTRACT1D'].header['SRCTYPE'] == "POINT"
def mos_spec1d_parser(app, data_obj, data_labels=None): """ Attempts to parse a 1D spectrum object. Parameters ---------- app : `~jdaviz.app.Application` The application-level object used to reference the viewers. data_obj : str or list or spectrum-like File path, list, or spectrum-like object to be read as a new row in the mosviz table. data_labels : str, optional The label applied to the glue data component. """ # If providing a file path, parse it using the specutils io tooling if _check_is_file(data_obj): data_obj = [Spectrum1D.read(data_obj)] if isinstance(data_labels, str): data_labels = [data_labels] # Coerce into list-like object. This works because `Spectrum1D` objects # don't have a length dunder method. if not hasattr(data_obj, '__len__'): data_obj = [data_obj] else: data_obj = [ Spectrum1D.read(x) if _check_is_file(x) else x for x in data_obj ] if data_labels is None: data_labels = [f"1D Spectrum {i}" for i in range(len(data_obj))] elif len(data_obj) != len(data_labels): data_labels = [f"{data_labels[0]} {i}" for i in range(len(data_obj))] # Handle the case where the 1d spectrum is a collection of spectra for i in range(len(data_obj)): app.data_collection[data_labels[i]] = data_obj[i] _add_to_table(app, data_labels, '1D Spectra')
def test_loader_uses_priority(tmpdir): counter = Counter() fname = str(tmpdir.join('good.txt')) with open(fname, 'w') as ff: ff.write('\n') def identifier(origin, *args, **kwargs): fname = args[0] return 'good' in fname @data_loader("test_counting_loader1", identifier=identifier, priority=1) def counting_loader1(*args, **kwargs): counter["test1"] += 1 wave = np.arange(1, 1.1, 0.01) * u.AA return Spectrum1D( spectral_axis=wave, flux=np.ones(len(wave)) * 1.e-14 * u.Jy, ) @data_loader("test_counting_loader2", identifier=identifier, priority=2) def counting_loader2(*args, **kwargs): counter["test2"] += 1 wave = np.arange(1, 1.1, 0.01) * u.AA return Spectrum1D( spectral_axis=wave, flux=np.ones(len(wave)) * 1.e-14 * u.Jy, ) Spectrum1D.read(fname) assert counter["test2"] == 1 assert counter["test1"] == 0 for datatype in [Spectrum1D, SpectrumList]: registry.unregister_reader("test_counting_loader1", datatype) registry.unregister_identifier("test_counting_loader1", datatype) registry.unregister_reader("test_counting_loader2", datatype) registry.unregister_identifier("test_counting_loader2", datatype)
def test_export_data(specviz_gui, tmpdir): fname = str(tmpdir.join('export.ecsv')) workspace = specviz_gui._workspaces[0] data_item = workspace.current_item workspace.export_data_item(data_item, fname, '*.ecsv') assert os.path.isfile(fname) exported = Spectrum1D.read(fname, format='ECSV') original = data_item.data_item.spectrum assert_quantity_allclose(exported.flux, original.flux) assert_quantity_allclose(exported.spectral_axis, original.spectral_axis) if original.uncertainty is None: assert exported.uncertainty is None else: assert_quantity_allclose(exported.uncertainty, original.uncertainty)
def __init__(self, filename): self.s1d = Spectrum1D.read(filename) self.resolution = 200 f = fits.open(filename) self.data = f[0].data self.header = f[0].header f.close() #Serie de balmer self.Ha = 6562.10 self.Hb = 4861.32 self.Hg = 4340.46 self.Hd = 4101.73 # self.Mg = 5714 # Doublet du sodium self.NaI = 5889.950 self.NaII = 5895.924 # self.SiIIa = 6347.1 self.SiIIb = 6371.4
def load_data(self, file_path, file_loader, display=False): """ Load spectral data given file path and loader. Parameters ---------- file_path : str Path to location of the spectrum file. file_loader : str Format specified for the astropy io interface. display : bool Automatically add the loaded spectral data to the plot. Returns ------- : :class:`~specviz.core.items.DataItem` The `DataItem` instance that has been added to the internal model. """ try: spec = Spectrum1D.read(file_path, format=file_loader) name = file_path.split('/')[-1].split('.')[0] data_item = self.model.add_data(spec, name=name) # If there are any current plots, attempt to add the data to the # plot plot_data_item = self.proxy_model.item_from_id(data_item.identifier) plot_data_item.visible = True self.current_plot_window.plot_widget.on_item_changed(data_item) self._on_item_changed(item=plot_data_item.data_item) return data_item except: message_box = QMessageBox() message_box.setText("Error loading data set.") message_box.setIcon(QMessageBox.Critical) message_box.setInformativeText( "{}\n{}".format( sys.exc_info()[0], sys.exc_info()[1]) ) message_box.exec()
def _load_spectrum(self, data: Union[str, BinaryIO, HDUList] = None) -> None: """ Load the `~specutils.Spectrum1D` object Loads data into a Spectrum1D object, using ``Spectrum1D.read``. If no input data is specified, uses the data attached to the ``self.data`` attribute. Valid ``Spectrum1D.read`` inputs are a filename, an open file-like object, or an Astropy fits.HDUList. Parameters ---------- data : str | `~python.io.BinaryIO` |`~astropy.io.fits.HDUList`, optional The data object to be read by Spectrum1D, by default None """ data = data if data else self.data try: # check robustness of this to self.data/self.filename when specutils 1.1.1 is released self.spectrum = Spectrum1D.read(data, format=self.specutils_format) except IORegistryError: warnings.warn('Could not load Spectrum1D for format ' f'{self.specutils_format}, {self.filename}')
def mos_spec1d_parser(app, data_obj, data_labels=None): """ Attempts to parse a 1D spectrum object. Parameters ---------- app : `~jdaviz.app.Application` The application-level object used to reference the viewers. data_obj : str or list or spectrum-like File path, list, or spectrum-like object to be read as a new row in the mosviz table. data_labels : str, optional The label applied to the glue data component. """ if isinstance(data_labels, str): data_labels = [data_labels] # Coerce into list if needed if not isinstance(data_obj, (list, tuple, SpectrumCollection)): data_obj = [data_obj] data_obj = [Spectrum1D.read(x) if _check_is_file(x) else x for x in data_obj] if data_labels is None: data_labels = [f"1D Spectrum {i}" for i in range(len(data_obj))] elif len(data_obj) != len(data_labels): data_labels = [f"{data_labels[0]} {i}" for i in range(len(data_obj))] # Handle the case where the 1d spectrum is a collection of spectra with app.data_collection.delay_link_manager_update(): for i in range(len(data_obj)): app.add_data(data_obj[i], data_labels[i], notify_done=False) _add_to_table(app, data_labels, '1D Spectra')
def read_1Dfits(self): print(self.parent().fname) sp = fits.open(self.parent().fname) print(len(sp)) i = 0 self.parent().database[self.parent().fname]['header'] = sp[i].header try: try: print(self.parent().fname) s = Spectrum1D.read(self.parent().fname) print(s) except: print( 'Exception at Spectra.read_1Dfits, specutils not able to open spectrum.' ) self.parent( ).wavelength = s.spectral_axis #specdata['loglam'] * u.AA self.parent( ).flux = s.flux #specdata['flux'] *u.flx #* 10**-17 * u.Unit('erg cm-2 s-1 AA-1') self.writespec(s.spectral_axis, s.flux) except: print('specutils unable to read spectrum') self.parent().poplast = True
def load_data(self, data, data_label=None, format=None): """ Loads a data file or `~specutils.Spectrum1D` object into SpecViz. Parameters ---------- data : str or `~specutils.Spectrum1D` Spectrum1D spectra, or path to compatible data file. data_label : str The Glue data label found in the ``DataCollection``. format : str Loader format specification used to indicate data format in `~specutils.Spectrum1D.read` io method. """ # If no data label is assigned, give it a unique identifier if data_label is None: data_label = "specviz_data|" + uuid.uuid4().hex # If data provided is a path, try opening into a Spectrum1D object try: path = pathlib.Path(data) if path.is_file(): data = Spectrum1D.read(path, format=format) else: raise FileNotFoundError("No such file: " + path) # If not, it must be a Spectrum1D object. Otherwise, it's unsupported except TypeError: if type(data) is SpectrumCollection: raise TypeError("`SpectrumCollection` detected. Please " "provide a `Spectrum1D`.") elif type(data) is not Spectrum1D: raise TypeError("Data is not a Spectrum1D object or compatible file") self.app.add_data(data, data_label) self.app.add_data_to_viewer('spectrum-viewer', data_label)
def gama_6dfgs_obscore_loader(fname): spec = Spectrum1D.read(fname, format="6dFGS-split") obscore = {} hdr = spec.meta["header"] # no date or time info in 6dF spectra # (t1,t2) = GetTimes_GAMA_GAMA(hdr) # if(t1 != None): # obscore['t_min'] = t1.to_value('mjd',subfmt='float') # if(t2 != None): # obscore['t_max'] = t2.to_value('mjd',subfmt='float') obscore["s_ra"] = hdr["RA"] obscore["s_dec"] = hdr["DEC"] obscore["s_fov"] = 6.7 / 3600 # obscore["obs_collection"] = "gama_dr2" # obscore["facility_name"] = "6dfgs" obscore["dataproduct_subtype"] = "science" obscore["calib_level"] = 2 # No exposure time # if('T_EXP' in hdr): # obscore['t_exptime'] = hdr['T_EXP'] # exposure times are 3x10 min (red arm) and 3x20 min (visual arm); Jones et # al. 2004 obscore["t_exptime"] = 3600 nspecpix = len(spec.spectral_axis) obscore["em_xel"] = nspecpix obscore["em_ucd"] = "em.wl" obscore["em_unit"] = "angstrom" obscore["s_xel1"] = nspecpix obscore["s_xel2"] = 1 obscore["t_xel"] = 1 obscore["em_min"] = spec.spectral_axis[0].meter obscore["em_max"] = spec.spectral_axis[nspecpix - 1].meter obscore["em_res_power"] = 1000 # obscore['em_res_power_min'] = 1050 # obscore['em_res_power_max'] = 1681 obscore["em_resolution"] = 8.5 * 1e-10 obscore["o_ucd"] = "phot.count" # spectra are calibrated in flux: ROW1 hdr comment: Flux-calibrated # spectrum in 10^-17 erg/s/cm^2/A # obscore['o_ucd'] = 'phot.flux' # obscore['o_unit'] = '1.0E-17 erg/s/cm^2/A' # or perhaps 'relative' since these are fibre spectra? # obscore['o_calib_status'] = 'absolute' obscore["instrument_name"] = "6dF" obscore["em_calib_status"] = "calibrated" if "GAMANAME" in hdr: obscore["target_name"] = hdr["GAMANAME"] if "TARGET" in hdr: obscore["alt_target_name"] = hdr["TARGET"] # if('WAVRESOL' in hdr): # obscore['em_resolution'] = hdr['WAVRESOL']*1e-10 # if('SN' in hdr): obscore["em_snr"] = 10 # alternative name: OBJECT if "SPECID" in hdr: obscore["obs_id"] = hdr["SPECID"] if "Z" in hdr: obscore["redshift"] = hdr["Z"] spec.meta["obscore"] = obscore return spec
def obscore_loader_gama_sdss(fname): """ This is a specutils loader which adds obscore information to a SDSS spectra in the GAMA survey """ spec = Spectrum1D.read(fname, format="SDSS-I/II spSpec") spec.meta["obscore"] = {} obscore = spec.meta["obscore"] hdr = spec.meta["header"] obscore["t_min"] = gama_sdss_time_to_mjd(hdr.get("TAI-BEG")).to_value('mjd',subfmt='float') obscore["t_max"] = gama_sdss_time_to_mjd(hdr.get("TAI-END")).to_value('mjd',subfmt='float') obscore["s_ra"] = hdr["RA"] obscore["s_dec"] = hdr["DEC"] obscore["s_seeing"] = hdr.get("SEEING50") # obscore["obs_collection"] = "gama_dr2" # obscore["facility_name"] = "sdss" obscore["dataproduct_subtype"] = "science" obscore["calib_level"] = 2 obscore["t_exptime"] = hdr.get("EXPTIME") nspecpix = len(spec.spectral_axis) obscore["em_xel"] = nspecpix obscore["em_ucd"] = "em.wl" obscore["em_unit"] = spec.spectral_axis.unit obscore["s_xel1"] = nspecpix obscore["s_xel2"] = 1 obscore["t_xel"] = 1 obscore["em_min"] = spec.spectral_axis[0].meter obscore["em_max"] = spec.spectral_axis[-1].meter obscore["em_res_power"] = 2000 # http://classic.sdss.org/dr7/instruments/spectrographs/index.html obscore["em_res_power_min"] = 1850 obscore["em_res_power_max"] = 2200 # lambda_cen=(3800+9200)/2=6500 A; Delta Lambda = 6500/2000 = 3.25 obscore["em_resolution"] = 3.25 * 1e-10 # obscore['o_ucd'] = 'phot.count' # spectra are calibrated in flux: # BUNIT = '1.0E-17 erg/cm/s/Ang' / units obscore["o_ucd"] = "phot.flux" obscore["o_unit"] = "1.0E-17 erg/s/cm^2/A" obscore["o_calib_status"] = "absolute" # or perhaps 'relative' since these are fibre spectra? obscore["instrument_name"] = "SDSS" # 3 arcsec diameter fibres obscore["s_fov"] = 3.0 / 3600 obscore["em_calib_status"] = "calibrated" obscore["target_name"] = hdr.get("GAMANAME") # if('OBJECT' in hdr): # obscore['alt_target_name'] = hdr['OBJECT'] # if('WAVRESOL' in hdr): # obscore['em_resolution'] = hdr['WAVRESOL']*1e-10 # SN_G and SN_I also available, choosing R-band as good compromise/average obscore["em_snr"] = hdr.get("SN_R") # alternative name: OBJECT obscore["obs_id"] = hdr.get("SPECID") obscore["redshift"] = hdr.get("Z") return spec
def specviz_spectrum1d_parser(app, data, data_label=None, format=None, show_in_viewer=True): """ Loads a data file or `~specutils.Spectrum1D` object into Specviz. Parameters ---------- data : str, `~specutils.Spectrum1D`, or `~specutils.SpectrumList` Spectrum1D, SpectrumList, or path to compatible data file. data_label : str The Glue data label found in the ``DataCollection``. format : str Loader format specification used to indicate data format in `~specutils.Spectrum1D.read` io method. """ # If no data label is assigned, give it a unique identifier if not data_label: data_label = "specviz_data|" + str( base64.b85encode(uuid.uuid4().bytes), "utf-8") if isinstance(data, SpectrumCollection): raise TypeError("SpectrumCollection detected." " Please provide a Spectrum1D or SpectrumList") elif isinstance(data, Spectrum1D): data = [data] data_label = [data_label] elif isinstance(data, SpectrumList): pass else: path = pathlib.Path(data) if path.is_file(): try: data = [Spectrum1D.read(str(path), format=format)] data_label = [data_label] except IORegistryError: # Multi-extension files may throw a registry error data = SpectrumList.read(str(path), format=format) else: raise FileNotFoundError("No such file: " + str(path)) if isinstance(data, SpectrumList): if not isinstance(data_label, (list, tuple)): temp_labels = [] for i in range(len(data)): temp_labels.append(f"{data_label} {i}") data_label = temp_labels elif len(data_label) != len(data): raise ValueError( f"Length of data labels list ({len(data_label)}) is different" f" than length of list of data ({len(data)})") # If there's already data in the viewer, convert units if needed current_unit = None current_spec = app.get_data_from_viewer("spectrum-viewer") if current_spec != {} and current_spec is not None: spec_key = list(current_spec.keys())[0] current_unit = current_spec[spec_key].spectral_axis.unit with app.data_collection.delay_link_manager_update(): for i in range(len(data)): spec = data[i] if current_unit is not None and spec.spectral_axis.unit != current_unit: spec = Spectrum1D( flux=spec.flux, spectral_axis=spec.spectral_axis.to(current_unit)) app.add_data(spec, data_label[i]) # Only auto-show the first spectrum in a list if i == 0 and show_in_viewer: app.add_data_to_viewer("spectrum-viewer", data_label[i])
def mos_spec2d_parser(app, data_obj, data_labels=None, add_to_table=True, show_in_viewer=False): """ Attempts to parse a 2D spectrum object. Notes ----- This currently only works with JWST-type data in which the data is in the second hdu of the fits file. Parameters ---------- app : `~jdaviz.app.Application` The application-level object used to reference the viewers. data_obj : str or list or spectrum-like File path, list, or spectrum-like object to be read as a new row in the mosviz table. data_labels : str, optional The label applied to the glue data component. """ def _parse_as_spectrum1d(path): # Parse as a FITS file and assume the WCS is correct with fits.open(path) as hdulist: data = hdulist[1].data header = hdulist[1].header wcs = WCS(header) return Spectrum1D(data, wcs=wcs) # Coerce into list-like object if not isinstance(data_obj, (list, tuple, SpectrumCollection)): data_obj = [data_obj] # If we're given a string, repeat it for each object if isinstance(data_labels, str): if len(data_obj) > 1: data_labels = [f"{data_labels} {i}" for i in range(len(data_obj))] else: data_labels = [data_labels] elif data_labels is None: if len(data_obj) > 1: data_labels = [f"2D Spectrum {i}" for i in range(len(data_obj))] else: data_labels = ['2D Spectrum'] with app.data_collection.delay_link_manager_update(): for index, data in enumerate(data_obj): # If we got a filepath, first try and parse using the Spectrum1D and # SpectrumList parsers, and then fall back to parsing it as a generic # FITS file. if _check_is_file(data): try: data = Spectrum1D.read(data) except IORegistryError: try: data = Spectrum1D.read(data) except IORegistryError: data = _parse_as_spectrum1d(data) # Copy (if present) region to top-level meta object if ('header' in data.meta and 'S_REGION' in data.meta['header'] and 'S_REGION' not in data.meta): data.meta['S_REGION'] = data.meta['header']['S_REGION'] # Set the instrument # TODO: this should not be set to nirspec for all datasets data.meta['INSTRUME'] = 'nirspec' # Get the corresponding label for this data product label = data_labels[index] app.data_collection[label] = data if add_to_table: _add_to_table(app, data_labels, '2D Spectra') if show_in_viewer: if len(data_labels) > 1: raise ValueError("More than one data label provided, unclear " + "which to show in viewer") app.add_data_to_viewer("spectrum-2d-viewer", data_labels[0])
def get_spectral_template(vis,x): return Spectrum1D.read("SDSS DR2 Spectral Templates/" + vis[x][1][0] + ".fit", format = "SDSS-I/II spSpec")