diff --git a/fix_fits_header.pro b/fix_fits_header.pro new file mode 100644 index 0000000000000000000000000000000000000000..60503b4788d5eba08cfe40f716f822c48c3642a2 --- /dev/null +++ b/fix_fits_header.pro @@ -0,0 +1,12 @@ +pro fix_fits_header, filename + fits_info, filename, n_ext = n_ext, /silent + for ext = 0, n_ext do begin + data = readfits(filename, header, exten_no = ext, /silent) + for k = 0, n_elements(header) - 1 do begin + while header[k].matches('\/ +') do begin + header[k] = header[k].replace('/ ', '/ ') + endwhile + endfor + modfits, filename, 0, header, exten_no = ext + endfor +end \ No newline at end of file diff --git a/metis_l2_prep_uv.pro b/metis_l2_prep_uv.pro index 387bb4de994bc9ce61bfc35402fb789812ddc69f..40b1a9b66fc0391fb3ef7f6d9ede11184cf6560c 100644 --- a/metis_l2_prep_uv.pro +++ b/metis_l2_prep_uv.pro @@ -1,275 +1,280 @@ pro metis_l2_prep_uv + ; keyword defining if the detector reference frame must be used for the output - ; keyword defining if the detector reference frame must be used for the output + ref_detector = 0 - ref_detector = 0 + ; start the log - ; start the log + journal, 'output/metis_l2_prep_log.txt' - journal,'output/metis_l2_prep_log.txt' + ; read the auxiliary file - here we have all the inputs we need - ; read the auxiliary file - here we have all the inputs we need + input = json_parse('input/contents.json', /toarray, /tostruct) - input = json_parse('input/contents.json', /toarray, /tostruct) + journal, 'File ' + file_basename(input.file_name) - journal, 'File ' + file_basename(input.file_name) + ; load the spice kernels - ; load the spice kernels + load_spice_kernels, input.spice_kernels, kernel_list = kernel_list, kernel_version = kernel_version - load_spice_kernels, input.spice_kernels, kernel_list = kernel_list, kernel_version = kernel_version + journal, 'SPICE kernel files correctly loaded:' + journal, ' SDK version= ' + kernel_version - journal, 'SPICE kernel files correctly loaded:' - journal, ' SDK version= ' + kernel_version + ; read the calibration package index - ; read the calibration package index + cal_pack = json_parse(input.cal_pack_path + '/index.json', /toarray, /tostruct) - cal_pack = json_parse(input.cal_pack_path + '/index.json', /toarray, /tostruct) + ; include the calibration package path into the cal_pack structure - ; include the calibration package path into the cal_pack structure + cal_pack = create_struct('path', input.cal_pack_path + path_sep(), cal_pack) - cal_pack = create_struct('path', input.cal_pack_path + path_sep(), cal_pack) + journal, 'Calibration package correctly imported:' + journal, ' version = ' + cal_pack.version + journal, ' validity range = ' + string(cal_pack.validity_range.start, cal_pack.validity_range._end, format = '(A, "-", A)') - journal, 'Calibration package correctly imported:' - journal, ' version = ' + cal_pack.version - journal, ' validity range = ' + string(cal_pack.validity_range.start, cal_pack.validity_range._end, format = '(A, "-", A)') + ; read the primary hdu - ; read the primary hdu + data = mrdfits(input.file_name, 0, primary_header, /silent) - data = mrdfits(input.file_name, 0, primary_header, /silent) + ; read the quality matrix - ; read the quality matrix + quality_matrix = mrdfits(input.file_name, 'quality matrix', /silent) - quality_matrix = mrdfits(input.file_name, 'quality matrix', /silent) + ; convert the string header into an idl structure - ; convert the string header into an idl structure + header = fits_hdr2struct(primary_header) - header = fits_hdr2struct(primary_header) + journal, 'L1 FITS file correctly read:' + journal, ' datatype = ' + string(header.datatype, format = '(I0)') + journal, ' sess_num = ' + header.sess_num + journal, ' nbin = ' + string(sqrt(header.nbin), format = '(I0)') - journal, 'L1 FITS file correctly read:' - journal, ' datatype = ' + string(header.datatype, format = '(I0)') - journal, ' sess_num = ' + header.sess_num - journal, ' nbin = ' + string(sqrt(header.nbin), format = '(I0)') + ; error is the quadratic relative error - ; error is the quadratic relative error - - error = 0. + error = 0. - ; calibration block + ; calibration block - case header.datatype of - 1 : begin - data = double(data) + case header.datatype of + 1: begin + data = double(data) - ; ==================================== - ; for old l1 data - ; data = data * header.nbin * header.ndit1 * header.ndit2 - ; header.xposure = header.dit/1000. * header.ndit1 * header.ndit2 - ; header.nsumexp = header.ndit1 * header.ndit2 - ; ==================================== + ; ==================================== + ; for old l1 data + ; data = data * header.nbin * header.ndit1 * header.ndit2 + ; header.xposure = header.dit/1000. * header.ndit1 * header.ndit2 + ; header.nsumexp = header.ndit1 * header.ndit2 + ; ==================================== - if fix(header.hdr_vers) le 4 then begin - data = metis_dark_uvda(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) - endif else begin - data = metis_dark_uvda_new(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) - endelse + if fix(header.hdr_vers) le 4 then begin + data = metis_dark_uvda(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) + endif else begin + data = metis_dark_uvda_new(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) + endelse - ; ==================================== - ; for data already subtracted of dark - ; file = file_basename(header.parent, '.fits') + '_subdark.fits' - ; data = mrdfits('UV subdark/' + file, 0, /silent) - ; data = rebin(data, header.naxis1, header.naxis2) - ; data = data * header.nbin * header.ndit1 * header.ndit2 - ; history = ['Dark correction: ', ' ' + file] - ; ==================================== + ; ==================================== + ; for data already subtracted of dark + ; file = file_basename(header.parent, '.fits') + '_subdark.fits' + ; data = mrdfits('UV subdark/' + file, 0, /silent) + ; data = rebin(data, header.naxis1, header.naxis2) + ; data = data * header.nbin * header.ndit1 * header.ndit2 + ; history = ['Dark correction: ', ' ' + file] + ; ==================================== - data = metis_flat_field(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) - data = metis_vignetting(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) - data = metis_rad_cal(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) + data = metis_flat_field(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) + data = metis_vignetting(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) + data = metis_rad_cal(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) - ; ==================================== - ; for simple radiometric calibration - ; cal_pack.uv_channel.cal_units = 'DN/s' - ; ==================================== + ; ==================================== + ; for simple radiometric calibration + ; cal_pack.uv_channel.cal_units = 'DN/s' + ; ==================================== - ; ==================================== - ; temporary correction based on alfa-leo data - ; data = data * 3.4 - ; history = [history, ' additional cal. factor from stars = 3.4'] - ; ==================================== - - btype = 'UV Lyman-alpha intensity' - bunit = cal_pack.uv_channel.cal_units - end + ; ==================================== + ; temporary correction based on alfa-leo data + ; data = data * 3.4 + ; history = [history, ' additional cal. factor from stars = 3.4'] + ; ==================================== - 4 : begin - ; calibration of temporal noise images - btype = 'UV temporal standard deviation' - bunit = 'DN' - history = !null - end + btype = 'UV Lyman-alpha intensity' + bunit = cal_pack.uv_channel.cal_units + end - 6 : begin - ; calibration of cr/sep log matrices - btype = 'UV cosmic-ray matrix' - bunit = 'DN' - history = !null - end - - else : begin - journal, 'Error 01: wrong input data product (expected data types 1, 4, or 6).' - journal - exit, status = 1 - end - endcase - - ; definitions for the primary header - ; version of the fits file - - version = string(input.l2_version, format = '(I02)') - - ; fits creation date - - date = date_conv(systime(/julian, /utc), 'FITS') - - ; name of the fits file - - file_name_fields = strsplit(header.filename, '_', /extract) - file_name = 'solo_L2_' + file_name_fields[2] + '_' + file_name_fields[3] + '_V' + version + '.fits' - out_file_name = 'output/' + file_name - - ; adjust the primary header - - fxaddpar, primary_header, 'FILENAME', file_name - fxaddpar, primary_header, 'PARENT', file_basename(input.file_name) - fxaddpar, primary_header, 'LEVEL', 'L2' - fxaddpar, primary_header, 'CREATOR', 'metis_l2_prep_uv.pro' - fxaddpar, primary_header, 'VERS_SW', input.sw_version - fxaddpar, primary_header, 'VERS_CAL', cal_pack.version - fxaddpar, primary_header, 'VERSION', version - fxaddpar, primary_header, 'BTYPE', btype - fxaddpar, primary_header, 'BUNIT', bunit - fxaddpar, primary_header, 'DATAMIN', min(data, /nan) - fxaddpar, primary_header, 'DATAMAX', max(data, /nan) - fxaddpar, primary_header, 'WAVEBAND', cal_pack.uv_channel.name - fxaddpar, primary_header, 'XPOSURE', header.xposure - fxaddpar, primary_header, 'NSUMEXP', header.nsumexp - - sxdelpar, primary_header, 'BLANK' - - ; fix planning info keywords - - if header.soopname.startswith('unknown') then soopname = 'none' else soopname = header.soopname - if header.obs_mode.startswith('unknown') then obs_mode = 'none' else obs_mode = header.obs_mode - if soopname eq 'none' then sooptype = 'none' else sooptype = header.sooptype - if obs_mode eq 'none' then obs_type = 'none' else obs_type = header.obs_type - if soopname eq 'none' and obs_mode eq 'none' then obs_id = 'none' else obs_id = header.obs_id - if obs_mode eq 'none' then obs_mode = 'METIS_GENERIC_OBS' - - fxaddpar, primary_header, 'SOOPNAME', soopname - fxaddpar, primary_header, 'SOOPTYPE', sooptype - fxaddpar, primary_header, 'OBS_MODE', obs_mode - fxaddpar, primary_header, 'OBS_TYPE', obs_type - fxaddpar, primary_header, 'OBS_ID', obs_id - - ; append wcs keywords - - wcs = metis_wcs(header, cal_pack, ref_detector = ref_detector) - foreach element, wcs do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' - - ; append solar ephemeris keywords - - ephemeris = solo_get_ephemeris(header, cal_pack) - foreach element, ephemeris do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' - - history = [history, 'Update WCS and solar ephemeris:', ' SKD version = ' + kernel_version] - - ; update the comment and history keywords - - fxaddpar, primary_header, 'COMMENT', 'WARNING: UV radiometric calibration is still preliminary.' - fxaddpar, primary_header, 'COMMENT', 'Uncertainty matrix in the FITS extension is preliminary.' - - if ref_detector then begin - fxaddpar, primary_header, 'COMMENT', 'Flip vertically and rotate CROTA degrees counter-clockwise' - fxaddpar, primary_header, 'COMMENT', ' to have Solar North up.' - endif else $ - fxaddpar, primary_header, 'COMMENT', 'Rotate CROTA degrees counter-clockwise to have Solar North up.' - - for k = 0, n_elements(history) - 1 do $ - fxaddpar, primary_header, 'HISTORY', history[k] - fxaddpar, primary_header, 'HISTORY', 'L2 FITS file created on ' + date - - if not ref_detector then data = metis_rectify(data, 'UV') - fits_add_checksum, primary_header, data - mwrfits, float(data), out_file_name, primary_header, /no_comment, /create, /silent - - journal, 'Fits file created:' - journal, ' file name = ' + file_basename(out_file_name) - - ; add the extension with the quality matrix - - base_header = primary_header - sxdelpar, base_header, 'SIMPLE' - sxdelpar, base_header, 'EXTEND' - sxdelpar, base_header, 'DATASUM' - sxdelpar, base_header, 'CHECKSUM' - sxdelpar, base_header, 'COMMENT' - sxdelpar, base_header, 'HISTORY' - - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Pixel quality' - fxaddpar, extension_header, 'BUNIT', 'None' - fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) - fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' - fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' - fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' - fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' - if not ref_detector then quality_matrix = metis_rectify(quality_matrix, 'UV') - fits_add_checksum, extension_header, quality_matrix - mwrfits, float(quality_matrix), out_file_name, extension_header, /no_comment, /silent - - journal, 'Quality-matrix extension correctly added.' - - ; add the extension with the error matrix - - if not ref_detector then error = metis_rectify(error, 'UV') - error_matrix = data * sqrt(error) - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Absolute error' - fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) - - fits_add_checksum, extension_header, float(error_matrix) - mwrfits, float(error_matrix), out_file_name, extension_header, /no_comment, /silent - - journal, 'Error-matrix extension correctly added.' - - ; write the auxiliary information file - - output = { $ - file_name: out_file_name, $ - l1_file_name: input.file_name, $ - log_file_name: 'output/metis_l2_prep_log.txt' $ - } - - json_write, output, 'output/contents.json' - - ; unload the spice kernels - - load_spice_kernels, kernel_list = kernel_list, /unload - - journal, 'SPICE kernel files unloaded.' - - ; close the log - - journal, 'Exiting without errors.' - journal - - exit, status = 0 -end + 4: begin + ; calibration of temporal noise images + btype = 'UV temporal standard deviation' + bunit = 'DN' + history = !null + end + + 6: begin + ; calibration of cr/sep log matrices + btype = 'UV cosmic-ray matrix' + bunit = 'DN' + history = !null + end + + else: begin + journal, 'Error 01: wrong input data product (expected data types 1, 4, or 6).' + journal + exit, status = 1 + end + endcase + + ; definitions for the primary header + ; version of the fits file + + version = string(input.l2_version, format = '(I02)') + + ; fits creation date + + date = date_conv(systime(/julian, /utc), 'FITS') + + ; name of the fits file + + file_name_fields = strsplit(header.filename, '_', /extract) + file_name = 'solo_L2_' + file_name_fields[2] + '_' + file_name_fields[3] + '_V' + version + '.fits' + out_file_name = 'output/' + file_name + + ; adjust the primary header + + fxaddpar, primary_header, 'FILENAME', file_name + fxaddpar, primary_header, 'PARENT', file_basename(input.file_name) + fxaddpar, primary_header, 'LEVEL', 'L2' + fxaddpar, primary_header, 'CREATOR', 'metis_l2_prep_uv.pro' + fxaddpar, primary_header, 'VERS_SW', input.sw_version + fxaddpar, primary_header, 'VERS_CAL', cal_pack.version + fxaddpar, primary_header, 'VERSION', version + fxaddpar, primary_header, 'BTYPE', btype + fxaddpar, primary_header, 'BUNIT', bunit + fxaddpar, primary_header, 'DATAMIN', min(data, /nan) + fxaddpar, primary_header, 'DATAMAX', max(data, /nan) + fxaddpar, primary_header, 'WAVEBAND', cal_pack.uv_channel.name + fxaddpar, primary_header, 'XPOSURE', header.xposure + fxaddpar, primary_header, 'NSUMEXP', header.nsumexp + + sxdelpar, primary_header, 'BLANK' + + ; fix planning info keywords + + if header.soopname.startswith('unknown') then soopname = 'none' else soopname = header.soopname + if header.obs_mode.startswith('unknown') then obs_mode = 'none' else obs_mode = header.obs_mode + if soopname eq 'none' then sooptype = 'none' else sooptype = header.sooptype + if obs_mode eq 'none' then obs_type = 'none' else obs_type = header.obs_type + if soopname eq 'none' and obs_mode eq 'none' then obs_id = 'none' else obs_id = header.obs_id + if obs_mode eq 'none' then obs_mode = 'METIS_GENERIC_OBS' + + fxaddpar, primary_header, 'SOOPNAME', soopname + fxaddpar, primary_header, 'SOOPTYPE', sooptype + fxaddpar, primary_header, 'OBS_MODE', obs_mode + fxaddpar, primary_header, 'OBS_TYPE', obs_type + fxaddpar, primary_header, 'OBS_ID', obs_id + + ; append wcs keywords + + wcs = metis_wcs(header, cal_pack, ref_detector = ref_detector) + foreach element, wcs do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' + + ; append solar ephemeris keywords + + ephemeris = solo_get_ephemeris(header, cal_pack) + foreach element, ephemeris do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' + + history = [history, 'Update WCS and solar ephemeris:', ' SKD version = ' + kernel_version] + + ; update the comment and history keywords + + fxaddpar, primary_header, 'COMMENT', 'WARNING: UV radiometric calibration is still preliminary.' + fxaddpar, primary_header, 'COMMENT', 'Uncertainty matrix in the FITS extension is preliminary.' + + if ref_detector then begin + fxaddpar, primary_header, 'COMMENT', 'Flip vertically and rotate CROTA degrees counter-clockwise' + fxaddpar, primary_header, 'COMMENT', ' to have Solar North up.' + endif else $ + fxaddpar, primary_header, 'COMMENT', 'Rotate CROTA degrees counter-clockwise to have Solar North up.' + + for k = 0, n_elements(history) - 1 do $ + fxaddpar, primary_header, 'HISTORY', history[k] + fxaddpar, primary_header, 'HISTORY', 'L2 FITS file created on ' + date + + if not ref_detector then data = metis_rectify(data, 'UV') + fits_add_checksum, primary_header, float(data) + mwrfits, float(data), out_file_name, primary_header, /no_comment, /create, /silent + + journal, 'Fits file created:' + journal, ' file name = ' + file_basename(out_file_name) + + ; add the extension with the quality matrix + + base_header = primary_header + sxdelpar, base_header, 'SIMPLE' + sxdelpar, base_header, 'EXTEND' + sxdelpar, base_header, 'DATASUM' + sxdelpar, base_header, 'CHECKSUM' + sxdelpar, base_header, 'COMMENT' + sxdelpar, base_header, 'HISTORY' + + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Pixel quality' + fxaddpar, extension_header, 'BUNIT', 'None' + fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) + fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' + fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' + fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' + fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' + if not ref_detector then quality_matrix = metis_rectify(quality_matrix, 'UV') + fits_add_checksum, extension_header, float(quality_matrix) + mwrfits, float(quality_matrix), out_file_name, extension_header, /no_comment, /silent + + journal, 'Quality-matrix extension correctly added.' + + ; add the extension with the error matrix + + if not ref_detector then error = metis_rectify(error, 'UV') + error_matrix = data * sqrt(error) + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Absolute error' + fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) + + fits_add_checksum, extension_header, float(error_matrix) + mwrfits, float(error_matrix), out_file_name, extension_header, /no_comment, /silent + + journal, 'Error-matrix extension correctly added.' + + ; fix some header keywords + + fix_fits_header, out_file_name + + ; write the auxiliary information file + + output = { $ + file_name: out_file_name, $ + l1_file_name: input.file_name, $ + log_file_name: 'output/metis_l2_prep_log.txt' $ + } + + json_write, output, 'output/contents.json' + + ; unload the spice kernels + + load_spice_kernels, kernel_list = kernel_list, /unload + + journal, 'SPICE kernel files unloaded.' + + ; close the log + + journal, 'Exiting without errors.' + journal + + exit, status = 0 +end \ No newline at end of file diff --git a/metis_l2_prep_vl_generic.pro b/metis_l2_prep_vl_generic.pro index 6e983f2cfb745b3fff9d115fa0fc4ccdf08bf80b..70c9f5a8107180e46fa64d83b5f4030f8e833b68 100644 --- a/metis_l2_prep_vl_generic.pro +++ b/metis_l2_prep_vl_generic.pro @@ -1,311 +1,316 @@ pro metis_l2_prep_vl_generic + ; keyword defining if the detector reference frame must be used for the output - ; keyword defining if the detector reference frame must be used for the output + ref_detector = 0 - ref_detector = 0 + ; start the log - ; start the log + journal, 'output/metis_l2_prep_log.txt' - journal,'output/metis_l2_prep_log.txt' + ; read the auxiliary file - here we have all the inputs we need - ; read the auxiliary file - here we have all the inputs we need + input = json_parse('input/contents.json', /toarray, /tostruct) - input = json_parse('input/contents.json', /toarray, /tostruct) + ; load the spice kernels - ; load the spice kernels + load_spice_kernels, input.spice_kernels, kernel_list = kernel_list, kernel_version = kernel_version - load_spice_kernels, input.spice_kernels, kernel_list = kernel_list, kernel_version = kernel_version + journal, 'SPICE kernel files correctly loaded:' + journal, ' SDK version= ' + kernel_version - journal, 'SPICE kernel files correctly loaded:' - journal, ' SDK version= ' + kernel_version + ; read the calibration package - ; read the calibration package + cal_pack = json_parse(input.cal_pack_path + '/index.json', /toarray, /tostruct) - cal_pack = json_parse(input.cal_pack_path + '/index.json', /toarray, /tostruct) + ; include the calibration package path into the cal_pack structure - ; include the calibration package path into the cal_pack structure + cal_pack = create_struct('path', input.cal_pack_path + path_sep(), cal_pack) - cal_pack = create_struct('path', input.cal_pack_path + path_sep(), cal_pack) - - journal, 'Calibration package correctly imported:' - journal, ' version = ' + cal_pack.version - journal, ' validity range = ' + string(cal_pack.validity_range.start, cal_pack.validity_range._end, format = '(A, "-", A)') - - ; read the primary hdu - - data = mrdfits(input.file_name, 0, primary_header, /silent) - - ; patch to fix the lack of the keyword SEQ_NUM - - pol_id = fxpar(primary_header, 'POL_ID', missing = 0) - if pol_id ge 1 and pol_id le 4 then begin - seq_num = fxpar(primary_header, 'SEQ_NUM', missing = 0) - if seq_num eq 0 then begin - obj_cnt = fxpar(primary_header, 'OBJ_CNT') - n_pol = fxpar(primary_header, 'N_POL', missing = 4) - seq_num = ((obj_cnt - 1)/n_pol + 1) - fxaddpar, primary_header, 'SEQ_NUM', seq_num, before = 'POL_ID' - endif - endif - - ; read the quality matrix + journal, 'Calibration package correctly imported:' + journal, ' version = ' + cal_pack.version + journal, ' validity range = ' + string(cal_pack.validity_range.start, cal_pack.validity_range._end, format = '(A, "-", A)') - quality_matrix = mrdfits(input.file_name, 'quality matrix', /silent) + ; read the primary hdu - ; convert the string header into an idl structure + data = mrdfits(input.file_name, 0, primary_header, /silent) - header = fits_hdr2struct(primary_header) + ; patch to fix the lack of the keyword SEQ_NUM - journal, 'L1 FITS file correctly read:' - journal, ' filename = ' + file_basename(input.file_name) - journal, ' datatype = ' + string(header.datatype, format = '(I0)') - journal, ' sess_num = ' + header.sess_num - journal, ' obj_cnt = ' + string(header.obj_cnt, format = '(I0)') - journal, ' pol_id = ' + string(header.pol_id, format = '(I0)') - journal, ' nbin = ' + string(sqrt(header.nbin), format = '(I0)') - - ; calibration block - - file_name_fields = strsplit(header.filename, '_', /extract) - - ; error is the quadratic relative error - - error = 0. - - case header.datatype of - 0 : begin - data = double(data) - - ; ==================================== - ; for old l1 data - ; data = data * header.nbin * header.ndit - ; header.xposure = header.xposure * header.ndit - ; header.nsumexp = header.ndit - ; ==================================== - - data = metis_dark_vlda(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) - - ; ==================================== - ; for data already subtracted of dark - ; file = file_basename(header.parent, '.fits') + '_subdark.fits' - ; data = mrdfits('VL subdark/' + file, 0, /silent) - ; data = rebin(data, header.naxis1, header.naxis2) - ; data = data * header.nbin * header.ndit - ; history = ['Dark correction: ', ' ' + file] - ; ==================================== - - if header.pol_id eq 0 then begin - btype = 'VL fixed-polarization intensity' - descriptor = 'metis-vl-image' - polarimetric = 1 - endif else if header.pol_id ge 1 and header.pol_id le 4 then begin - btype = 'VL fixed-polarization intensity' - descriptor = 'metis-vl-image' - polarimetric = 1 - endif else begin - btype = 'VL total brightness' - descriptor = 'metis-vl-tb' - polarimetric = 0 - endelse - - data = metis_flat_field(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) - data = metis_vignetting(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) - data = metis_rad_cal(data, header, cal_pack, polarimetric = polarimetric, error = error, quality_matrix = quality_matrix, history = history) - - bunit = cal_pack.vl_channel.cal_units - end - - 3 : begin - ; calibration of temporal noise images - btype = 'VL temporal standard deviation' - descriptor = file_name_fields[2] - bunit = 'DN' - history = !null - end - - 5 : begin - ; calibration of cr/sep log matrices - btype = 'VL cosmic-ray matrix' - descriptor = file_name_fields[2] - bunit = 'DN' - history = !null - end - - else : begin - journal, 'Error 01: wrong input data product (expected data types 0, 3, or 5).' - journal - exit, status = 1 - end - endcase - - ; definitions for the primary header - ; version of the fits file - - version = string(input.l2_version, format = '(I02)') - - ; fits creation date - - date = date_conv(systime(/julian, /utc), 'FITS') - - ; name of the fits file - - file_name = 'solo_L2_' + descriptor + '_' + file_name_fields[3] + '_V' + version + '.fits' - out_file_name = 'output/' + file_name - - ; adjust the primary header - - fxaddpar, primary_header, 'FILENAME', file_name - fxaddpar, primary_header, 'PARENT', file_basename(input.file_name) - fxaddpar, primary_header, 'LEVEL', 'L2' - fxaddpar, primary_header, 'CREATOR', 'metis_l2_prep_vl_generic.pro' - fxaddpar, primary_header, 'VERS_SW', input.sw_version - fxaddpar, primary_header, 'VERS_CAL', cal_pack.version - fxaddpar, primary_header, 'VERSION', version - fxaddpar, primary_header, 'BTYPE', btype - fxaddpar, primary_header, 'BUNIT', bunit - fxaddpar, primary_header, 'DATAMIN', min(data, /nan) - fxaddpar, primary_header, 'DATAMAX', max(data, /nan) - fxaddpar, primary_header, 'WAVEBAND', cal_pack.vl_channel.name - fxaddpar, primary_header, 'XPOSURE', header.xposure - fxaddpar, primary_header, 'NSUMEXP', header.nsumexp - - sxdelpar, primary_header, 'BLANK' - - ; fix planning info keywords - - if header.soopname.startswith('unknown') then soopname = 'none' else soopname = header.soopname - if header.obs_mode.startswith('unknown') then obs_mode = 'none' else obs_mode = header.obs_mode - if soopname eq 'none' then sooptype = 'none' else sooptype = header.sooptype - if obs_mode eq 'none' then obs_type = 'none' else obs_type = header.obs_type - if soopname eq 'none' and obs_mode eq 'none' then obs_id = 'none' else obs_id = header.obs_id - if obs_mode eq 'none' then obs_mode = 'METIS_GENERIC_OBS' - - fxaddpar, primary_header, 'SOOPNAME', soopname - fxaddpar, primary_header, 'SOOPTYPE', sooptype - fxaddpar, primary_header, 'OBS_MODE', obs_mode - fxaddpar, primary_header, 'OBS_TYPE', obs_type - fxaddpar, primary_header, 'OBS_ID', obs_id - - ; read the calibration curve to convert pmp raw voltages (dacpol) into effective polarization angles - - dacpol_cal = cal_pack.vl_channel.dacpol_cal - - if header.pol_id ne 5 then begin - if fix(header.hdr_vers) le 4 then begin - case header.pol_id of - 0: dacpol = header.dac1pol1 - 1: dacpol = header.dac1pol1 - 2: dacpol = header.dac1pol2 - 3: dacpol = header.dac1pol3 - 4: dacpol = header.dac1pol4 - endcase - endif - - if fix(header.hdr_vers) ge 5 then begin - dacpol = header.dac1pol1 - endif - - k = where(dacpol_cal.dacpol eq dacpol) - angle = dacpol_cal.angle[k] - angle = float(angle[0]) - - fxaddpar, primary_header, 'POLANGLE', angle, '[deg] polarization angle', after = 'POL_ID' - endif - - ; append wcs keywords - - wcs = metis_wcs(header, cal_pack, ref_detector = ref_detector) - foreach element, wcs do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' - - ; append solar ephemeris keywords - - ephemeris = solo_get_ephemeris(header, cal_pack) - foreach element, ephemeris do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' - - history = [history, 'Update WCS and solar ephemeris:', ' SKD version = ' + kernel_version] - - ; update the comment and history keywords - - fxaddpar, primary_header, 'COMMENT', 'Uncertainty matrix in the FITS extension is preliminary.' - fxaddpar, primary_header, 'COMMENT', 'Rotate CROTA degrees counter-clockwise to have Solar North up.' - - for k = 0, n_elements(history) - 1 do $ - fxaddpar, primary_header, 'HISTORY', history[k] - fxaddpar, primary_header, 'HISTORY', 'L2 FITS file created on ' + date - - ; add checksum and datasum to the fits header - - if not ref_detector then data = metis_rectify(data, 'VL') - fits_add_checksum, primary_header, data - mwrfits, float(data), out_file_name, primary_header, /no_comment, /create, /silent - - journal, 'Fits file created:' - journal, ' file name = ' + file_basename(out_file_name) - - ; add the extension with the quality matrix - - base_header = primary_header - sxdelpar, base_header, 'SIMPLE' - sxdelpar, base_header, 'EXTEND' - sxdelpar, base_header, 'DATASUM' - sxdelpar, base_header, 'CHECKSUM' - sxdelpar, base_header, 'COMMENT' - sxdelpar, base_header, 'HISTORY' - - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Pixel quality' - fxaddpar, extension_header, 'BUNIT', 'None' - fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) - fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' - fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' - fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' - fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' - if not ref_detector then quality_matrix = metis_rectify(quality_matrix, 'VL') - fits_add_checksum, extension_header, quality_matrix - mwrfits, float(quality_matrix), out_file_name, extension_header, /no_comment, /silent - - journal, 'Quality-matrix extension correctly added.' - - ; add the extension with the error matrix - - if not ref_detector then error = metis_rectify(error, 'VL') - error_matrix = data * sqrt(error) - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Absolute error' - fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) - fits_add_checksum, extension_header, float(error_matrix) - mwrfits, float(error_matrix), out_file_name, extension_header, /no_comment, /silent - - journal, 'Error-matrix extension correctly added.' + pol_id = fxpar(primary_header, 'POL_ID', missing = 0) + if pol_id ge 1 and pol_id le 4 then begin + seq_num = fxpar(primary_header, 'SEQ_NUM', missing = 0) + if seq_num eq 0 then begin + obj_cnt = fxpar(primary_header, 'OBJ_CNT') + n_pol = fxpar(primary_header, 'N_POL', missing = 4) + seq_num = ((obj_cnt - 1) / n_pol + 1) + fxaddpar, primary_header, 'SEQ_NUM', seq_num, before = 'POL_ID' + endif + endif - ; write the auxiliary information file - - output = { $ - file_name: out_file_name, $ - l1_file_name: input.file_name, $ - log_file_name: 'output/metis_l2_prep_log.txt' $ - } - - json_write, output, 'output/contents.json' - - ; unload the spice kernels + ; read the quality matrix - load_spice_kernels, kernel_list = kernel_list, /unload + quality_matrix = mrdfits(input.file_name, 'quality matrix', /silent) - journal, 'SPICE kernel files unloaded.' + ; convert the string header into an idl structure - ; close the log + header = fits_hdr2struct(primary_header) - journal, 'Exiting without errors.' - journal - - exit, status = 0 + journal, 'L1 FITS file correctly read:' + journal, ' filename = ' + file_basename(input.file_name) + journal, ' datatype = ' + string(header.datatype, format = '(I0)') + journal, ' sess_num = ' + header.sess_num + journal, ' obj_cnt = ' + string(header.obj_cnt, format = '(I0)') + journal, ' pol_id = ' + string(header.pol_id, format = '(I0)') + journal, ' nbin = ' + string(sqrt(header.nbin), format = '(I0)') + + ; calibration block + + file_name_fields = strsplit(header.filename, '_', /extract) + + ; error is the quadratic relative error + + error = 0. + + case header.datatype of + 0: begin + data = double(data) + + ; ==================================== + ; for old l1 data + ; data = data * header.nbin * header.ndit + ; header.xposure = header.xposure * header.ndit + ; header.nsumexp = header.ndit + ; ==================================== + + data = metis_dark_vlda(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) + + ; ==================================== + ; for data already subtracted of dark + ; file = file_basename(header.parent, '.fits') + '_subdark.fits' + ; data = mrdfits('VL subdark/' + file, 0, /silent) + ; data = rebin(data, header.naxis1, header.naxis2) + ; data = data * header.nbin * header.ndit + ; history = ['Dark correction: ', ' ' + file] + ; ==================================== + + if header.pol_id eq 0 then begin + btype = 'VL fixed-polarization intensity' + descriptor = 'metis-vl-image' + polarimetric = 1 + endif else if header.pol_id ge 1 and header.pol_id le 4 then begin + btype = 'VL fixed-polarization intensity' + descriptor = 'metis-vl-image' + polarimetric = 1 + endif else begin + btype = 'VL total brightness' + descriptor = 'metis-vl-tb' + polarimetric = 0 + endelse + + data = metis_flat_field(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) + data = metis_vignetting(data, header, cal_pack, error = error, quality_matrix = quality_matrix, history = history) + data = metis_rad_cal(data, header, cal_pack, polarimetric = polarimetric, error = error, quality_matrix = quality_matrix, history = history) + + bunit = cal_pack.vl_channel.cal_units + end + + 3: begin + ; calibration of temporal noise images + btype = 'VL temporal standard deviation' + descriptor = file_name_fields[2] + bunit = 'DN' + history = !null + end + + 5: begin + ; calibration of cr/sep log matrices + btype = 'VL cosmic-ray matrix' + descriptor = file_name_fields[2] + bunit = 'DN' + history = !null + end + + else: begin + journal, 'Error 01: wrong input data product (expected data types 0, 3, or 5).' + journal + exit, status = 1 + end + endcase + + ; definitions for the primary header + ; version of the fits file + + version = string(input.l2_version, format = '(I02)') + + ; fits creation date + + date = date_conv(systime(/julian, /utc), 'FITS') + + ; name of the fits file + + file_name = 'solo_L2_' + descriptor + '_' + file_name_fields[3] + '_V' + version + '.fits' + out_file_name = 'output/' + file_name + + ; adjust the primary header + + fxaddpar, primary_header, 'FILENAME', file_name + fxaddpar, primary_header, 'PARENT', file_basename(input.file_name) + fxaddpar, primary_header, 'LEVEL', 'L2' + fxaddpar, primary_header, 'CREATOR', 'metis_l2_prep_vl_generic.pro' + fxaddpar, primary_header, 'VERS_SW', input.sw_version + fxaddpar, primary_header, 'VERS_CAL', cal_pack.version + fxaddpar, primary_header, 'VERSION', version + fxaddpar, primary_header, 'BTYPE', btype + fxaddpar, primary_header, 'BUNIT', bunit + fxaddpar, primary_header, 'DATAMIN', min(data, /nan) + fxaddpar, primary_header, 'DATAMAX', max(data, /nan) + fxaddpar, primary_header, 'WAVEBAND', cal_pack.vl_channel.name + fxaddpar, primary_header, 'XPOSURE', header.xposure + fxaddpar, primary_header, 'NSUMEXP', header.nsumexp + + sxdelpar, primary_header, 'BLANK' + + ; fix planning info keywords + + if header.soopname.startswith('unknown') then soopname = 'none' else soopname = header.soopname + if header.obs_mode.startswith('unknown') then obs_mode = 'none' else obs_mode = header.obs_mode + if soopname eq 'none' then sooptype = 'none' else sooptype = header.sooptype + if obs_mode eq 'none' then obs_type = 'none' else obs_type = header.obs_type + if soopname eq 'none' and obs_mode eq 'none' then obs_id = 'none' else obs_id = header.obs_id + if obs_mode eq 'none' then obs_mode = 'METIS_GENERIC_OBS' + + fxaddpar, primary_header, 'SOOPNAME', soopname + fxaddpar, primary_header, 'SOOPTYPE', sooptype + fxaddpar, primary_header, 'OBS_MODE', obs_mode + fxaddpar, primary_header, 'OBS_TYPE', obs_type + fxaddpar, primary_header, 'OBS_ID', obs_id + + ; read the calibration curve to convert pmp raw voltages (dacpol) into effective polarization angles + + dacpol_cal = cal_pack.vl_channel.dacpol_cal + + if header.pol_id ne 5 then begin + if fix(header.hdr_vers) le 4 then begin + case header.pol_id of + 0: dacpol = header.dac1Pol1 + 1: dacpol = header.dac1Pol1 + 2: dacpol = header.dac1Pol2 + 3: dacpol = header.dac1Pol3 + 4: dacpol = header.dac1Pol4 + endcase + endif + + if fix(header.hdr_vers) ge 5 then begin + dacpol = header.dac1Pol1 + endif + + k = where(dacpol_cal.dacpol eq dacpol) + angle = dacpol_cal.angle[k] + angle = float(angle[0]) + + fxaddpar, primary_header, 'POLANGLE', angle, '[deg] polarization angle', after = 'POL_ID' + endif + + ; append wcs keywords + + wcs = metis_wcs(header, cal_pack, ref_detector = ref_detector) + foreach element, wcs do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' + + ; append solar ephemeris keywords + + ephemeris = solo_get_ephemeris(header, cal_pack) + foreach element, ephemeris do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' + + history = [history, 'Update WCS and solar ephemeris:', ' SKD version = ' + kernel_version] + + ; update the comment and history keywords + + fxaddpar, primary_header, 'COMMENT', 'Uncertainty matrix in the FITS extension is preliminary.' + fxaddpar, primary_header, 'COMMENT', 'Rotate CROTA degrees counter-clockwise to have Solar North up.' + + for k = 0, n_elements(history) - 1 do $ + fxaddpar, primary_header, 'HISTORY', history[k] + fxaddpar, primary_header, 'HISTORY', 'L2 FITS file created on ' + date + + ; add checksum and datasum to the fits header + + if not ref_detector then data = metis_rectify(data, 'VL') + fits_add_checksum, primary_header, float(data) + mwrfits, float(data), out_file_name, primary_header, /no_comment, /create, /silent + + journal, 'Fits file created:' + journal, ' file name = ' + file_basename(out_file_name) + + ; add the extension with the quality matrix + + base_header = primary_header + sxdelpar, base_header, 'SIMPLE' + sxdelpar, base_header, 'EXTEND' + sxdelpar, base_header, 'DATASUM' + sxdelpar, base_header, 'CHECKSUM' + sxdelpar, base_header, 'COMMENT' + sxdelpar, base_header, 'HISTORY' + + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Pixel quality' + fxaddpar, extension_header, 'BUNIT', 'None' + fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) + fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' + fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' + fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' + fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' + if not ref_detector then quality_matrix = metis_rectify(quality_matrix, 'VL') + fits_add_checksum, extension_header, float(quality_matrix) + mwrfits, float(quality_matrix), out_file_name, extension_header, /no_comment, /silent + + journal, 'Quality-matrix extension correctly added.' + + ; add the extension with the error matrix + + if not ref_detector then error = metis_rectify(error, 'VL') + error_matrix = data * sqrt(error) + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Absolute error' + fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) + fits_add_checksum, extension_header, float(error_matrix) + mwrfits, float(error_matrix), out_file_name, extension_header, /no_comment, /silent + + journal, 'Error-matrix extension correctly added.' + + ; fix some header keywords + + fix_fits_header, out_file_name + + ; write the auxiliary information file + + output = { $ + file_name: out_file_name, $ + l1_file_name: input.file_name, $ + log_file_name: 'output/metis_l2_prep_log.txt' $ + } + + json_write, output, 'output/contents.json' + + ; unload the spice kernels + + load_spice_kernels, kernel_list = kernel_list, /unload + + journal, 'SPICE kernel files unloaded.' + + ; close the log + + journal, 'Exiting without errors.' + journal + + exit, status = 0 end \ No newline at end of file diff --git a/metis_l2_prep_vl_polariz.pro b/metis_l2_prep_vl_polariz.pro index c41b19f0f4742496ab5bad5fecc816e472459b62..2cad5819ca6b75f9fc7552384a188d1dfadd5883 100755 --- a/metis_l2_prep_vl_polariz.pro +++ b/metis_l2_prep_vl_polariz.pro @@ -1,799 +1,809 @@ pro metis_l2_prep_vl_polariz + ; keyword defining if the detector reference frame must be used for the output - ; keyword defining if the detector reference frame must be used for the output + ref_detector = 0 - ref_detector = 0 + ; start the log - ; start the log + journal, 'output/metis_l2_prep_log.txt' - journal,'output/metis_l2_prep_log.txt' + ; read the auxiliary file - here we have all the inputs we need - ; read the auxiliary file - here we have all the inputs we need + input = json_parse('input/contents.json', /toarray, /tostruct) - input = json_parse('input/contents.json', /toarray, /tostruct) + ; load the spice kernels - ; load the spice kernels + load_spice_kernels, input.spice_kernels, kernel_list = kernel_list, kernel_version = kernel_version - load_spice_kernels, input.spice_kernels, kernel_list = kernel_list, kernel_version = kernel_version + journal, 'SPICE kernel files correctly loaded:' + journal, ' SDK version = ' + kernel_version - journal, 'SPICE kernel files correctly loaded:' - journal, ' SDK version = ' + kernel_version + ; read the calibration package - ; read the calibration package + cal_pack = json_parse(input.cal_pack_path + '/index.json', /toarray, /tostruct) - cal_pack = json_parse(input.cal_pack_path + '/index.json', /toarray, /tostruct) + ; include the calibration package path into the cal_pack structure - ; include the calibration package path into the cal_pack structure + cal_pack = create_struct('path', input.cal_pack_path + path_sep(), cal_pack) - cal_pack = create_struct('path', input.cal_pack_path + path_sep(), cal_pack) + journal, 'Calibration package correctly imported:' + journal, ' version = ' + cal_pack.version + journal, ' validity range = ' + string(cal_pack.validity_range.start, cal_pack.validity_range._end, format = '(A, "-", A)') - journal, 'Calibration package correctly imported:' - journal, ' version = ' + cal_pack.version - journal, ' validity range = ' + string(cal_pack.validity_range.start, cal_pack.validity_range._end, format = '(A, "-", A)') - - n = n_elements(input.file_name) + n = n_elements(input.file_name) - ; check on the number of input images (= npol = 4) + ; check on the number of input images (= npol = 4) - if n ne 4 then begin - journal, 'Error 07: wrong number of input images (expected 4).' - journal - exit, status = 7 - endif + if n ne 4 then begin + journal, 'Error 07: wrong number of input images (expected 4).' + journal + exit, status = 7 + endif - ; calibration block + ; calibration block - data = !null - abs_error = !null - data_header = !null - data_subdark = !null + data = !null + abs_error = !null + data_header = !null + data_subdark = !null - quality_matrix = 1. + quality_matrix = 1. - for k = 0, 3 do begin + for k = 0, 3 do begin + ; read the input image - ; read the input image + image = mrdfits(input.file_name[k], 0, primary_header, /silent) - image = mrdfits(input.file_name[k], 0, primary_header, /silent) + ; patch to fix the lack of the keyword SEQ_NUM - ; patch to fix the lack of the keyword SEQ_NUM - - seq_num = fxpar(primary_header, 'SEQ_NUM', missing = 0) - if seq_num eq 0 then begin - obj_cnt = fxpar(primary_header, 'OBJ_CNT') - n_pol = fxpar(primary_header, 'N_POL', missing = 4) - seq_num = ((obj_cnt - 1)/n_pol + 1) - fxaddpar, primary_header, 'SEQ_NUM', seq_num, before = 'POL_ID' - endif + seq_num = fxpar(primary_header, 'SEQ_NUM', missing = 0) + if seq_num eq 0 then begin + obj_cnt = fxpar(primary_header, 'OBJ_CNT') + n_pol = fxpar(primary_header, 'N_POL', missing = 4) + seq_num = ((obj_cnt - 1) / n_pol + 1) + fxaddpar, primary_header, 'SEQ_NUM', seq_num, before = 'POL_ID' + endif - if k eq 0 then comment = fxpar(primary_header, 'COMMENT') else begin - sxdelpar, primary_header, 'COMMENT' - foreach line, comment do $ - fxaddpar, primary_header, 'COMMENT', line - endelse + if k eq 0 then comment = fxpar(primary_header, 'COMMENT') else begin + sxdelpar, primary_header, 'COMMENT' + foreach line, comment do $ + fxaddpar, primary_header, 'COMMENT', line + endelse - header = fits_hdr2struct(primary_header) + header = fits_hdr2struct(primary_header) - ; ==================================== - ; for old l1 data - ; image = image * header.nbin * header.ndit - ; header.xposure = header.dit/1000. * header.ndit - ; header.nsumexp = header.ndit - ; ==================================== + ; ==================================== + ; for old l1 data + ; image = image * header.nbin * header.ndit + ; header.xposure = header.dit/1000. * header.ndit + ; header.nsumexp = header.ndit + ; ==================================== - ; check data type + ; check data type - if header.datatype ne 0 then begin - journal, 'Error 01: wrong input data product (expected data type 0).' - journal - exit, status = 1 - endif - - ; check consistency of polarization state + if header.datatype ne 0 then begin + journal, 'Error 01: wrong input data product (expected data type 0).' + journal + exit, status = 1 + endif - if header.pol_id lt 1 or header.pol_id gt 4 then begin - journal, 'Error 04: image has inconsistent polarization state.' - journal - exit, status = 4 - endif + ; check consistency of polarization state - ; check consistency of pmp raw voltages (dacpol) + if header.pol_id lt 1 or header.pol_id gt 4 then begin + journal, 'Error 04: image has inconsistent polarization state.' + journal + exit, status = 4 + endif - if $ - header.dac1pol1 ne header.dac2pol1 or $ - header.dac1pol2 ne header.dac2pol2 or $ - header.dac1pol3 ne header.dac2pol3 or $ - header.dac1pol4 ne header.dac2pol4 then begin - journal, 'Error 05: image has inconsistent PMP voltages.' - journal - exit, status = 5 - endif + ; check consistency of pmp raw voltages (dacpol) - journal, 'Reading L1 FITS file: ' + file_basename(input.file_name[k]) + ' ...' - journal, ' datatype = ' + string(header.datatype, format = '(I0)') - journal, ' sess_num = ' + header.sess_num - journal, ' seq_num = ' + string(header.seq_num, format = '(I0)') - journal, ' obj_cnt = ' + string(header.obj_cnt, format = '(I0)') - journal, ' pol_id = ' + string(header.pol_id, format = '(I0)') - journal, ' nbin = ' + string(sqrt(header.nbin), format = '(I0)') + if $ + header.dac1Pol1 ne header.dac2Pol1 or $ + header.dac1Pol2 ne header.dac2Pol2 or $ + header.dac1Pol3 ne header.dac2Pol3 or $ + header.dac1Pol4 ne header.dac2Pol4 then begin + journal, 'Error 05: image has inconsistent PMP voltages.' + journal + exit, status = 5 + endif - ; pile up images and headers + journal, 'Reading L1 FITS file: ' + file_basename(input.file_name[k]) + ' ...' + journal, ' datatype = ' + string(header.datatype, format = '(I0)') + journal, ' sess_num = ' + header.sess_num + journal, ' seq_num = ' + string(header.seq_num, format = '(I0)') + journal, ' obj_cnt = ' + string(header.obj_cnt, format = '(I0)') + journal, ' pol_id = ' + string(header.pol_id, format = '(I0)') + journal, ' nbin = ' + string(sqrt(header.nbin), format = '(I0)') - data = [[[data]], [[image]]] - data_header = [data_header, header] - - ; read and update the quality matrix - - image_quality_matrix = mrdfits(input.file_name[k], 'quality matrix', /silent) - - ; apply dark correction to compute stokes i and total brightness - - tb_history = !null - - image_subdark = metis_dark_vlda(image, header, error = image_error, quality_matrix = image_quality_matrix, cal_pack, history = tb_history) - - ; ==================================== - ; for data already subtracted of dark - ; file = file_basename(header.parent, '.fits') + '_subdark.fits' - ; image_subdark = mrdfits('input/subdark/' + file, 0, /silent) - ; image_subdark = rebin(image_subdark, header.naxis1, header.naxis2) * header.nbin - ; image_subdark = image_subdark * header.ndit - ; tb_history = ['Dark correction: ', ' ' + file] - ; ==================================== - - data_subdark = [[[data_subdark]], [[image_subdark]]] - abs_error = [[[abs_error]], [[image_subdark * sqrt(image_error)]]] - - quality_matrix *= image_quality_matrix - endfor - - ; adjust header keywords - - obt_beg = min(data_header.obt_beg, i) - obt_end = max(data_header.obt_end, j) - - header = data_header[0] - header.date_beg = data_header[i].date_beg - header.date_obs = data_header[i].date_beg - header.date_end = data_header[j].date_end - - telapse = obt_end - obt_beg - obt_avg = (obt_beg + obt_end)/2. - date_avg = solo_obt2utc(decode_obt(obt_avg, /from_decimal)) - - header.obt_beg = obt_beg - header.obt_end = obt_end - header.date_avg = date_avg - header.tsensor = mean(data_header.tsensor) - header.pmptemp = mean(data_header.pmptemp) - - journal, 'Average values computed:' - journal, ' obt_beg = ' + string(header.obt_beg, format = '(F0)') - journal, ' date_beg = ' + header.date_avg - journal, ' tsensor = ' + string(header.tsensor, format = '(F0)') - journal, ' pmptemp = ' + string(header.pmptemp, format = '(F0)') - - ; read the demodulation object of the calibration package - - demod_info = cal_pack.vl_channel.demodulation - - ; read the calibration curve to convert pmp raw voltages (dacpol) into effective polarization angles - - dacpol_cal = cal_pack.vl_channel.dacpol_cal - - ; apply the demodulation - - journal, 'Demodulation:' - - demod_tensor = fltarr(header.naxis1, header.naxis2, 4) - - angles = !null - stokes_name = ['I', 'Q', 'U'] - stokes = dblarr(header.naxis1, header.naxis2, 3) - stokes_subdark = dblarr(header.naxis1, header.naxis2, 3) - stokes_abs_error = dblarr(header.naxis1, header.naxis2, 3) - for i = 0, 2 do begin - - journal, ' stokes = ' + stokes_name[i] - - for j = 0, 3 do begin - - journal, ' pol_id = ' + string(data_header[j].pol_id, format = '(I0)') - - ; check the polarization state of the image and select the corresponding dacpol value - - if fix(data_header[j].hdr_vers) le 4 then begin - case data_header[j].pol_id of - 1: dacpol = data_header[j].dac1pol1 - 2: dacpol = data_header[j].dac1pol2 - 3: dacpol = data_header[j].dac1pol3 - 4: dacpol = data_header[j].dac1pol4 - endcase - endif - - if fix(data_header[j].hdr_vers) ge 5 then begin - dacpol = data_header[j].dac1pol1 - endif - - ; select the correct demodulation tensor element based on effective angle and stokes paramater - - k = where(dacpol_cal.dacpol eq dacpol) - angle = dacpol_cal.angle[k] - angle = angle[0] - - if isa(angles) then begin - if n_elements(angles) lt 4 then angles = [angles, angle] - endif else angles = angle - - journal, ' pol. angle = ' + string(angle, format = '(F0.1)') - - n = where(demod_info.angle eq angle and demod_info.stokes eq stokes_name[i], count) - - if count ne 1 then begin - journal, 'Error 06: applicable demodulation-tensor element file not found.' - journal - exit, status = 6 - endif - - demod_file = demod_info[n].file_name - demod_image = float(readfits(cal_pack.path + demod_file, /silent)) - - ; rebin the demodulation tensor image to match image size - - demod_image = rebin(demod_image, header.naxis1, header.naxis2) - demod_tensor[*, *, j] = demod_image - - journal, ' demod. tensor file = ' + demod_file - endfor - - ; compute the stokes parameters - - stokes[*, *, i] = $ - demod_tensor[*, *, 0] * data[*, *, 0] + $ - demod_tensor[*, *, 1] * data[*, *, 1] + $ - demod_tensor[*, *, 2] * data[*, *, 2] + $ - demod_tensor[*, *, 3] * data[*, *, 3] - - ; compute the dark-subtracted stokes parameters - - stokes_subdark[*, *, i] = $ - demod_tensor[*, *, 0] * data_subdark[*, *, 0] + $ - demod_tensor[*, *, 1] * data_subdark[*, *, 1] + $ - demod_tensor[*, *, 2] * data_subdark[*, *, 2] + $ - demod_tensor[*, *, 3] * data_subdark[*, *, 3] - - ; compute the stokes errors - - stokes_abs_error[*, *, i] = sqrt( $ - demod_tensor[*, *, 0]^2 * abs_error[*, *, 0]^2 + $ - demod_tensor[*, *, 1]^2 * abs_error[*, *, 1]^2 + $ - demod_tensor[*, *, 2]^2 * abs_error[*, *, 2]^2 + $ - demod_tensor[*, *, 3]^2 * abs_error[*, *, 3]^2) - endfor - - demod_history = 'Demodulation performed for angles ' + string(angles, format = '(3(f0.1, ", "), f0.1)') + ' deg' - - tb_history = [tb_history, demod_history] - pb_history = demod_history - - i = reform(stokes_subdark[*, *, 0]) - i_abs_error = reform(stokes_abs_error[*, *, 0]) - q = reform(stokes[*, *, 1]) - q_abs_error = reform(stokes_abs_error[*, *, 1]) - u = reform(stokes[*, *, 2]) - u_abs_error = reform(stokes_abs_error[*, *, 2]) - - ; compute the tb from the dark-subtracted stokes i and apply other calibrations - - journal, 'Calibrating total brightness...' - - tb_image = i - tb_error = (i_abs_error/i)^2 - tb_quality_matrix = quality_matrix - tb_image = metis_flat_field(tb_image, header, error = tb_error, quality_matrix = tb_quality_matrix, cal_pack, history = tb_history) - tb_image = metis_vignetting(tb_image, header, error = tb_error, quality_matrix = tb_quality_matrix, cal_pack, history = tb_history) - tb_image = metis_rad_cal(tb_image, header, error = tb_error, quality_matrix = tb_quality_matrix, cal_pack, /polarimetric, history = tb_history) - - ; compute the pb from the stokes q and u and apply other calibrations - - journal, 'Calibrating polarized brightness...' - - pb_image = sqrt(q^2 + u^2) - pb_error = (q^2 * q_abs_error^2 + u^2 * u_abs_error^2)/pb_image^4 - pb_quality_matrix = quality_matrix - pb_image = metis_flat_field(pb_image, header, error = pb_error, quality_matrix = pb_quality_matrix, cal_pack, history = pb_history) - pb_image = metis_vignetting(pb_image, header, error = pb_error, quality_matrix = pb_quality_matrix, cal_pack, history = pb_history) - pb_image = metis_rad_cal(pb_image, header, error = pb_error, quality_matrix = pb_quality_matrix, cal_pack, /polarimetric, history = pb_history) - - ; ==================================== - ; for simple radiometric calibration - ; cal_pack.vl_channel.cal_units = 'DN/s' - ; ==================================== - - ; compute the polarization angle from the stokes q and u - - pol_angle = 0.5D0 * atan(stokes[*, *, 2], stokes[*, *, 1]) - pol_angle_error = (q^2 * u_abs_error^2 + u^2 * q_abs_error^2)/(2D0 * pb_image^4)/pol_angle^2 - - pol_angle = pol_angle * !radeg - - journal, 'Polarization angle correctly computed.' - - ; definitions for the primary header - ; version of the fits file - - version = string(input.l2_version, format = '(I02)') - - ; creation and acquisition times in utc - - date = date_conv(systime(/julian, /utc), 'FITS') - - ; adjust the primary header - - fxaddpar, primary_header, 'PARENT', strjoin(file_basename(input.file_name), ', ') - fxaddpar, primary_header, 'LEVEL', 'L2' - fxaddpar, primary_header, 'CREATOR', 'metis_l2_prep_vl_polariz.pro' - fxaddpar, primary_header, 'VERS_SW', input.sw_version - fxaddpar, primary_header, 'VERS_CAL', cal_pack.version - fxaddpar, primary_header, 'VERSION', version - fxaddpar, primary_header, 'DATE', date, 'date and time of FITS file creation' - fxaddpar, primary_header, 'DATE-BEG', header.date_beg, 'start time of observation' - fxaddpar, primary_header, 'DATE-OBS', header.date_obs, 'same as DATE-BEG' - fxaddpar, primary_header, 'DATE-AVG', header.date_avg, 'average time of observation' - fxaddpar, primary_header, 'DATE-END', header.date_end, 'end time of observation' - fxaddpar, primary_header, 'OBT_BEG', header.obt_beg, 'start acquisition time in on-board time', format = 'F0.5' - fxaddpar, primary_header, 'OBT_END', header.obt_end, 'end acquisition time in on-board time', format = 'F0.5' - fxaddpar, primary_header, 'TELAPSE', telapse, '[s] elapsed time between beginning and end of observation' - fxaddpar, primary_header, 'WAVEBAND', cal_pack.vl_channel.name - fxaddpar, primary_header, 'XPOSURE', header.xposure - fxaddpar, primary_header, 'NSUMEXP', header.nsumexp - fxaddpar, primary_header, 'TSENSOR', header.tsensor - fxaddpar, primary_header, 'PMPTEMP', header.pmptemp - - ; fix planning info keywords - - if header.soopname.startswith('unknown') then soopname = 'none' else soopname = header.soopname - if header.obs_mode.startswith('unknown') then obs_mode = 'none' else obs_mode = header.obs_mode - if soopname eq 'none' then sooptype = 'none' else sooptype = header.sooptype - if obs_mode eq 'none' then obs_type = 'none' else obs_type = header.obs_type - if soopname eq 'none' and obs_mode eq 'none' then obs_id = 'none' else obs_id = header.obs_id - if obs_mode eq 'none' then obs_mode = 'METIS_GENERIC_OBS' - - fxaddpar, primary_header, 'SOOPNAME', soopname - fxaddpar, primary_header, 'SOOPTYPE', sooptype - fxaddpar, primary_header, 'OBS_MODE', obs_mode - fxaddpar, primary_header, 'OBS_TYPE', obs_type - fxaddpar, primary_header, 'OBS_ID', obs_id - - ; append wcs keywords - - wcs = metis_wcs(header, cal_pack, ref_detector = ref_detector) - foreach element, wcs do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' - - ; append solar ephemeris keywords - - ephemeris = solo_get_ephemeris(header, cal_pack) - foreach element, ephemeris do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' - - ; update the comment and history keywords - - fxaddpar, primary_header, 'COMMENT', 'Uncertainty matrix in the FITS extension is preliminary.' - fxaddpar, primary_header, 'COMMENT', 'Rotate CROTA degrees counter-clockwise to have Solar North up.' - - history = ['Update WCS and solar ephemeris:', ' SKD version = ' + kernel_version] - - tb_history = [tb_history, history] - pb_history = [pb_history, history] - - ; delete useless keywords - - sxdelpar, primary_header, 'BLANK' - sxdelpar, primary_header, 'OBJ_CNT' - sxdelpar, primary_header, 'POL_ID' - sxdelpar, primary_header, 'SNRMIN' - sxdelpar, primary_header, 'SNRMAX' - sxdelpar, primary_header, 'NB_IMG' - sxdelpar, primary_header, 'SN_MEAN1' - sxdelpar, primary_header, 'SN_VAR1' - sxdelpar, primary_header, 'SN_MEAN2' - sxdelpar, primary_header, 'SN_VAR2' - sxdelpar, primary_header, 'SN_MEAN3' - sxdelpar, primary_header, 'SN_VAR3' - sxdelpar, primary_header, 'SN_MEAN4' - sxdelpar, primary_header, 'SN_VAR4' - sxdelpar, primary_header, 'SN_MEAN5' - sxdelpar, primary_header, 'SN_VAR5' - sxdelpar, primary_header, 'N' - - date_beg_string = strmid(header.date_beg, 0, 19) - date_beg_string = date_beg_string.replace('-', '') - date_beg_string = date_beg_string.replace(':', '') - - ; array of output file names - - out_file_name = strarr(4) - - ; keywords specific for polarized brightness images - - primary_pb_header = primary_header - - ; name of the fits file - - file_name = 'solo_L2_metis-vl-pb_' + date_beg_string + '_V' + version + '.fits' - out_file_name[0] = 'output/' + file_name - - fxaddpar, primary_pb_header, 'FILENAME', file_name - fxaddpar, primary_pb_header, 'BTYPE', 'VL polarized brightness' - fxaddpar, primary_pb_header, 'BUNIT', cal_pack.vl_channel.cal_units - fxaddpar, primary_pb_header, 'DATAMIN', min(pb_image, /nan) - fxaddpar, primary_pb_header, 'DATAMAX', max(pb_image, /nan) - - ; add the history keyword - - for k = 0, n_elements(pb_history) - 1 do fxaddpar, primary_pb_header, 'HISTORY', pb_history[k] - fxaddpar, primary_pb_header, 'HISTORY', 'L2 FITS file created on ' + date - - ; add checksum and datasum to the fits header - - if not ref_detector then pb_image = metis_rectify(pb_image, 'VL') - fits_add_checksum, primary_pb_header, pb_image - mwrfits, float(pb_image), out_file_name[0], primary_pb_header, /no_comment, /create, /silent - - journal, 'Polarized-brightness FITS file created:' - journal, ' file name = ' + file_basename(out_file_name[0]) - - ; add the extension with the quality matrix - - base_header = primary_pb_header - sxdelpar, base_header, 'SIMPLE' - sxdelpar, base_header, 'EXTEND' - sxdelpar, base_header, 'DATASUM' - sxdelpar, base_header, 'CHECKSUM' - sxdelpar, base_header, 'COMMENT' - sxdelpar, base_header, 'HISTORY' - - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Pixel quality' - fxaddpar, extension_header, 'BUNIT', 'None' - fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) - fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' - fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' - fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' - fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' - if not ref_detector then pb_quality_matrix = metis_rectify(pb_quality_matrix, 'VL') - fits_add_checksum, extension_header, pb_quality_matrix - mwrfits, float(pb_quality_matrix), out_file_name[0], extension_header, /no_comment, /silent - - journal, 'Quality-matrix extension correctly added.' - - ; add the extension with the error matrix - - if not ref_detector then pb_error = metis_rectify(pb_error, 'VL') - error_matrix = pb_image * 0. ;pb_image * sqrt(pb_error) - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Absolute error' - fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) - fits_add_checksum, extension_header, error_matrix - mwrfits, float(error_matrix), out_file_name[0], extension_header, /no_comment, /silent - - journal, 'Error-matrix extension correctly added.' - - ; keywords specific for total brightness images - - primary_tb_header = primary_header - - ; name of the fits file - - file_name = 'solo_L2_metis-vl-tb_' + date_beg_string + '_V' + version + '.fits' - out_file_name[1] = 'output/' + file_name - - fxaddpar, primary_tb_header, 'FILENAME', file_name - fxaddpar, primary_tb_header, 'BTYPE', 'VL total brightness' - fxaddpar, primary_tb_header, 'BUNIT', cal_pack.vl_channel.cal_units - fxaddpar, primary_tb_header, 'DATAMIN', min(tb_image, /nan) - fxaddpar, primary_tb_header, 'DATAMAX', max(tb_image, /nan) - - ; add the history keyword - - for k = 0, n_elements(tb_history) - 1 do fxaddpar, primary_tb_header, 'HISTORY', tb_history[k] - fxaddpar, primary_tb_header, 'HISTORY', 'L2 FITS file created on ' + date - - ; add checksum and datasum to the fits header - - if not ref_detector then tb_image = metis_rectify(tb_image, 'VL') - fits_add_checksum, primary_tb_header, tb_image - mwrfits, float(tb_image), out_file_name[1], primary_tb_header, /no_comment, /create, /silent - - journal, 'Total-brightness FITS file created:' - journal, ' file name = ' + file_basename(out_file_name[1]) - - ; add the extension with the quality matrix - - base_header = primary_tb_header - sxdelpar, base_header, 'SIMPLE' - sxdelpar, base_header, 'EXTEND' - sxdelpar, base_header, 'DATASUM' - sxdelpar, base_header, 'CHECKSUM' - sxdelpar, base_header, 'COMMENT' - sxdelpar, base_header, 'HISTORY' - - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Pixel quality' - fxaddpar, extension_header, 'BUNIT', 'None' - fxaddpar, extension_header, 'DATAMIN', min(tb_quality_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(tb_quality_matrix, /nan) - fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' - fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' - fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' - fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' - if not ref_detector then tb_quality_matrix = metis_rectify(tb_quality_matrix, 'VL') - fits_add_checksum, extension_header, tb_quality_matrix - mwrfits, float(tb_quality_matrix), out_file_name[1], extension_header, /no_comment, /silent - - journal, 'Quality-matrix extension correctly added.' - - ; add the extension with the error matrix - - if not ref_detector then tb_error = metis_rectify(tb_error, 'VL') - error_matrix = tb_image * 0. ;tb_image * sqrt(tb_error) - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Absolute error' - fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) - fits_add_checksum, extension_header, error_matrix - mwrfits, float(error_matrix), out_file_name[1], extension_header, /no_comment, /silent - - journal, 'Error-matrix extension correctly added.' - - ; keywords specific for polarization-angle images - - primary_polangle_header = primary_header - - ; name of the fits file - - file_name = 'solo_L2_metis-vl-pol-angle_' + date_beg_string + '_V' + version + '.fits' - out_file_name[2] = 'output/' + file_name - - fxaddpar, primary_polangle_header, 'FILENAME', file_name - fxaddpar, primary_polangle_header, 'BTYPE', 'VL polarization angle' - fxaddpar, primary_polangle_header, 'BUNIT', 'deg' - fxaddpar, primary_polangle_header, 'DATAMIN', min(pol_angle, /nan) - fxaddpar, primary_polangle_header, 'DATAMAX', max(pol_angle, /nan) - - ; add the history keyword - - for k = 0, n_elements(pb_history) - 1 do fxaddpar, primary_polangle_header, 'HISTORY', pb_history[k] - fxaddpar, primary_polangle_header, 'HISTORY', 'L2 FITS file created on ' + date - - ; add checksum and datasum to the fits header - - if not ref_detector then pol_angle = metis_rectify(pol_angle, 'VL') - fits_add_checksum, primary_polangle_header, pol_angle - mwrfits, float(pol_angle), out_file_name[2], primary_polangle_header, /no_comment, /create, /silent + ; pile up images and headers - journal, 'Polarization-angle FITS file created:' - journal, ' file name = ' + file_basename(out_file_name[2]) - - ; add the extension with the quality matrix - - base_header = primary_polangle_header - sxdelpar, base_header, 'SIMPLE' - sxdelpar, base_header, 'EXTEND' - sxdelpar, base_header, 'DATASUM' - sxdelpar, base_header, 'CHECKSUM' - sxdelpar, base_header, 'COMMENT' - sxdelpar, base_header, 'HISTORY' - - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Pixel quality' - fxaddpar, extension_header, 'BUNIT', 'None' - fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) - fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' - fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' - fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' - fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' - if not ref_detector then pol_angle_quality_matrix = metis_rectify(quality_matrix, 'VL') - fits_add_checksum, extension_header, pol_angle_quality_matrix - mwrfits, float(pol_angle_quality_matrix), out_file_name[2], extension_header, /no_comment, /silent - - journal, 'Quality-matrix extension correctly added.' - - ; add the extension with the error matrix - - if not ref_detector then pol_angle_error = metis_rectify(pol_angle_error, 'VL') - error_matrix = pol_angle * 0. ;pol_angle * sqrt(pol_angle_error) - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Absolute error' - fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) - fits_add_checksum, extension_header, error_matrix - mwrfits, float(error_matrix), out_file_name[2], extension_header, /no_comment, /silent - - journal, 'Error-matrix extension correctly added.' - - ; management of stokes images - - journal, 'Calibrating Stokes parameter I...' - - i = reform(stokes_subdark[*, *, 0]) - i = metis_flat_field(i, header, cal_pack) - i = metis_vignetting(i, header, cal_pack) - i = metis_rad_cal(i, header, cal_pack, /polarimetric) - - journal, 'Calibrating Stokes parameter Q...' - - q = reform(stokes[*, *, 1]) - q = metis_flat_field(q, header, cal_pack) - q = metis_vignetting(q, header, cal_pack) - q = metis_rad_cal(q, header, cal_pack, /polarimetric) - - journal, 'Calibrating Stokes parameter U...' - - u = reform(stokes[*, *, 2]) - u = metis_flat_field(u, header, cal_pack) - u = metis_vignetting(u, header, cal_pack) - u = metis_rad_cal(u, header, cal_pack, /polarimetric) - - ; keywords specific for stokes images - - primary_stokes_header = primary_header - - ; name of the fits file - - file_name = 'solo_L2_metis-vl-stokes_' + date_beg_string + '_V' + version + '.fits' - out_file_name[3] = 'output/' + file_name - - fxaddpar, primary_stokes_header, 'FILENAME', file_name - fxaddpar, primary_stokes_header, 'BTYPE', 'Stokes I' - fxaddpar, primary_stokes_header, 'BUNIT', cal_pack.vl_channel.cal_units - fxaddpar, primary_stokes_header, 'DATAMIN', min(i, /nan) - fxaddpar, primary_stokes_header, 'DATAMAX', max(i, /nan) - - ; add the history keyword - - for k = 0, n_elements(tb_history) - 1 do fxaddpar, primary_stokes_header, 'HISTORY', tb_history[k] - fxaddpar, primary_stokes_header, 'HISTORY', 'L2 FITS file created on ' + date - - ; add checksum and datasum to the fits header - if not ref_detector then i = metis_rectify(i, 'VL') - fits_add_checksum, primary_stokes_header, i - mwrfits, float(i), out_file_name[3], primary_stokes_header, /no_comment, /create, /silent - - journal, 'Stokes parameters FITS file created:' - journal, ' file name = ' + file_basename(out_file_name[3]) - - ; add the extension with the stokes q image - - extension_header = primary_stokes_header - sxdelpar, extension_header, 'SIMPLE' - sxdelpar, extension_header, 'EXTEND' - sxdelpar, extension_header, 'DATASUM' - sxdelpar, extension_header, 'CHECKSUM' - sxdelpar, extension_header, 'COMMENT' - sxdelpar, extension_header, 'HISTORY' - - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Stokes Q', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Stokes Q' - fxaddpar, extension_header, 'BUNIT', cal_pack.vl_channel.cal_units - fxaddpar, extension_header, 'DATAMIN', min(q, /nan) - fxaddpar, extension_header, 'DATAMAX', max(q, /nan) - sxdelpar, extension_header, 'EXTEND' - if not ref_detector then q = metis_rectify(q, 'VL') - fits_add_checksum, extension_header, q - mwrfits, float(q), out_file_name[3], extension_header, /no_comment, /silent - - journal, 'Q parameter extension correctly added.' - - ; add the extension with the stokes u image - - extension_header = primary_stokes_header - sxdelpar, extension_header, 'SIMPLE' - sxdelpar, extension_header, 'EXTEND' - sxdelpar, extension_header, 'DATASUM' - sxdelpar, extension_header, 'CHECKSUM' - sxdelpar, extension_header, 'COMMENT' - sxdelpar, extension_header, 'HISTORY' - - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Stokes U', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Stokes U' - fxaddpar, extension_header, 'BUNIT', cal_pack.vl_channel.cal_units - fxaddpar, extension_header, 'DATAMIN', min(u, /nan) - fxaddpar, extension_header, 'DATAMAX', max(u, /nan) - sxdelpar, extension_header, 'EXTEND' - if not ref_detector then u = metis_rectify(u, 'VL') - fits_add_checksum, extension_header, u - mwrfits, float(u), out_file_name[3], extension_header, /no_comment, /silent - - journal, 'U parameter extension correctly added.' - - ; add the extension with the quality matrix - - base_header = primary_stokes_header - sxdelpar, base_header, 'EXTEND' - sxdelpar, base_header, 'DATASUM' - sxdelpar, base_header, 'CHECKSUM' - sxdelpar, base_header, 'COMMENT' - sxdelpar, base_header, 'HISTORY' - - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Pixel quality' - fxaddpar, extension_header, 'BUNIT', 'None' - fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) - fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' - fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' - fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' - fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' - if not ref_detector then quality_matrix = metis_rectify(quality_matrix, 'VL') - fits_add_checksum, extension_header, quality_matrix - mwrfits, float(quality_matrix), out_file_name[3], extension_header, /no_comment, /silent - - journal, 'Quality-matrix extension correctly added.' - - ; add the extension with the error matrix - - if not ref_detector then error_matrix = metis_rectify(error_matrix, 'VL') - error_matrix = tb_image * 0. ;tb_image * sqrt(tb_error) - extension_header = base_header - fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' - fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' - fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' - fxaddpar, extension_header, 'BTYPE', 'Absolute error' - fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) - fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) - fits_add_checksum, extension_header, error_matrix - mwrfits, float(error_matrix), out_file_name[3], extension_header, /no_comment, /silent - - journal, 'Error-matrix extension correctly added.' - - ; write the auxiliary information file - - output = { $ - file_name: out_file_name, $ - l1_file_name: input.file_name, $ - log_file_name: 'output/metis_l2_prep_log.txt' $ - } - - json_write, output, 'output/contents.json' - - ; unload the spice kernels - - load_spice_kernels, kernel_list = kernel_list, /unload - - journal, 'SPICE kernel files unloaded.' - - ; close the log - - journal, 'Exiting without errors.' - journal - - exit, status = 0 -end + data = [[[data]], [[image]]] + data_header = [data_header, header] + + ; read and update the quality matrix + + image_quality_matrix = mrdfits(input.file_name[k], 'quality matrix', /silent) + + ; apply dark correction to compute stokes i and total brightness + + tb_history = !null + + image_subdark = metis_dark_vlda(image, header, error = image_error, quality_matrix = image_quality_matrix, cal_pack, history = tb_history) + + ; ==================================== + ; for data already subtracted of dark + ; file = file_basename(header.parent, '.fits') + '_subdark.fits' + ; image_subdark = mrdfits('input/subdark/' + file, 0, /silent) + ; image_subdark = rebin(image_subdark, header.naxis1, header.naxis2) * header.nbin + ; image_subdark = image_subdark * header.ndit + ; tb_history = ['Dark correction: ', ' ' + file] + ; ==================================== + + data_subdark = [[[data_subdark]], [[image_subdark]]] + abs_error = [[[abs_error]], [[image_subdark * sqrt(image_error)]]] + + quality_matrix *= image_quality_matrix + endfor + + ; adjust header keywords + + obt_beg = min(data_header.obt_beg, i) + obt_end = max(data_header.obt_end, j) + + header = data_header[0] + header.date_beg = data_header[i].date_beg + header.date_obs = data_header[i].date_beg + header.date_end = data_header[j].date_end + + telapse = obt_end - obt_beg + obt_avg = (obt_beg + obt_end) / 2. + date_avg = solo_obt2utc(decode_obt(obt_avg, /from_decimal)) + + header.obt_beg = obt_beg + header.obt_end = obt_end + header.date_avg = date_avg + header.tsensor = mean(data_header.tsensor) + header.pmptemp = mean(data_header.pmptemp) + + journal, 'Average values computed:' + journal, ' obt_beg = ' + string(header.obt_beg, format = '(F0)') + journal, ' date_beg = ' + header.date_avg + journal, ' tsensor = ' + string(header.tsensor, format = '(F0)') + journal, ' pmptemp = ' + string(header.pmptemp, format = '(F0)') + + ; read the demodulation object of the calibration package + + demod_info = cal_pack.vl_channel.demodulation + + ; read the calibration curve to convert pmp raw voltages (dacpol) into effective polarization angles + + dacpol_cal = cal_pack.vl_channel.dacpol_cal + + ; apply the demodulation + + journal, 'Demodulation:' + + demod_tensor = fltarr(header.naxis1, header.naxis2, 4) + + angles = !null + stokes_name = ['I', 'Q', 'U'] + stokes = dblarr(header.naxis1, header.naxis2, 3) + stokes_subdark = dblarr(header.naxis1, header.naxis2, 3) + stokes_abs_error = dblarr(header.naxis1, header.naxis2, 3) + for i = 0, 2 do begin + journal, ' stokes = ' + stokes_name[i] + + for j = 0, 3 do begin + journal, ' pol_id = ' + string(data_header[j].pol_id, format = '(I0)') + + ; check the polarization state of the image and select the corresponding dacpol value + + if fix(data_header[j].hdr_vers) le 4 then begin + case data_header[j].pol_id of + 1: dacpol = data_header[j].dac1Pol1 + 2: dacpol = data_header[j].dac1Pol2 + 3: dacpol = data_header[j].dac1Pol3 + 4: dacpol = data_header[j].dac1Pol4 + endcase + endif + + if fix(data_header[j].hdr_vers) ge 5 then begin + dacpol = data_header[j].dac1Pol1 + endif + + ; select the correct demodulation tensor element based on effective angle and stokes paramater + + k = where(dacpol_cal.dacpol eq dacpol) + angle = dacpol_cal.angle[k] + angle = angle[0] + + if isa(angles) then begin + if n_elements(angles) lt 4 then angles = [angles, angle] + endif else angles = angle + + journal, ' pol. angle = ' + string(angle, format = '(F0.1)') + + n = where(demod_info.angle eq angle and demod_info.stokes eq stokes_name[i], count) + + if count ne 1 then begin + journal, 'Error 06: applicable demodulation-tensor element file not found.' + journal + exit, status = 6 + endif + + demod_file = demod_info[n].file_name + demod_image = float(readfits(cal_pack.path + demod_file, /silent)) + + ; rebin the demodulation tensor image to match image size + + demod_image = rebin(demod_image, header.naxis1, header.naxis2) + demod_tensor[*, *, j] = demod_image + + journal, ' demod. tensor file = ' + demod_file + endfor + + ; compute the stokes parameters + + stokes[*, *, i] = $ + demod_tensor[*, *, 0] * data[*, *, 0] + $ + demod_tensor[*, *, 1] * data[*, *, 1] + $ + demod_tensor[*, *, 2] * data[*, *, 2] + $ + demod_tensor[*, *, 3] * data[*, *, 3] + + ; compute the dark-subtracted stokes parameters + + stokes_subdark[*, *, i] = $ + demod_tensor[*, *, 0] * data_subdark[*, *, 0] + $ + demod_tensor[*, *, 1] * data_subdark[*, *, 1] + $ + demod_tensor[*, *, 2] * data_subdark[*, *, 2] + $ + demod_tensor[*, *, 3] * data_subdark[*, *, 3] + + ; compute the stokes errors + + stokes_abs_error[*, *, i] = sqrt( $ + demod_tensor[*, *, 0] ^ 2 * abs_error[*, *, 0] ^ 2 + $ + demod_tensor[*, *, 1] ^ 2 * abs_error[*, *, 1] ^ 2 + $ + demod_tensor[*, *, 2] ^ 2 * abs_error[*, *, 2] ^ 2 + $ + demod_tensor[*, *, 3] ^ 2 * abs_error[*, *, 3] ^ 2) + endfor + + demod_history = 'Demodulation performed for angles ' + string(angles, format = '(3(f0.1, ", "), f0.1)') + ' deg' + + tb_history = [tb_history, demod_history] + pb_history = demod_history + + i = reform(stokes_subdark[*, *, 0]) + i_abs_error = reform(stokes_abs_error[*, *, 0]) + q = reform(stokes[*, *, 1]) + q_abs_error = reform(stokes_abs_error[*, *, 1]) + u = reform(stokes[*, *, 2]) + u_abs_error = reform(stokes_abs_error[*, *, 2]) + + ; compute the tb from the dark-subtracted stokes i and apply other calibrations + + journal, 'Calibrating total brightness...' + + tb_image = i + tb_error = (i_abs_error / i) ^ 2 + tb_quality_matrix = quality_matrix + tb_image = metis_flat_field(tb_image, header, error = tb_error, quality_matrix = tb_quality_matrix, cal_pack, history = tb_history) + tb_image = metis_vignetting(tb_image, header, error = tb_error, quality_matrix = tb_quality_matrix, cal_pack, history = tb_history) + tb_image = metis_rad_cal(tb_image, header, error = tb_error, quality_matrix = tb_quality_matrix, cal_pack, /polarimetric, history = tb_history) + + ; compute the pb from the stokes q and u and apply other calibrations + + journal, 'Calibrating polarized brightness...' + + pb_image = sqrt(q ^ 2 + u ^ 2) + pb_error = (q ^ 2 * q_abs_error ^ 2 + u ^ 2 * u_abs_error ^ 2) / pb_image ^ 4 + pb_quality_matrix = quality_matrix + pb_image = metis_flat_field(pb_image, header, error = pb_error, quality_matrix = pb_quality_matrix, cal_pack, history = pb_history) + pb_image = metis_vignetting(pb_image, header, error = pb_error, quality_matrix = pb_quality_matrix, cal_pack, history = pb_history) + pb_image = metis_rad_cal(pb_image, header, error = pb_error, quality_matrix = pb_quality_matrix, cal_pack, /polarimetric, history = pb_history) + + ; ==================================== + ; for simple radiometric calibration + ; cal_pack.vl_channel.cal_units = 'DN/s' + ; ==================================== + + ; compute the polarization angle from the stokes q and u + + pol_angle = 0.5d0 * atan(stokes[*, *, 2], stokes[*, *, 1]) + pol_angle_error = (q ^ 2 * u_abs_error ^ 2 + u ^ 2 * q_abs_error ^ 2) / (2d0 * pb_image ^ 4) / pol_angle ^ 2 + + pol_angle = pol_angle * !radeg + + journal, 'Polarization angle correctly computed.' + + ; definitions for the primary header + ; version of the fits file + + version = string(input.l2_version, format = '(I02)') + + ; creation and acquisition times in utc + + date = date_conv(systime(/julian, /utc), 'FITS') + + ; adjust the primary header + + fxaddpar, primary_header, 'PARENT', strjoin(file_basename(input.file_name), ', ') + fxaddpar, primary_header, 'LEVEL', 'L2' + fxaddpar, primary_header, 'CREATOR', 'metis_l2_prep_vl_polariz.pro' + fxaddpar, primary_header, 'VERS_SW', input.sw_version + fxaddpar, primary_header, 'VERS_CAL', cal_pack.version + fxaddpar, primary_header, 'VERSION', version + fxaddpar, primary_header, 'DATE', date, 'date and time of FITS file creation' + fxaddpar, primary_header, 'DATE-BEG', header.date_beg, 'start time of observation' + fxaddpar, primary_header, 'DATE-OBS', header.date_obs, 'same as DATE-BEG' + fxaddpar, primary_header, 'DATE-AVG', header.date_avg, 'average time of observation' + fxaddpar, primary_header, 'DATE-END', header.date_end, 'end time of observation' + fxaddpar, primary_header, 'OBT_BEG', header.obt_beg, 'start acquisition time in on-board time', format = 'F0.5' + fxaddpar, primary_header, 'OBT_END', header.obt_end, 'end acquisition time in on-board time', format = 'F0.5' + fxaddpar, primary_header, 'TELAPSE', telapse, '[s] elapsed time between beginning and end of observation' + fxaddpar, primary_header, 'WAVEBAND', cal_pack.vl_channel.name + fxaddpar, primary_header, 'XPOSURE', header.xposure + fxaddpar, primary_header, 'NSUMEXP', header.nsumexp + fxaddpar, primary_header, 'TSENSOR', header.tsensor + fxaddpar, primary_header, 'PMPTEMP', header.pmptemp + + ; fix planning info keywords + + if header.soopname.startswith('unknown') then soopname = 'none' else soopname = header.soopname + if header.obs_mode.startswith('unknown') then obs_mode = 'none' else obs_mode = header.obs_mode + if soopname eq 'none' then sooptype = 'none' else sooptype = header.sooptype + if obs_mode eq 'none' then obs_type = 'none' else obs_type = header.obs_type + if soopname eq 'none' and obs_mode eq 'none' then obs_id = 'none' else obs_id = header.obs_id + if obs_mode eq 'none' then obs_mode = 'METIS_GENERIC_OBS' + + fxaddpar, primary_header, 'SOOPNAME', soopname + fxaddpar, primary_header, 'SOOPTYPE', sooptype + fxaddpar, primary_header, 'OBS_MODE', obs_mode + fxaddpar, primary_header, 'OBS_TYPE', obs_type + fxaddpar, primary_header, 'OBS_ID', obs_id + + ; append wcs keywords + + wcs = metis_wcs(header, cal_pack, ref_detector = ref_detector) + foreach element, wcs do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' + + ; append solar ephemeris keywords + + ephemeris = solo_get_ephemeris(header, cal_pack) + foreach element, ephemeris do fxaddpar, primary_header, element.name, element.value, element.comment, before = 'DATATYPE' + + ; update the comment and history keywords + + fxaddpar, primary_header, 'COMMENT', 'Uncertainty matrix in the FITS extension is preliminary.' + fxaddpar, primary_header, 'COMMENT', 'Rotate CROTA degrees counter-clockwise to have Solar North up.' + + history = ['Update WCS and solar ephemeris:', ' SKD version = ' + kernel_version] + + tb_history = [tb_history, history] + pb_history = [pb_history, history] + + ; delete useless keywords + + sxdelpar, primary_header, 'BLANK' + sxdelpar, primary_header, 'OBJ_CNT' + sxdelpar, primary_header, 'POL_ID' + sxdelpar, primary_header, 'SNRMIN' + sxdelpar, primary_header, 'SNRMAX' + sxdelpar, primary_header, 'NB_IMG' + sxdelpar, primary_header, 'SN_MEAN1' + sxdelpar, primary_header, 'SN_VAR1' + sxdelpar, primary_header, 'SN_MEAN2' + sxdelpar, primary_header, 'SN_VAR2' + sxdelpar, primary_header, 'SN_MEAN3' + sxdelpar, primary_header, 'SN_VAR3' + sxdelpar, primary_header, 'SN_MEAN4' + sxdelpar, primary_header, 'SN_VAR4' + sxdelpar, primary_header, 'SN_MEAN5' + sxdelpar, primary_header, 'SN_VAR5' + sxdelpar, primary_header, 'N' + + date_beg_string = strmid(header.date_beg, 0, 19) + date_beg_string = date_beg_string.replace('-', '') + date_beg_string = date_beg_string.replace(':', '') + + ; array of output file names + + out_file_name = strarr(4) + + ; keywords specific for polarized brightness images + + primary_pb_header = primary_header + + ; name of the fits file + + file_name = 'solo_L2_metis-vl-pb_' + date_beg_string + '_V' + version + '.fits' + out_file_name[0] = 'output/' + file_name + + fxaddpar, primary_pb_header, 'FILENAME', file_name + fxaddpar, primary_pb_header, 'BTYPE', 'VL polarized brightness' + fxaddpar, primary_pb_header, 'BUNIT', cal_pack.vl_channel.cal_units + fxaddpar, primary_pb_header, 'DATAMIN', min(pb_image, /nan) + fxaddpar, primary_pb_header, 'DATAMAX', max(pb_image, /nan) + + ; add the history keyword + + for k = 0, n_elements(pb_history) - 1 do fxaddpar, primary_pb_header, 'HISTORY', pb_history[k] + fxaddpar, primary_pb_header, 'HISTORY', 'L2 FITS file created on ' + date + + ; add checksum and datasum to the fits header + + if not ref_detector then pb_image = metis_rectify(pb_image, 'VL') + fits_add_checksum, primary_pb_header, float(pb_image) + mwrfits, float(pb_image), out_file_name[0], primary_pb_header, /no_comment, /create, /silent + + journal, 'Polarized-brightness FITS file created:' + journal, ' file name = ' + file_basename(out_file_name[0]) + + ; add the extension with the quality matrix + + base_header = primary_pb_header + sxdelpar, base_header, 'SIMPLE' + sxdelpar, base_header, 'EXTEND' + sxdelpar, base_header, 'DATASUM' + sxdelpar, base_header, 'CHECKSUM' + sxdelpar, base_header, 'COMMENT' + sxdelpar, base_header, 'HISTORY' + + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Pixel quality' + fxaddpar, extension_header, 'BUNIT', 'None' + fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) + fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' + fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' + fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' + fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' + if not ref_detector then pb_quality_matrix = metis_rectify(pb_quality_matrix, 'VL') + fits_add_checksum, extension_header, float(pb_quality_matrix) + mwrfits, float(pb_quality_matrix), out_file_name[0], extension_header, /no_comment, /silent + + journal, 'Quality-matrix extension correctly added.' + + ; add the extension with the error matrix + + if not ref_detector then pb_error = metis_rectify(pb_error, 'VL') + error_matrix = pb_image * 0. ; pb_image * sqrt(pb_error) + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Absolute error' + fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) + fits_add_checksum, extension_header, float(error_matrix) + mwrfits, float(error_matrix), out_file_name[0], extension_header, /no_comment, /silent + + journal, 'Error-matrix extension correctly added.' + + ; fix some header keywords + + fix_fits_header, out_file_name[0] + + ; keywords specific for total brightness images + + primary_tb_header = primary_header + + ; name of the fits file + + file_name = 'solo_L2_metis-vl-tb_' + date_beg_string + '_V' + version + '.fits' + out_file_name[1] = 'output/' + file_name + + fxaddpar, primary_tb_header, 'FILENAME', file_name + fxaddpar, primary_tb_header, 'BTYPE', 'VL total brightness' + fxaddpar, primary_tb_header, 'BUNIT', cal_pack.vl_channel.cal_units + fxaddpar, primary_tb_header, 'DATAMIN', min(tb_image, /nan) + fxaddpar, primary_tb_header, 'DATAMAX', max(tb_image, /nan) + + ; add the history keyword + + for k = 0, n_elements(tb_history) - 1 do fxaddpar, primary_tb_header, 'HISTORY', tb_history[k] + fxaddpar, primary_tb_header, 'HISTORY', 'L2 FITS file created on ' + date + + ; add checksum and datasum to the fits header + + if not ref_detector then tb_image = metis_rectify(tb_image, 'VL') + fits_add_checksum, primary_tb_header, float(tb_image) + mwrfits, float(tb_image), out_file_name[1], primary_tb_header, /no_comment, /create, /silent + + journal, 'Total-brightness FITS file created:' + journal, ' file name = ' + file_basename(out_file_name[1]) + + ; add the extension with the quality matrix + + base_header = primary_tb_header + sxdelpar, base_header, 'SIMPLE' + sxdelpar, base_header, 'EXTEND' + sxdelpar, base_header, 'DATASUM' + sxdelpar, base_header, 'CHECKSUM' + sxdelpar, base_header, 'COMMENT' + sxdelpar, base_header, 'HISTORY' + + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Pixel quality' + fxaddpar, extension_header, 'BUNIT', 'None' + fxaddpar, extension_header, 'DATAMIN', min(tb_quality_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(tb_quality_matrix, /nan) + fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' + fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' + fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' + fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' + if not ref_detector then tb_quality_matrix = metis_rectify(tb_quality_matrix, 'VL') + fits_add_checksum, extension_header, float(tb_quality_matrix) + mwrfits, float(tb_quality_matrix), out_file_name[1], extension_header, /no_comment, /silent + + journal, 'Quality-matrix extension correctly added.' + + ; add the extension with the error matrix + + if not ref_detector then tb_error = metis_rectify(tb_error, 'VL') + error_matrix = tb_image * 0. ; tb_image * sqrt(tb_error) + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Absolute error' + fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) + fits_add_checksum, extension_header, float(error_matrix) + mwrfits, float(error_matrix), out_file_name[1], extension_header, /no_comment, /silent + + journal, 'Error-matrix extension correctly added.' + + ; fix some header keywords + + fix_fits_header, out_file_name[1] + + ; keywords specific for polarization-angle images + + primary_polangle_header = primary_header + + ; name of the fits file + + file_name = 'solo_L2_metis-vl-pol-angle_' + date_beg_string + '_V' + version + '.fits' + out_file_name[2] = 'output/' + file_name + + fxaddpar, primary_polangle_header, 'FILENAME', file_name + fxaddpar, primary_polangle_header, 'BTYPE', 'VL polarization angle' + fxaddpar, primary_polangle_header, 'BUNIT', 'deg' + fxaddpar, primary_polangle_header, 'DATAMIN', min(pol_angle, /nan) + fxaddpar, primary_polangle_header, 'DATAMAX', max(pol_angle, /nan) + + ; add the history keyword + + for k = 0, n_elements(pb_history) - 1 do fxaddpar, primary_polangle_header, 'HISTORY', pb_history[k] + fxaddpar, primary_polangle_header, 'HISTORY', 'L2 FITS file created on ' + date + + ; add checksum and datasum to the fits header + + if not ref_detector then pol_angle = metis_rectify(pol_angle, 'VL') + fits_add_checksum, primary_polangle_header, float(pol_angle) + mwrfits, float(pol_angle), out_file_name[2], primary_polangle_header, /no_comment, /create, /silent + + journal, 'Polarization-angle FITS file created:' + journal, ' file name = ' + file_basename(out_file_name[2]) + + ; add the extension with the quality matrix + + base_header = primary_polangle_header + sxdelpar, base_header, 'SIMPLE' + sxdelpar, base_header, 'EXTEND' + sxdelpar, base_header, 'DATASUM' + sxdelpar, base_header, 'CHECKSUM' + sxdelpar, base_header, 'COMMENT' + sxdelpar, base_header, 'HISTORY' + + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Pixel quality' + fxaddpar, extension_header, 'BUNIT', 'None' + fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) + fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' + fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' + fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' + fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' + if not ref_detector then pol_angle_quality_matrix = metis_rectify(quality_matrix, 'VL') + fits_add_checksum, extension_header, float(pol_angle_quality_matrix) + mwrfits, float(pol_angle_quality_matrix), out_file_name[2], extension_header, /no_comment, /silent + + journal, 'Quality-matrix extension correctly added.' + + ; add the extension with the error matrix + + if not ref_detector then pol_angle_error = metis_rectify(pol_angle_error, 'VL') + error_matrix = pol_angle * 0. ; pol_angle * sqrt(pol_angle_error) + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Absolute error' + fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) + fits_add_checksum, extension_header, float(error_matrix) + mwrfits, float(error_matrix), out_file_name[2], extension_header, /no_comment, /silent + + journal, 'Error-matrix extension correctly added.' + + ; fix some header keywords + + fix_fits_header, out_file_name[2] + + ; management of stokes images + + journal, 'Calibrating Stokes parameter I...' + + i = reform(stokes_subdark[*, *, 0]) + i = metis_flat_field(i, header, cal_pack) + i = metis_vignetting(i, header, cal_pack) + i = metis_rad_cal(i, header, cal_pack, /polarimetric) + + journal, 'Calibrating Stokes parameter Q...' + + q = reform(stokes[*, *, 1]) + q = metis_flat_field(q, header, cal_pack) + q = metis_vignetting(q, header, cal_pack) + q = metis_rad_cal(q, header, cal_pack, /polarimetric) + + journal, 'Calibrating Stokes parameter U...' + + u = reform(stokes[*, *, 2]) + u = metis_flat_field(u, header, cal_pack) + u = metis_vignetting(u, header, cal_pack) + u = metis_rad_cal(u, header, cal_pack, /polarimetric) + + ; keywords specific for stokes images + + primary_stokes_header = primary_header + + ; name of the fits file + + file_name = 'solo_L2_metis-vl-stokes_' + date_beg_string + '_V' + version + '.fits' + out_file_name[3] = 'output/' + file_name + + fxaddpar, primary_stokes_header, 'FILENAME', file_name + fxaddpar, primary_stokes_header, 'BTYPE', 'Stokes I' + fxaddpar, primary_stokes_header, 'BUNIT', cal_pack.vl_channel.cal_units + fxaddpar, primary_stokes_header, 'DATAMIN', min(i, /nan) + fxaddpar, primary_stokes_header, 'DATAMAX', max(i, /nan) + + ; add the history keyword + + for k = 0, n_elements(tb_history) - 1 do fxaddpar, primary_stokes_header, 'HISTORY', tb_history[k] + fxaddpar, primary_stokes_header, 'HISTORY', 'L2 FITS file created on ' + date + + ; add checksum and datasum to the fits header + if not ref_detector then i = metis_rectify(i, 'VL') + fits_add_checksum, primary_stokes_header, float(i) + mwrfits, float(i), out_file_name[3], primary_stokes_header, /no_comment, /create, /silent + + journal, 'Stokes parameters FITS file created:' + journal, ' file name = ' + file_basename(out_file_name[3]) + + ; add the extension with the stokes q image + + base_header = primary_stokes_header + sxdelpar, base_header, 'SIMPLE' + sxdelpar, base_header, 'EXTEND' + sxdelpar, base_header, 'DATASUM' + sxdelpar, base_header, 'CHECKSUM' + + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Stokes Q', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Stokes Q' + fxaddpar, extension_header, 'BUNIT', cal_pack.vl_channel.cal_units + fxaddpar, extension_header, 'DATAMIN', min(q, /nan) + fxaddpar, extension_header, 'DATAMAX', max(q, /nan) + sxdelpar, extension_header, 'EXTEND' + if not ref_detector then q = metis_rectify(q, 'VL') + fits_add_checksum, extension_header, float(q) + mwrfits, float(q), out_file_name[3], extension_header, /no_comment, /silent + + journal, 'Q parameter extension correctly added.' + + ; add the extension with the stokes u image + + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Stokes U', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Stokes U' + fxaddpar, extension_header, 'BUNIT', cal_pack.vl_channel.cal_units + fxaddpar, extension_header, 'DATAMIN', min(u, /nan) + fxaddpar, extension_header, 'DATAMAX', max(u, /nan) + sxdelpar, extension_header, 'EXTEND' + if not ref_detector then u = metis_rectify(u, 'VL') + fits_add_checksum, extension_header, float(u) + mwrfits, float(u), out_file_name[3], extension_header, /no_comment, /silent + + journal, 'U parameter extension correctly added.' + + sxdelpar, base_header, 'COMMENT' + sxdelpar, base_header, 'HISTORY' + + ; add the extension with the quality matrix + + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Quality matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Pixel quality' + fxaddpar, extension_header, 'BUNIT', 'None' + fxaddpar, extension_header, 'DATAMIN', min(quality_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(quality_matrix, /nan) + fxaddpar, extension_header, 'COMMENT', 'Quality matrix values:' + fxaddpar, extension_header, 'COMMENT', ' NaN = saturated or null L0 pixel counts' + fxaddpar, extension_header, 'COMMENT', ' 0 = unreliable pixel value' + fxaddpar, extension_header, 'COMMENT', ' 1 = good pixel' + if not ref_detector then quality_matrix = metis_rectify(quality_matrix, 'VL') + fits_add_checksum, extension_header, float(quality_matrix) + mwrfits, float(quality_matrix), out_file_name[3], extension_header, /no_comment, /silent + + journal, 'Quality-matrix extension correctly added.' + + ; add the extension with the error matrix + + if not ref_detector then error_matrix = metis_rectify(error_matrix, 'VL') + error_matrix = tb_image * 0. ; tb_image * sqrt(tb_error) + extension_header = base_header + fxaddpar, extension_header, 'XTENSION', 'IMAGE', 'image extension', before = 'BITPIX' + fxaddpar, extension_header, 'PCOUNT', 0, 'parameter count', before = 'LONGSTRN' + fxaddpar, extension_header, 'GCOUNT', 1, 'group count', before = 'LONGSTRN' + fxaddpar, extension_header, 'EXTNAME', 'Error matrix', 'extension name', before = 'LONGSTRN' + fxaddpar, extension_header, 'BTYPE', 'Absolute error' + fxaddpar, extension_header, 'DATAMIN', min(error_matrix, /nan) + fxaddpar, extension_header, 'DATAMAX', max(error_matrix, /nan) + fits_add_checksum, extension_header, float(error_matrix) + mwrfits, float(error_matrix), out_file_name[3], extension_header, /no_comment, /silent + + journal, 'Error-matrix extension correctly added.' + + ; fix some header keywords + + fix_fits_header, out_file_name[3] + + ; write the auxiliary information file + + output = { $ + file_name: out_file_name, $ + l1_file_name: input.file_name, $ + log_file_name: 'output/metis_l2_prep_log.txt' $ + } + + json_write, output, 'output/contents.json' + + ; unload the spice kernels + + load_spice_kernels, kernel_list = kernel_list, /unload + + journal, 'SPICE kernel files unloaded.' + + ; close the log + + journal, 'Exiting without errors.' + journal + + exit, status = 0 +end \ No newline at end of file