Skip to content

Commit

Permalink
Merge pull request #133 from LCOGT/feature/source-filtering
Browse files Browse the repository at this point in the history
Feature/source filtering
  • Loading branch information
LTDakin authored Dec 13, 2024
2 parents 349ef3b + 9369acf commit 631ed36
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 62 deletions.
84 changes: 84 additions & 0 deletions src/components/Global/NonLinearSlider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script setup>
import { ref, watch } from 'vue'
/**
* Non Linear modification of the Vuetify range slider
* Props:
* - min: Minimum value of the range
* - max: Maximum value of the range
* - stepCount: Number of steps to use in the range slider, default is 200, higher values will result in a smoother slider
* - scaling: The scaling function to use, default is log10
*/
const props = defineProps({
min: {
type: Number,
default: 0,
required: true
},
max: {
type: Number,
required: true
},
stepCount: {
type: Number,
default: 200,
required: false,
},
scaling: {
type: Function,
default: (index, stepCount, max) => Math.floor(Math.pow(10, index * Math.log10(max) / stepCount)),
required: false,
}
})
const scaledRange = defineModel({ type: Array })
const customScale = ref([])
const sliderRange = ref([])
function updateRange() {
// Mapping the range slider values to the custom scale
const [start, end] = sliderRange.value
scaledRange.value = [customScale.value[start], customScale.value[end]]
}
function calcCustomScale(){
customScale.value = Array.from({length: props.stepCount}, (_, i) => props.scaling(i, props.stepCount, props.max))
// Calculation could result in last value being less than max, so we add the max value to the scale
if (customScale.value.at(-1) < props.max) {
customScale.value.push(props.max)
}
// Clips the custom scale to the min value
customScale.value.splice(0, customScale.value.findIndex((value) => value >= props.min)-1)
}
watch(() => [props.max, props.min], () => {
calcCustomScale()
// Set the initial range selection on the slider to the ends
scaledRange.value = [customScale.value[0], customScale.value[customScale.value.length-1]]
sliderRange.value = [0, customScale.value.length-1]
updateRange()
},{ immediate: true })
</script>
<template>
<v-range-slider
v-model="sliderRange"
step="1"
:max="customScale.length-1"
color="var(--light-blue)"
thumb-color="var(--dark-green)"
strict
hide-details
@update:model-value="updateRange"
/>
</template>
<style scoped>
</style>
27 changes: 14 additions & 13 deletions src/components/Global/Scaling/HistogramSlider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ watch(
variant="outlined"
step="10"
hide-details
@update:modelValue="updateLowerScale">
</v-text-field>
@update:model-value="updateLowerScale"
/>
</v-col>
<v-col cols="4">
<v-text-field
Expand All @@ -165,33 +165,34 @@ watch(
variant="outlined"
step="10"
hide-details
@update:modelValue="updateUpperScale">
</v-text-field>
@update:model-value="updateUpperScale"
/>
</v-col>
<v-col cols="4">
<v-btn
class="ml-2"
variant="outlined"
@click="zScaleImage"
>Z-Scale</v-btn>
>
Z-Scale
</v-btn>
</v-col>
</v-row>
<v-row class="ma-0">
<v-sheet class="stackSheet">
<v-sparkline
smooth=true
fill=true
:smooth="true"
:fill="true"
line-width="0.1"
height="50"
:gradient="gradient"
gradient-direction='left'
gradient-direction="left"
:model-value="props.histogram"
auto-draw
>
</v-sparkline>
/>
<v-range-slider
class="stackControls"
v-model="sliderRange"
class="stackControls"
step="1"
track-size="0"
:track-color="backgroundColor"
Expand All @@ -202,9 +203,9 @@ watch(
thumb-label="always"
strict
hide-details
@update:modelValue="updateScaleRange"
@update:model-value="updateScaleRange"
>
<template v-slot:thumb-label="{ modelValue }">
<template #thumb-label="{ modelValue }">
{{ labelSliderToScaleValue(modelValue) }}
</template>
</v-range-slider>
Expand Down
34 changes: 24 additions & 10 deletions src/components/Project/ImageAnalysis/ImageAnalyzer.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<script setup>
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { fetchApiCall } from '../../../utils/api'
import { useConfigurationStore } from '@/stores/configuration'
import { useAlertsStore } from '@/stores/alerts'
import ImageViewer from './ImageViewer.vue'
import LinePlot from './LinePlot.vue'
import FilterBadge from '@/components/Global/FilterBadge.vue'
import NonLinearSlider from '@/components/Global/NonLinearSlider.vue'
import ImageDownloadMenu from '@/components/Project/ImageAnalysis/ImageDownloadMenu.vue'
import { siteIDToName } from '@/utils/common'
Expand All @@ -32,9 +33,15 @@ const catalog = ref([])
const positionAngle = ref()
const headerDialog = ref(false)
const headerData = ref({})
const fluxSliderRange = ref([0, 10000])
const filteredCatalog = computed(() => {
return catalog.value.filter((source) => source.flux >= fluxSliderRange.value[0] && source.flux <= fluxSliderRange.value[1])
})
function closeDialog() {
lineProfile.value = []
catalog.value = []
lineProfileLength.value = 0
startCoords.value = null
endCoords.value = null
Expand Down Expand Up @@ -131,19 +138,22 @@ function showHeaderDialog() {
<div class="analysis-content">
<image-viewer
:image-src="image.largeCachedUrl"
:catalog="catalog"
:catalog="filteredCatalog"
@analysis-action="requestAnalysis"
/>
<div class="side-panel-container">
<v-sheet
v-if="image.site_id || image.telescope_id || image.instrument_id || image.observation_date"
v-if="catalog.length"
rounded
class="basic-info-sheet"
class="image-controls-sheet"
>
<p><v-icon icon="mdi-earth" /> {{ siteIDToName(image.site_id) ?? 'Missing Site' }}</p>
<p><v-icon icon="mdi-telescope" /> {{ image.telescope_id ?? 'Missing Telescope ID' }}</p>
<p><v-icon icon="mdi-camera" /> {{ image.instrument_id ?? 'Missing Instrument ID' }} </p>
<p><v-icon icon="mdi-clock" /> {{ new Date(image.observation_date).toLocaleString() }}</p>
<b>{{ filteredCatalog.length }} Sources with Flux between {{ fluxSliderRange[0] }} and {{ fluxSliderRange[1] }}</b>
<non-linear-slider
v-model="fluxSliderRange"
prepend-icon="mdi-flare"
:max="Math.max(...catalog.map((source) => source.flux))"
:min="Math.min(...catalog.map((source) => source.flux))"
/>
</v-sheet>
<v-sheet
class="line-plot-sheet"
Expand All @@ -166,7 +176,11 @@ function showHeaderDialog() {
width="auto"
>
<v-sheet class="pa-12">
<h1 class="mb-8">
<p><v-icon icon="mdi-earth" /> {{ siteIDToName(image.site_id) ?? 'Missing Site' }}</p>
<p><v-icon icon="mdi-telescope" /> {{ image.telescope_id ?? 'Missing Telescope ID' }}</p>
<p><v-icon icon="mdi-camera" /> {{ image.instrument_id ?? 'Missing Instrument ID' }} </p>
<p><v-icon icon="mdi-clock" /> {{ new Date(image.observation_date).toLocaleString() }}</p>
<h1 class="mb-4 mt-4">
FITS Headers
</h1>
<v-table>
Expand Down Expand Up @@ -215,7 +229,7 @@ a{
margin-left: 10px;
width: 45vw;
}
.basic-info-sheet{
.image-controls-sheet{
padding: 1rem;
margin-bottom: 1rem;
color: var(--tan);
Expand Down
66 changes: 27 additions & 39 deletions src/components/Project/ImageAnalysis/ImageViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ let imageBounds
let initialCenter = [0, 0]
let initialZoom = 1
let lineLayer = null
let layerControl = null
let catalogLayerGroup = null
let catalogLayerControl = null
const imageWidth = ref(0)
const imageHeight = ref(0)
const isLoading = ref(true)
const imageContainer = ref(null)
const leafletDiv = ref(null)
const alerts = useAlertsStore()
onMounted(() => {
Expand All @@ -46,7 +46,7 @@ watch(() => props.catalog, () => {
// loads image overlay and set bounds
function loadImageOverlay() {
// Create leaflet map (here referred to as image)
imageMap = L.map(imageContainer.value, {
imageMap = L.map(leafletDiv.value, {
center: [0, 0],
maxZoom: 6,
crs: L.CRS.Simple,
Expand Down Expand Up @@ -83,7 +83,6 @@ function loadImageOverlay() {
initialCenter = imageMap.getCenter()
initialZoom = imageMap.getZoom()
isLoading.value = false
}
}
Expand All @@ -101,7 +100,7 @@ function leafletSetup(){
toggle: false,
})
// Disabling default controls
// Geoman settings setup
imageMap.pm.addControls({
position: 'topleft',
drawMarker: false,
Expand All @@ -117,6 +116,7 @@ function leafletSetup(){
removalMode: true
})
// Logic to only allow the user to draw one line with two points
imageMap.on('pm:drawstart', ({ workingLayer }) => {
// Remove last drawn line when starting new one
if (lineLayer && imageMap.hasLayer(lineLayer)) {
Expand All @@ -130,7 +130,7 @@ function leafletSetup(){
})
})
// Get coordinates of the line
// Requests a Line Profile when a line is drawn/edited
imageMap.on('pm:create', (e) => {
// Save last drawn line
lineLayer = e.layer
Expand All @@ -144,8 +144,8 @@ function resizeMap(){
const resizeObserver = new ResizeObserver(() => {
imageMap.invalidateSize()
})
if(imageContainer.value){
resizeObserver.observe(imageContainer.value)
if(leafletDiv.value){
resizeObserver.observe(leafletDiv.value)
}
}
Expand Down Expand Up @@ -186,37 +186,33 @@ function requestLineProfile(latLngs) {
// When we get the catalog data this creates a layer of circles on the map
function createCatalogLayer(){
if (!props.catalog){
alerts.setAlert('info', 'No source catalog data for this image')
return
}
let sourceCatalogMarkers = []
props.catalog.forEach((source) => {
let sourceMarker = L.circle([source.y, source.x], {
// Function to create a marker for a source
function createSourceMarker(source){
return new L.Circle([source.y, source.x], {
color: 'var(--tangerine)',
fillColor: 'var(--tangerine)',
fillOpacity: 0.2,
radius: 10,
pmIgnore: true, // Ignore this layer for editing
snapIgnore: false, // Allow snapping to this layer
})
sourceMarker.bindPopup(`Flux: ${source.flux ?? 'N/A'}<br>Ra: ${source.ra ?? 'N/A'}<br>Dec: ${source.dec ?? 'N/A'}`)
sourceCatalogMarkers.push(sourceMarker)
})
}).bindPopup(`Flux: ${source.flux ?? 'N/A'}<br>Ra: ${source.ra ?? 'N/A'}<br>Dec: ${source.dec ?? 'N/A'}`)
}
// Associate all source markers with a single layer group
let catalogLayerGroup = L.layerGroup(sourceCatalogMarkers)
const sourceCatalogMarkers = props.catalog.map(createSourceMarker)
// Displays the catalog layer by default
catalogLayerGroup.addTo(imageMap)
// update or create the catalog layer group
if (catalogLayerGroup) {
catalogLayerGroup.clearLayers()
sourceCatalogMarkers.forEach((marker) => catalogLayerGroup.addLayer(marker))
} else {
catalogLayerGroup = new L.LayerGroup(sourceCatalogMarkers)
catalogLayerGroup.addTo(imageMap)
}
// Layer control allows the toggling of layers on the map
layerControl = L.control.layers(null, null, {collapsed:false}).addTo(imageMap)
layerControl.addOverlay(catalogLayerGroup, 'Source Catalog')
if(!catalogLayerControl){
catalogLayerControl = new L.Control.Layers(null, { 'Source Catalog': catalogLayerGroup }, { collapsed: false }).addTo(imageMap)
}
}
function fetchCatalog(){
Expand All @@ -229,16 +225,8 @@ function fetchCatalog(){
</script>
<template>
<template v-if="isLoading">
<v-progress-circular
indeterminate
model-value="20"
:size="50"
:width="9"
/>
</template>
<div
ref="imageContainer"
ref="leafletDiv"
:style="{ width: imageWidth + 'px' }"
/>
</template>
Expand Down

0 comments on commit 631ed36

Please sign in to comment.