Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions k8s/base/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
1 change: 1 addition & 0 deletions public/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
1 change: 1 addition & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Analysis/LightCurvePlot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Analysis/PeriodPlot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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}))
Expand All @@ -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() {
Expand Down
5 changes: 2 additions & 3 deletions src/components/Analysis/VariableStarDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
56 changes: 31 additions & 25 deletions src/components/DataSession/DataSession.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<script setup>
import { ref, watch, computed } from 'vue'
import OperationPipeline from './OperationPipeline.vue'
import OperationPipeline from './Operation/OperationPipeline.vue'
import OperationPipelineFlow from './OperationGraph/OperationPipelineFlow.vue'
import { fetchApiCall, handleError } from '@/utils/api.js'
import { calculateColumnSpan } from '@/utils/common'
import { useConfigurationStore } from '@/stores/configuration'
import { useAlertsStore } from '@/stores/alerts'
import ImageGrid from '@/components/Global/ImageGrid.vue'
import OperationWizard from '@/components/DataSession/OperationWizard.vue'
import OperationOutputGrid from '@/components/Global/OperationOutputGrid.vue'
import OperationWizard from '@/components/DataSession/Operation/OperationWizard.vue'
import _ from 'lodash'

const props = defineProps({
data: {
Expand All @@ -24,7 +25,7 @@ const store = useConfigurationStore()
const alertStore = useAlertsStore()

const operations = ref([...props.data.operations])
const images = ref([...props.data.input_data])
const items = ref([...props.data.input_data])
const showWizardDialog = ref(false)
const tab = ref('main')
const operationPollingTimers = {}
Expand All @@ -38,9 +39,9 @@ var operationMap = {}
// When a user clicks on an operation, we filter to only show the outputs of that operation
const filteredImages = computed(() => {
if (selectedOperation.value === -1) {
return images.value
return items.value
} else {
return images.value.filter(image => image.operation === selectedOperation.value)
return items.value.filter(item => item.operation === selectedOperation.value)
}
})

Expand All @@ -55,22 +56,27 @@ function selectOperation(operationId) {
}


// Image Equality Check
function imagesContainsFile(file) {
return images.value.some(image => image.basename == file.basename && image.source == file.source && image.operation == file.operation)
// Output Equality Check
function itemsContainsOutput(output) {
return items.value.some(item => _.isEqual(item, output))
}

// Add completed operation images to image list and attach operation metadata to identify their source
// Add completed operation output to output items list and attach operation metadata to identify their source
function addCompletedOperation(operation) {
if ('output' in operation && 'output_files' in operation.output) {
operation.output.output_files.forEach(outputFile => {
outputFile.operation = operation.id
outputFile.operationIndex = operation.index
outputFile.operationName = operation.name
if (!imagesContainsFile(outputFile)) {
images.value.push(outputFile)
const outputKeysToExpand = ['output_files', 'output_data']
if ('output' in operation){
for (const key of outputKeysToExpand) {
if (key in operation.output) {
operation.output[key].forEach(output => {
output.operation = operation.id
output.operationIndex = operation.index
output.operationName = operation.name
if (!itemsContainsOutput(output)) {
items.value.push(output)
}
})
}
})
}
}
}

Expand Down Expand Up @@ -101,9 +107,9 @@ function stopPollingById(operationIDs) {
function operationDeleted(operationIDs){
// Stop polling for deleted operations
stopPollingById(operationIDs)
// Remove outputFiles with matching operationIDs from images
images.value = images.value.filter(image => {
return !operationIDs.some(id => image.operation == id)
// Remove outputFiles with matching operationIDs from output items
items.value = items.value.filter(item => {
return !operationIDs.some(id => item.operation == id)
})
}

Expand Down Expand Up @@ -241,7 +247,7 @@ watch(
:session-id="data.id"
:operations="operations"
:selected-operation="selectedOperation"
:images="images"
:images="items"
:active="props.active"
@select-operation="selectOperation"
@close-graph="tab = 'main'"
Expand Down Expand Up @@ -273,8 +279,8 @@ watch(
@click="showWizardDialog = true"
/>
</v-col>
<image-grid
:images="filteredImages"
<operation-output-grid
:operation-outputs="filteredImages"
:column-span="calculateColumnSpan(filteredImages.length, IMAGES_PER_ROW)"
/>
</v-container>
Expand All @@ -287,7 +293,7 @@ watch(
z-index="999"
>
<operation-wizard
:images="images"
:data="items"
@close-wizard="showWizardDialog = false"
@add-operation="addOperation"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useThumbnailsStore } from '@/stores/thumbnails'
import { useConfigurationStore } from '@/stores/configuration'
import ThumbnailImage from '@/components/Global/ThumbnailImage.vue'
import {Drag,DropList} from 'vue-easy-dnd'
import { ref, onMounted, computed } from 'vue'
import { ref, onMounted, computed, watch } from 'vue'

const props = defineProps({
inputDescriptions: {
Expand All @@ -28,11 +28,12 @@ const props = defineProps({
},
})

const emit = defineEmits(['insertImage', 'removeImage', 'addChannel', 'removeChannel', 'updateChannelColor'])
const emit = defineEmits(['insertImage', 'removeImage', 'setImages', 'addChannel', 'removeChannel', 'updateChannelColor'])

const thumbnailsStore = useThumbnailsStore()
const configurationStore = useConfigurationStore()
var imageDetails = ref({})
var selectedFilter = ref({})

const colorChannelMode = computed(() => { return props.inputDescriptions.color_channels != null })

Expand All @@ -54,14 +55,54 @@ const fitsImages = computed(() => {
})
})

const filterOptionsMap = computed(() => {
let filterMap = new Map()
if (fitsImages.value) {
fitsImages.value.forEach(image => {
const filter = image.filter || image.primary_optical_element
filterMap.set(filter, (filterMap.get(filter) || 0) + 1)
})
}
return filterMap
})

onMounted(() => {
imageDetails.value = reloadImages(props.images)
})

// When inputDescriptions is set, update initial selectedFilter if need be
watch(() => props.inputDescriptions, () => updateSelectedFilter())

function mostFrequentFilterForInputKey(inputKey) {
let mostFrequentCount = 0
const filterOptions = filterOptionsForInputKey(inputKey)
let mostFrequentFilter = undefined
for (const [filter, count] of filterOptionsMap.value.entries()) {
if (filterOptions.includes(filter) && count > mostFrequentCount) {
mostFrequentCount = count
mostFrequentFilter = filter
}
}
return mostFrequentFilter
}

function filterOptionsForInputKey(inputKey) {
let filterOptions = Array.from(filterOptionsMap.value.keys())
if (props.inputDescriptions[inputKey].filter_options) {
return filterOptions.filter(item => props.inputDescriptions[inputKey].filter_options.includes(item))
}
else{
return filterOptions
}
}

// Image dragged into the selected images area
function insert(inputKey, index, event) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a suggestion, would it make sense to make this function into a utils function since other operations use insert logic?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm realizing that this component is global. Ignore!

if (inputKey !== 'all' && ! props.inputImages[inputKey].includes(event.data)) {
emit('insertImage', inputKey, event.data, index)
if (inputKey !== 'all' && !props.inputImages[inputKey].includes(event.data)) {
// Also check if the inputKey is single_filter mode and already has another filter set
if (!props.inputDescriptions[inputKey].single_filter || event.data.filter == selectedFilter.value[inputKey] || event.data.primary_optical_element == selectedFilter.value[inputKey]) {
emit('insertImage', inputKey, event.data, index)
}
}
}

Expand All @@ -86,6 +127,24 @@ function reloadImages(newImages) {
return newImageDetails
}

function updateSelectedFilter() {
if (props.inputDescriptions) {
for (const [inputKey, inputDescription] of Object.entries(props.inputDescriptions)) {
if (inputDescription.single_filter) {
selectedFilter.value[inputKey] = mostFrequentFilterForInputKey(inputKey)
updateSelectedImagesForFilter(inputKey, selectedFilter.value[inputKey])
}
}
}
}

function updateSelectedImagesForFilter(inputKey, filter) {
let filteredImages = fitsImages.value.filter(image => {
return image.filter == filter || image.primary_optical_element == filter
})
emit('setImages', inputKey, filteredImages)
}

</script>

<template>
Expand All @@ -94,10 +153,28 @@ function reloadImages(newImages) {
<v-card
v-for="(inputKey, index) in inputList"
:key="inputKey+index"
:title="props.inputDescriptions[inputKey].name"
color="var(--card-background)"
density="compact"
>
<v-card-title>
<div v-if="!props.inputDescriptions[inputKey].single_filter">
{{ props.inputDescriptions[inputKey].name }}
</div>
<div v-if="props.inputDescriptions[inputKey].single_filter">
<v-select
v-model="selectedFilter[inputKey]"
:items="filterOptionsForInputKey(inputKey)"
label="Filter"
density="compact"
variant="outlined"
@update:model-value="updateSelectedImagesForFilter(inputKey, $event)"
>
<template #prepend>
{{ props.inputDescriptions[inputKey].name }} Filter:
</template>
</v-select>
</div>
</v-card-title>
<v-card-text>
<drop-list
:items="colorChannelMode? [props.inputImages[inputKey][index]] : props.inputImages[inputKey]"
Expand Down Expand Up @@ -133,6 +210,13 @@ function reloadImages(newImages) {
/>
</template>
</drop-list>
<v-alert
v-if="props.inputDescriptions[inputKey].minimum && props.inputImages[inputKey].length < props.inputDescriptions[inputKey].minimum"
density="compact"
:text="'Requires at least ' + props.inputDescriptions[inputKey].minimum + ' images'"
type="warning"
>
</v-alert>
<v-color-picker
v-if="colorChannelMode"
:model-value="props.inputImages[inputKey][index].color"
Expand Down Expand Up @@ -234,6 +318,7 @@ function reloadImages(newImages) {
min-width: 200px;
min-height: 200px;
display: flex;
overflow-x: scroll;
}

</style>
Loading