Skip to content

Commit

Permalink
Aggregate Updates (#106)
Browse files Browse the repository at this point in the history
* fix compression/expanded annotation toggling issues

* allowing removal of single species Id

* adding in a compressedOverlay for visualizing compressed items in the main view

* finalize overlay
  • Loading branch information
BryonLewis authored Nov 12, 2024
1 parent d408399 commit 6b974cf
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 19 deletions.
20 changes: 10 additions & 10 deletions bats_ai/core/views/guanometadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,16 @@ def default_data(
nabat_fields['nabat_longitude'] = str(float(nabat_fields['nabat_longitude']) * -1)
# Extract additional fields with conditionals
additional_fields = {
'nabat_activation_start_time': parse_datetime(
gfile.get('NABat|Activation start time', None)
)
if 'NABat|Activation start time' in gfile
else None,
'nabat_activation_end_time': parse_datetime(
gfile.get('NABat|Activation end time', None)
)
if 'NABat|Activation end time' in gfile
else None,
'nabat_activation_start_time': (
parse_datetime(gfile.get('NABat|Activation start time', None))
if 'NABat|Activation start time' in gfile
else None
),
'nabat_activation_end_time': (
parse_datetime(gfile.get('NABat|Activation end time', None))
if 'NABat|Activation end time' in gfile
else None
),
'nabat_software_type': gfile.get('NABat|Software type', None),
'nabat_species_list': gfile.get('NABat|Species List', '').split(','),
'nabat_comments': gfile.get('NABat|Comments', None),
Expand Down
10 changes: 9 additions & 1 deletion bats_ai/core/views/recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ def get_spectrogram(request: HttpRequest, id: int):
with colormap(None):
spectrogram = recording.spectrogram

compressed = recording.compressed_spectrogram

spectro_data = {
'url': spectrogram.image_url,
'spectroInfo': {
Expand All @@ -290,6 +292,12 @@ def get_spectrogram(request: HttpRequest, id: int):
'high_freq': spectrogram.frequency_max,
},
}
if compressed:
spectro_data['compressed'] = {
'start_times': compressed.starts,
'end_times': compressed.stops,
}

# Get distinct other users who have made annotations on the recording
if recording.owner == request.user:
other_users_qs = (
Expand Down Expand Up @@ -579,7 +587,7 @@ def patch_annotation(
annotation_instance.save()

# Clear existing species associations
if species_ids:
if species_ids is not None:
annotation_instance.species.clear()
# Add species to the annotation based on the provided species_ids
for species_id in species_ids:
Expand Down
4 changes: 4 additions & 0 deletions client/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export interface Spectrogram {
annotations?: SpectrogramAnnotation[];
temporal?: SpectrogramTemporalAnnotation[];
spectroInfo?: SpectroInfo;
compressed?: {
start_times: number[];
end_times: number[];
}
currentUser?: string;
otherUsers?: UserInfo[];

Expand Down
5 changes: 4 additions & 1 deletion client/src/components/SpectrogramViewer.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { defineComponent, PropType, ref, Ref, watch } from "vue";
import { defineComponent, onMounted, onUnmounted, PropType, ref, Ref, watch } from "vue";
import { SpectroInfo, spectroToCenter, useGeoJS } from "./geoJS/geoJSUtils";
import {
patchAnnotation,
Expand Down Expand Up @@ -131,6 +131,7 @@ export default defineComponent({
}
}
};
onMounted(() => initialized.value = false);
watch([containerRef], () => {
scaledWidth.value = props.spectroInfo?.width;
scaledHeight.value = props.spectroInfo?.height;
Expand Down Expand Up @@ -267,6 +268,8 @@ export default defineComponent({
scaledVals.value = {x: xScale, y: yScale};
};
onUnmounted(() => geoJS.destroyGeoViewer());
return {
Expand Down
35 changes: 34 additions & 1 deletion client/src/components/geoJS/LayerManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { SpectrogramAnnotation, SpectrogramTemporalAnnotation } from "../../api/
import { geojsonToSpectro, SpectroInfo } from "./geoJSUtils";
import EditAnnotationLayer from "./layers/editAnnotationLayer";
import RectangleLayer from "./layers/rectangleLayer";
import CompressedOverlayLayer from "./layers/compressedOverlayLayer";
import TemporalLayer from "./layers/temporalLayer";
import LegendLayer from "./layers/legendLayer";
import TimeLayer from "./layers/timeLayer";
Expand Down Expand Up @@ -57,6 +58,7 @@ export default defineComponent({
selectedId,
selectedType,
setSelectedId,
viewCompressedOverlay,
} = useState();
const selectedAnnotationId: Ref<null | number> = ref(null);
const hoveredAnnotationId: Ref<null | number> = ref(null);
Expand All @@ -65,6 +67,7 @@ export default defineComponent({
const editing = ref(false);
const editingAnnotation: Ref<null | SpectrogramAnnotation | SpectrogramTemporalAnnotation> = ref(null);
let rectAnnotationLayer: RectangleLayer;
let compressedOverlayLayer: CompressedOverlayLayer;
let temporalAnnotationLayer: TemporalLayer;
let editAnnotationLayer: EditAnnotationLayer;
let legendLayer: LegendLayer;
Expand Down Expand Up @@ -315,6 +318,10 @@ export default defineComponent({
);
rectAnnotationLayer.redraw();
}
if (viewCompressedOverlay.value && compressedOverlayLayer && !props.spectroInfo?.compressedWidth && props.spectroInfo?.start_times && props.spectroInfo.end_times) {
compressedOverlayLayer.formatData(props.spectroInfo.start_times, props.spectroInfo.end_times, props.yScale);
compressedOverlayLayer.redraw();
}
if (temporalAnnotationLayer && layerVisibility.value.includes('temporal')) {
temporalAnnotationLayer.formatData(
temporalAnnotations,
Expand Down Expand Up @@ -409,6 +416,9 @@ export default defineComponent({
if (rectAnnotationLayer) {
rectAnnotationLayer.destroy();
}
if (compressedOverlayLayer) {
compressedOverlayLayer.destroy();
}
if (temporalAnnotationLayer) {
temporalAnnotationLayer.destroy();
}
Expand All @@ -427,6 +437,11 @@ export default defineComponent({
});
const initLayers = () => {
if (props.spectroInfo) {
if (!compressedOverlayLayer) {
compressedOverlayLayer = new CompressedOverlayLayer(props.geoViewerRef, props.spectroInfo);
} else {
compressedOverlayLayer.spectroInfo = props.spectroInfo;
}
if (!editAnnotationLayer) {
editAnnotationLayer = new EditAnnotationLayer(props.geoViewerRef, event, props.spectroInfo);
} else {
Expand All @@ -444,6 +459,10 @@ export default defineComponent({
}
rectAnnotationLayer.formatData(localAnnotations.value, selectedAnnotationId.value, currentUser.value, colorScale.value, props.yScale);
rectAnnotationLayer.redraw();
if (viewCompressedOverlay.value && compressedOverlayLayer && props.spectroInfo.start_times && props.spectroInfo.end_times) {
compressedOverlayLayer.formatData(props.spectroInfo.start_times, props.spectroInfo.end_times, props.yScale);
compressedOverlayLayer.redraw();
}
temporalAnnotationLayer.formatData(localTemporalAnnotations.value, selectedAnnotationId.value, currentUser.value, colorScale.value, props.yScale);
temporalAnnotationLayer.redraw();
if (!props.thumbnail) {
Expand Down Expand Up @@ -514,6 +533,11 @@ export default defineComponent({
props.yScale,
);
rectAnnotationLayer.redraw();
if (compressedOverlayLayer && props.spectroInfo?.start_times && props.spectroInfo.end_times) {
compressedOverlayLayer.setScaledDimensions(props.scaledWidth, props.scaledHeight);
compressedOverlayLayer.formatData(props.spectroInfo.start_times, props.spectroInfo.end_times, props.yScale);
compressedOverlayLayer.redraw();
}
editAnnotationLayer.setScaledDimensions(props.scaledWidth, props.scaledHeight);
if (editing.value && editingAnnotation.value) {
setTimeout(() => {
Expand Down Expand Up @@ -558,11 +582,20 @@ export default defineComponent({
}
});
watch(viewCompressedOverlay, () => {
if (viewCompressedOverlay.value && compressedOverlayLayer && props.spectroInfo?.start_times && props.spectroInfo.end_times) {
compressedOverlayLayer.setScaledDimensions(props.scaledWidth, props.scaledHeight);
compressedOverlayLayer.formatData(props.spectroInfo.start_times, props.spectroInfo.end_times, props.yScale);
compressedOverlayLayer.redraw();
} else {
compressedOverlayLayer.disable();
}
});
watch(
() => annotationState.value,
() => {
if (annotationState.value === "creating") {
if (!props.thumbnail && annotationState.value === "creating") {
editing.value = false;
selectedAnnotationId.value = null;
editingAnnotation.value = null;
Expand Down
15 changes: 11 additions & 4 deletions client/src/components/geoJS/geoJSUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const useGeoJS = () => {
return geoViewer;
};

const destroyGeoViewer = () => {
if (geoViewer.value) {
geoViewer.value.exit();
}
};

const initializeViewer = (
sourceContainer: HTMLElement,
width: number,
Expand Down Expand Up @@ -235,6 +241,7 @@ const useGeoJS = () => {
resetMapDimensions,
resetZoom,
updateMapSize,
destroyGeoViewer,
};
};

Expand Down Expand Up @@ -268,7 +275,7 @@ function spectroTemporalToGeoJSon(
const adjustedWidth = scaledWidth > spectroInfo.width ? scaledWidth : spectroInfo.width;
// const adjustedHeight = scaledHeight > spectroInfo.height ? scaledHeight : spectroInfo.height;
//scale pixels to time and frequency ranges
if (spectroInfo.start_times === undefined || spectroInfo.end_times === undefined) {
if (spectroInfo.compressedWidth && spectroInfo.start_times === undefined || spectroInfo.end_times === undefined) {
const widthScale = adjustedWidth / (spectroInfo.end_time - spectroInfo.start_time);
// Now we remap our annotation to pixel coordinates
const start_time = annotation.start_time * widthScale;
Expand Down Expand Up @@ -367,7 +374,7 @@ function spectroToGeoJSon(
const adjustedWidth = scaledWidth > spectroInfo.width ? scaledWidth : spectroInfo.width;
const adjustedHeight = scaledHeight > spectroInfo.height ? scaledHeight : spectroInfo.height;

if (spectroInfo.start_times === undefined || spectroInfo.end_times === undefined) {
if (spectroInfo.compressedWidth === undefined) {
const widthScale = adjustedWidth / (spectroInfo.end_time - spectroInfo.start_time);
const heightScale = adjustedHeight / (spectroInfo.high_freq - spectroInfo.low_freq);
// Now we remap our annotation to pixel coordinates
Expand All @@ -389,7 +396,7 @@ function spectroToGeoJSon(
],
],
};
} else if (spectroInfo.start_times && spectroInfo.end_times) {
} else if (spectroInfo.compressedWidth && spectroInfo.start_times && spectroInfo.end_times) {
// Compressed Spectro has different conversion
// Find what section the annotation is in
const start = annotation.start_time;
Expand Down Expand Up @@ -497,7 +504,7 @@ function geojsonToSpectro(
const adjustedHeight = scaledHeight > spectroInfo.height ? scaledHeight : spectroInfo.height;

const coords = geojson.geometry.coordinates[0];
if (spectroInfo.start_times === undefined && spectroInfo.end_times === undefined) {
if (spectroInfo.compressedWidth === undefined) {
const widthScale = adjustedWidth / (spectroInfo.end_time - spectroInfo.start_time);
const heightScale = adjustedHeight / (spectroInfo.high_freq - spectroInfo.low_freq);
const start_time = Math.round(coords[1][0] / widthScale);
Expand Down
142 changes: 142 additions & 0 deletions client/src/components/geoJS/layers/compressedOverlayLayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/* eslint-disable class-methods-use-this */
import { SpectroInfo } from "../geoJSUtils";
import { LayerStyle } from "./types";

interface RectCompressedGeoJSData {
polygon: GeoJSON.Polygon;
}


function scaleCompressedTime(start_time: number, end_time: number, spectroInfo: SpectroInfo, yScale: number, scaledWidth: number, scaledHeight: number) {
const adjustedWidth = scaledWidth > spectroInfo.width ? scaledWidth : spectroInfo.width;
const adjustedHeight = scaledHeight > spectroInfo.height ? scaledHeight : spectroInfo.height;

const widthScale = adjustedWidth / (spectroInfo.end_time - spectroInfo.start_time);
const heightScale = adjustedHeight / (spectroInfo.high_freq - spectroInfo.low_freq);
// Now we remap our annotation to pixel coordinates
const low_freq =
adjustedHeight - (spectroInfo.low_freq) * heightScale;
const high_freq =
adjustedHeight - (spectroInfo.high_freq) * heightScale;
const start_time_scaled = start_time * widthScale;
const end_time_scaled = end_time * widthScale;
return {
type: "Polygon",
coordinates: [
[
[start_time_scaled, low_freq * yScale],
[start_time_scaled, high_freq * yScale],
[end_time_scaled, high_freq * yScale],
[end_time_scaled, low_freq * yScale],
[start_time_scaled, low_freq * yScale],
],
],
} as GeoJSON.Polygon;

}

export default class CompressedOverlayLayer {
formattedData: RectCompressedGeoJSData[];


// eslint-disable-next-line @typescript-eslint/no-explicit-any
featureLayer: any;


// eslint-disable-next-line @typescript-eslint/no-explicit-any
geoViewerRef: any;

spectroInfo: SpectroInfo;

style: LayerStyle<RectCompressedGeoJSData>;

scaledWidth: number;
scaledHeight: number;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
geoViewerRef: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
spectroInfo: SpectroInfo
) {
this.geoViewerRef = geoViewerRef;
this.spectroInfo = spectroInfo;
this.formattedData = [];
this.scaledWidth = 0;
this.scaledHeight = 0;
//Only initialize once, prevents recreating Layer each edit
const layer = this.geoViewerRef.createLayer("feature", {
features: ["polygon"],
});
this.featureLayer = layer
.createFeature("polygon", { selectionAPI: true });
this.style = this.createStyle();
}

setScaledDimensions(newWidth: number, newHeight: number) {
this.scaledWidth = newWidth;
this.scaledHeight = newHeight;
}

destroy() {
if (this.featureLayer) {
this.geoViewerRef.deleteLayer(this.featureLayer);
}
}

formatData(
baseStartTimes: number[],
baseEndTimes: number[],
yScale = 1,
) {
const arr: RectCompressedGeoJSData[] = [];
const startTimes = [...baseStartTimes];
const endTimes = [...baseEndTimes];
startTimes.push(this.spectroInfo.end_time);
endTimes.unshift(this.spectroInfo.start_time);
for (let i = 0; i< startTimes.length; i += 1) {
// These are swapped because we want to mask out the area inbetween
const startTime = endTimes[i];
const endTime = startTimes[i];
const polygon = scaleCompressedTime(startTime, endTime, this.spectroInfo, yScale, this.scaledWidth, this.scaledHeight);
const newAnnotation: RectCompressedGeoJSData = {
polygon,
};
arr.push(newAnnotation);
}
this.formattedData = arr;
}

redraw() {
// add some styles
this.featureLayer
.data(this.formattedData)
.polygon((d: RectCompressedGeoJSData) => d.polygon.coordinates[0])
.style(this.createStyle())
.draw();
}

disable() {
this.featureLayer.data([]).draw();
}

createStyle(): LayerStyle<RectCompressedGeoJSData> {
return {
...{
strokeColor: "transparent",
strokeWidth: 2.0,
antialiasing: 0,
stroke: true,
uniformPolygon: true,
fill: true,
fillColor: "black",
fillOpacity: 0.75,
},
// Style conversion to get array objects to work in geoJS
position: (point) => {
return { x: point[0], y: point[1] };
},
};
}
}
Loading

0 comments on commit 6b974cf

Please sign in to comment.