Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aggregate Updates #106

Merged
merged 4 commits into from
Nov 12, 2024
Merged
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
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 @@
annotations?: SpectrogramAnnotation[];
temporal?: SpectrogramTemporalAnnotation[];
spectroInfo?: SpectroInfo;
compressed?: {
start_times: number[];
end_times: number[];
}
currentUser?: string;
otherUsers?: UserInfo[];

Expand All @@ -120,7 +124,7 @@

export type OtherUserAnnotations = Record<string, {annotations: SpectrogramAnnotation[], temporal: SpectrogramTemporalAnnotation[]}>;

interface PaginatedNinjaResponse<T> {

Check warning on line 127 in client/src/api/api.ts

View workflow job for this annotation

GitHub Actions / Lint [eslint]

'PaginatedNinjaResponse' is defined but never used
count: number,
items: T[],
}
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
Loading