diff --git a/k8s/base/config.json b/k8s/base/config.json index 9380f978..83312d32 100644 --- a/k8s/base/config.json +++ b/k8s/base/config.json @@ -4,5 +4,6 @@ "ptrObserveUrl": "https://ptr-observe.lco.global", "observationPortalUrl": "https://observe.lco.global/api/", "thumbnailServiceUrl": "https://thumbnails.lco.global/", + "simbad2kUrl": "https://simbad2k.lco.global/", "archiveType": "lco" } diff --git a/public/config/config.json b/public/config/config.json index 4f94830e..f3bcd130 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -3,5 +3,6 @@ "datalabArchiveApiUrl": "https://archive-api.lco.global/", "observationPortalUrl": "https://observe.lco.global/api/", "thumbnailServiceUrl": "https://thumbnails.lco.global/", + "simbad2kUrl": "https://simbad2k.lco.global/", "archiveType": "lco" } diff --git a/src/App.vue b/src/App.vue index eabda65b..149542bf 100644 --- a/src/App.vue +++ b/src/App.vue @@ -22,6 +22,7 @@ onMounted(async () => { configurationStore.observationPortalUrl = config.observationPortalUrl configurationStore.datalabArchiveApiUrl = config.datalabArchiveApiUrl configurationStore.thumbnailServiceUrl = config.thumbnailServiceUrl || '' + configurationStore.simbad2kUrl = config.simbad2kUrl || '' configurationStore.archiveType = config.archiveType || 'ptr' configurationStore.isConfigLoaded = true } diff --git a/src/components/Analysis/LightCurvePlot.vue b/src/components/Analysis/LightCurvePlot.vue index ad1ec59f..dc7c03c1 100644 --- a/src/components/Analysis/LightCurvePlot.vue +++ b/src/components/Analysis/LightCurvePlot.vue @@ -14,7 +14,7 @@ const DECIMAL_PLACES = 4 const chartData = computed(() => { const magTimeSeries = analysisStore.magTimeSeries - const dates = magTimeSeries.map(({ julian_date }) => julian_date) + const dates = magTimeSeries.map(({ observation_date }) => observation_date) const magnitudes = magTimeSeries.map(({ mag }) => mag.toFixed(DECIMAL_PLACES)) // Error bars as [lowerBound, upperBound] const errors = magTimeSeries.map(({ mag, magerr }) => { diff --git a/src/components/Analysis/PeriodPlot.vue b/src/components/Analysis/PeriodPlot.vue index 1097ab4f..753dccd4 100644 --- a/src/components/Analysis/PeriodPlot.vue +++ b/src/components/Analysis/PeriodPlot.vue @@ -21,7 +21,7 @@ const probabilityChipColor = computed(() => { // Periodogram data formatted for chartJs const chartData = computed(() => { - const periodogram = analysisStore.variableStarData.magPeriodogram + const periodogram = analysisStore.variableStarData.magPhasedLightCurve const period1 = periodogram.map(( p ) => ({ x: p.phase, y: p.mag})) const period2 = periodogram.map(( p ) => ({ x: p.phase + 1, y: p.mag})) @@ -35,7 +35,7 @@ const chartData = computed(() => { }) watch(() => analysisStore.variableStarData, () => { - periodChart && analysisStore.variableStarData.magPeriodogram ? updateChart() : createChart() + periodChart && analysisStore.variableStarData.magPhasedLightCurve ? updateChart() : createChart() }, { deep: true}) function updateChart() { diff --git a/src/components/Analysis/VariableStarDialog.vue b/src/components/Analysis/VariableStarDialog.vue index fa605858..7b4a737d 100644 --- a/src/components/Analysis/VariableStarDialog.vue +++ b/src/components/Analysis/VariableStarDialog.vue @@ -31,13 +31,12 @@ const matchingImages = ref({ count: 0, results: [] }) // When date range changes, queries the archive and updates tooltip with how many images are available watch([startDate, endDate], () => { const { datalabArchiveApiUrl } = configStore - const { imageFilter, imageProposalId } = analysisStore + const { imageFilter } = analysisStore const { ra, dec } = props.coords - const queryUrl = datalabArchiveApiUrl + 'frames/?' + + const queryUrl = datalabArchiveApiUrl + 'frames/?force_count=true&reduction_level=91&' + `start=${ISOStartDate.value}&end=${ISOEndDate.value}&` + `primary_optical_element=${imageFilter}&` + - `proposal_id=${imageProposalId}&` + `covers=POINT(${ra} ${dec})` fetchApiCall({url: queryUrl, method: 'GET', diff --git a/src/components/DataSession/DataSession.vue b/src/components/DataSession/DataSession.vue index 7bd646de..89e953c0 100644 --- a/src/components/DataSession/DataSession.vue +++ b/src/components/DataSession/DataSession.vue @@ -1,13 +1,14 @@ @@ -94,10 +153,28 @@ function reloadImages(newImages) { + + + {{ props.inputDescriptions[inputKey].name }} + + + + + {{ props.inputDescriptions[inputKey].name }} Filter: + + + + + + diff --git a/src/components/DataSession/OperationPipeline.vue b/src/components/DataSession/Operation/OperationPipeline.vue similarity index 100% rename from src/components/DataSession/OperationPipeline.vue rename to src/components/DataSession/Operation/OperationPipeline.vue diff --git a/src/components/DataSession/OperationWizard.vue b/src/components/DataSession/Operation/OperationWizard.vue similarity index 72% rename from src/components/DataSession/OperationWizard.vue rename to src/components/DataSession/Operation/OperationWizard.vue index 646d7f48..b17707e3 100644 --- a/src/components/DataSession/OperationWizard.vue +++ b/src/components/DataSession/Operation/OperationWizard.vue @@ -2,10 +2,10 @@ import { ref, onMounted, computed} from 'vue' import { fetchApiCall, handleError } from '@/utils/api' import { rgbFilterMap, colorRGBMap } from '@/utils/color' -import MultiImageInputSelector from '@/components/DataSession/MultiImageInputSelector.vue' +import MultiImageInputSelector from '@/components/DataSession/Operation/MultiImageInputSelector.vue' import WizardScalingPage from '@/components/Global/Scaling/WizardScalingPage.vue' +import SourceInputWidget from './SourceInputWidget.vue' import { useConfigurationStore } from '@/stores/configuration' - /* This component is a step wizard for configuring the input of a new operation to the data session. It has three main pages: @@ -15,8 +15,8 @@ import { useConfigurationStore } from '@/stores/configuration' */ const props = defineProps({ - // Available images to select from - images: { + // Available operation data (images or output data) to select from + data: { type: Array, required: true } @@ -40,8 +40,43 @@ const WIZARD_PAGES = { // Start on the select operation page const page = ref(WIZARD_PAGES.SELECT) +const images = computed(() => { + return props.data.filter(image => image.basename) +}) + const inputDescriptions = computed(() => { return selectedOperation.value.inputs }) +// Groups of 2 elements to set for inputDescriptions that are not image based +const groupedInputDescriptions = computed(() => { + const typesToGroup = ['string', 'float', 'int'] + let groups = [] + let currentGroup = {} + if (inputDescriptions.value) { + for( const [inputKey, inputDescription] of Object.entries(inputDescriptions.value)) { + if (typesToGroup.includes(inputDescription.type)) { + currentGroup[inputKey] = inputDescription + if (Object.keys(currentGroup).length == 2) { + groups.push({...currentGroup}) + currentGroup = {} + } + } + } + } + return groups +}) + +const sourceInputDescriptions = computed(() => { + let sourceDescriptions = {} + if (inputDescriptions.value) { + for ( const [inputKey, inputDescription] of Object.entries(inputDescriptions.value)) { + if (inputDescription.type == 'source') { + sourceDescriptions[inputKey] = {...inputDescription} + } + } + } + return sourceDescriptions +}) + // Text for the forward button changes based on the current page const goForwardText = computed(() => { if (page.value == WIZARD_PAGES.SELECT) { @@ -88,11 +123,20 @@ const disableGoForward = computed(() => { const isInputComplete = computed(() => { for (const inputKey in operationInputs.value) { const input = operationInputs.value[inputKey] - const minimum = inputDescriptions.value[inputKey].minimum - const lessThanMinimumInputs = minimum ? input.length < minimum : input.length == 0 - if ( input === undefined || input === null || lessThanMinimumInputs) { + if ( input === undefined || input === null) { return false } + if (inputDescriptions.value[inputKey].type == 'source') { + if (!input.ra || !input.dec) { + return false + } + } + if (Array.isArray(input)) { + const minimum = inputDescriptions.value[inputKey].minimum || 0 + if (input.length < minimum) { + return false + } + } } return true }) @@ -129,7 +173,7 @@ function goForward() { // if there are no images for a filter required by the operation, do not proceed for (const inputKey in inputDescriptions.value) { const inputField = inputDescriptions.value[inputKey] - if (inputField.type == 'fits' && props.images.length == 0){ + if (inputField.type == 'fits' && images.value.length == 0){ return } } @@ -182,7 +226,7 @@ function selectOperation(name) { * Default: start with 3 channels (RGB) and try to preselect images for those */ operationInputs.value[key] = Object.entries(rgbFilterMap).map(([color, filters]) => { - const preselectedImage = props.images.find(image => filters.includes(image.filter.toLowerCase())) + const preselectedImage = images.value.find(image => filters.includes(image.filter.toLowerCase())) let output = {} if (preselectedImage) { output = {...preselectedImage} @@ -191,9 +235,22 @@ function selectOperation(name) { return output }) } - else { + else if (value.type == 'fits') { operationInputs.value[key] = [] } + else if (value.type == 'source') { + operationInputs.value[key] = {} + } + else { + operationInputs.value[key] = null + } + } +} + +// Set images to the inputKey - only for non-color images +function setImages(inputKey, filteredImages) { + if (!imageInputDescriptions.value[inputKey].color_picker) { + operationInputs.value[inputKey] = [...filteredImages] } } @@ -308,30 +365,66 @@ function updateScaling(channelIndex, zmin, zmax) { v-show="page == WIZARD_PAGES.CONFIGURE" class="wizard-card" > + + + + + + + + + + + + - - - diff --git a/src/components/DataSession/Operation/SourceInputWidget.vue b/src/components/DataSession/Operation/SourceInputWidget.vue new file mode 100644 index 00000000..6ee27f2b --- /dev/null +++ b/src/components/DataSession/Operation/SourceInputWidget.vue @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + diff --git a/src/components/Global/DataOutput.vue b/src/components/Global/DataOutput.vue new file mode 100644 index 00000000..c99a08a7 --- /dev/null +++ b/src/components/Global/DataOutput.vue @@ -0,0 +1,141 @@ + + + + + + + {{ props.operationOutput.operationName }} + + + + + {{ props.operationOutput.period.toFixed(4) }} days + + + + + + + + {{ title }} + + + + + + + + diff --git a/src/components/Global/ImageGrid.vue b/src/components/Global/ImageGrid.vue index 48e84b1d..933cb2af 100644 --- a/src/components/Global/ImageGrid.vue +++ b/src/components/Global/ImageGrid.vue @@ -4,7 +4,7 @@ import { useThumbnailsStore } from '@/stores/thumbnails' import { useConfigurationStore } from '@/stores/configuration' import { useAlertsStore } from '@/stores/alerts' import ThumbnailImage from '@/components/Global/ThumbnailImage.vue' -import AnalysisView from '../../views/AnalysisView.vue' +import ImageAnalysisView from '../../views/ImageAnalysisView.vue' const props = defineProps({ images: { @@ -97,7 +97,7 @@ watch(() => props.images, () => { v-model="showAnalysisDialog" fullscreen > - diff --git a/src/components/Global/OperationOutputGrid.vue b/src/components/Global/OperationOutputGrid.vue new file mode 100644 index 00000000..a3d12c47 --- /dev/null +++ b/src/components/Global/OperationOutputGrid.vue @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Project/CreateSessionDialog.vue b/src/components/Project/CreateSessionDialog.vue index f5775eb2..b85b8b71 100644 --- a/src/components/Project/CreateSessionDialog.vue +++ b/src/components/Project/CreateSessionDialog.vue @@ -41,8 +41,9 @@ async function addImagesToExistingSession(session){ 'basename': image.basename.replace('-small', '') || image.basename.replace('-large', ''), 'id': image.id, 'url': image.url, - 'filter': image.FILTER, - 'target_name': image.target_name + 'filter': image.primary_optical_element, + 'target_name': image.target_name, + 'observation_date': image.observation_date }))] const requestBody = { 'name': session.name, @@ -62,8 +63,9 @@ async function createNewDataSession(){ 'basename': image.basename.replace('-small', '') || image.basename.replace('-large', ''), 'id': image.id, 'url': image.url, - 'filter': image.FILTER, - 'target_name': image.target_name + 'filter': image.primary_optical_element, + 'target_name': image.target_name, + 'observation_date': image.observation_date })) const requestBody = { 'name': newSessionName.value, diff --git a/src/components/Project/ImageList.vue b/src/components/Project/ImageList.vue index 85328d15..79059c97 100644 --- a/src/components/Project/ImageList.vue +++ b/src/components/Project/ImageList.vue @@ -5,7 +5,7 @@ import { useAlertsStore } from '@/stores/alerts' import { useConfigurationStore } from '@/stores/configuration' import { siteIDToName } from '@/utils/common' import FilterBadge from '@/components/Global/FilterBadge.vue' -import ImageAnalyzer from '../../views/AnalysisView.vue' +import ImageAnalyzer from '../../views/ImageAnalysisView.vue' const props = defineProps({ images: { diff --git a/src/stores/analysis.js b/src/stores/analysis.js index 79da6a5f..0607205d 100644 --- a/src/stores/analysis.js +++ b/src/stores/analysis.js @@ -29,7 +29,7 @@ export const useAnalysisStore = defineStore('analysis', { variableStarData: { loading: false, // flag to indicate if variable star data is loading targetCoords: 0, // target coordinates for the variable star - magPeriodogram: [], // magTimeSeries sorted by phase + magPhasedLightCurve: [], // magTimeSeries sorted by phase period: 0, // period of the variable star falseAlarmProbability: 0, // false alarm probability for the variable star fluxFallback: false, // flag to indicate if flux fallback is used @@ -38,8 +38,8 @@ export const useAnalysisStore = defineStore('analysis', { }), getters: { // General - imageProposalId: (state) => { return state.image?.proposal_id}, - imageFilter: (state) => { return state.image?.FILTER }, + imageProposalId: (state) => { return state.image?.proposal_id || state.headerData?.PROPID }, + imageFilter: (state) => { return state.image?.filter || state.headerData?.FILTER }, loading: (state) => { return state.imageScaleLoading || state.variableStarData.loading }, // Histogram Editing imageScaleReady: (state) => state.imageWidth && state.imageHeight && state.rawData && state.zmin != null && state.zmax != null, @@ -150,7 +150,7 @@ export const useAnalysisStore = defineStore('analysis', { this.variableStarData = { loading: false, targetCoords: target_coords, - magPeriodogram: [], + magPhasedLightCurve: [], period: period, falseAlarmProbability: fap, fluxFallback: flux_fallback, @@ -160,7 +160,7 @@ export const useAnalysisStore = defineStore('analysis', { foldPeriod(this.magTimeSeries, this.variableStarData.period) // Sort the light curve data by phase - this.variableStarData.magPeriodogram = [...this.magTimeSeries].sort((a, b) => a.phase - b.phase) + this.variableStarData.magPhasedLightCurve = [...this.magTimeSeries].sort((a, b) => a.phase - b.phase) } this.variableStarData.loading = false diff --git a/src/views/DataAnalysisView.vue b/src/views/DataAnalysisView.vue new file mode 100644 index 00000000..dc16a42c --- /dev/null +++ b/src/views/DataAnalysisView.vue @@ -0,0 +1,67 @@ + + + + + + + + + + + + + diff --git a/src/views/AnalysisView.vue b/src/views/ImageAnalysisView.vue similarity index 97% rename from src/views/AnalysisView.vue rename to src/views/ImageAnalysisView.vue index c03250aa..67bc1d2a 100644 --- a/src/views/AnalysisView.vue +++ b/src/views/ImageAnalysisView.vue @@ -55,7 +55,7 @@ const filteredCatalog = computed(() => { const sideChartItems = computed(() => { const chartItems = [] - if (analysisStore.variableStarData.magPeriodogram?.length) chartItems.push('Periodogram') + if (analysisStore.variableStarData.magPhasedLightCurve?.length) chartItems.push('Phased Light Curve') if (analysisStore.magTimeSeries?.length) chartItems.push('Light Curve') if (lineProfile.value?.length) chartItems.push('Line Profile') return chartItems @@ -115,7 +115,7 @@ function handleAnalysisOutput(response, action, action_callback){ case 'variable-star': analysisStore.setVariableStarData(response) // Default to periodogram if available, otherwise light curve - analysisStore.variableStarData.period ? sideChart.value = 'Periodogram' : sideChart.value = 'Light Curve' + analysisStore.variableStarData.period ? sideChart.value = 'Phased Light Curve' : sideChart.value = 'Light Curve' break case 'get-tif': // ImageDownloadMenu.vue downloadFile() @@ -278,7 +278,7 @@ function updateScaling(min, max){ :end-coords="endCoords" :position-angle="positionAngle" /> - +
{{ props.operationOutput.operationName }}
{{ props.operationOutput.period.toFixed(4) }} days
+ {{ title }} +