Skip to content

Commit f8030fc

Browse files
authored
Merge pull request #142 from Kitware/issue-133-alt-color-scheme
Add ability to select coloscheme for spectrogram
2 parents 71b2c07 + b84971e commit f8030fc

12 files changed

Lines changed: 444 additions & 88 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Generated by Django 4.1.13 on 2025-05-02 20:43
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
('core', '0016_alter_species_common_name_alter_species_family_and_more'),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name='configuration',
14+
name='default_color_scheme',
15+
field=models.CharField(
16+
choices=[
17+
('inferno', 'Inferno'),
18+
('cividis', 'Cividis'),
19+
('viridis', 'Viridis'),
20+
('magma', 'Magma'),
21+
('turbo', 'Turbo'),
22+
('plasma', 'Plasma'),
23+
],
24+
default='inferno',
25+
max_length=20,
26+
),
27+
),
28+
migrations.AddField(
29+
model_name='configuration',
30+
name='default_spectrogram_background_color',
31+
field=models.CharField(default='rgb(0, 0, 0)', max_length=18),
32+
),
33+
]

bats_ai/core/models/configuration.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,28 @@ class SpectrogramViewMode(models.TextChoices):
99
COMPRESSED = 'compressed'
1010
UNCOMPRESSED = 'uncompressed'
1111

12+
class AvailableColorScheme(models.TextChoices):
13+
INFERNO = 'inferno'
14+
CIVIDIS = 'cividis'
15+
VIRIDIS = 'viridis'
16+
MAGMA = 'magma'
17+
TURBO = 'turbo'
18+
PLASMA = 'plasma'
19+
1220
display_pulse_annotations = models.BooleanField(default=True)
1321
display_sequence_annotations = models.BooleanField(default=True)
1422
run_inference_on_upload = models.BooleanField(default=True)
1523
spectrogram_x_stretch = models.DecimalField(default=2.5, max_digits=3, decimal_places=2)
1624
spectrogram_view = models.CharField(
1725
max_length=12, choices=SpectrogramViewMode.choices, default=SpectrogramViewMode.COMPRESSED
1826
)
27+
default_color_scheme = models.CharField(
28+
max_length=20,
29+
choices=AvailableColorScheme.choices,
30+
default=AvailableColorScheme.INFERNO,
31+
)
32+
# 18 characters is just enough for "rgb(255, 255, 255)"
33+
default_spectrogram_background_color = models.CharField(max_length=18, default='rgb(0, 0, 0)')
1934

2035
def save(self, *args, **kwargs):
2136
# Ensure only one instance of Configuration exists

bats_ai/core/views/configuration.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class ConfigurationSchema(Schema):
2020
run_inference_on_upload: bool
2121
spectrogram_x_stretch: float
2222
spectrogram_view: Configuration.SpectrogramViewMode
23+
default_color_scheme: Configuration.AvailableColorScheme
24+
default_spectrogram_background_color: str
2325

2426

2527
# Endpoint to retrieve the configuration status
@@ -34,6 +36,8 @@ def get_configuration(request):
3436
run_inference_on_upload=config.run_inference_on_upload,
3537
spectrogram_x_stretch=config.spectrogram_x_stretch,
3638
spectrogram_view=config.spectrogram_view,
39+
default_color_scheme=config.default_color_scheme,
40+
default_spectrogram_background_color=config.default_spectrogram_background_color,
3741
is_admin=request.user.is_authenticated and request.user.is_superuser,
3842
)
3943

client/src/api/api.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ export interface ConfigurationSettings {
358358
spectrogram_x_stretch: number;
359359
spectrogram_view: 'compressed' | 'uncompressed';
360360
is_admin?: boolean;
361+
default_color_scheme: string;
362+
default_spectrogram_background_color: string;
361363
}
362364

363365
export type Configuration = ConfigurationSettings & { is_admin: boolean };
@@ -391,7 +393,7 @@ export interface ProcessingTaskDetails {
391393
error:string;
392394
}
393395

394-
}
396+
}
395397

396398
async function getProcessingTasks(): Promise<ProcessingTask[]> {
397399
return (await axiosInstance.get('/processing-task')).data;
@@ -441,9 +443,9 @@ async function getGuanoMetadata(file: File): Promise<GuanoMetadata> {
441443

442444
async function adminNaBatUpdateSpecies(apiToken: string) {
443445
return axiosInstance.post<{ taskId: string }>('/nabat/configuration/update-species', { params: { apiToken } });
444-
446+
445447
}
446-
448+
447449

448450
export {
449451
uploadRecordingFile,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<script setup lang="ts">
2+
import * as d3 from 'd3';
3+
import { defineModel, ref } from 'vue';
4+
defineProps({
5+
tooltipText: {
6+
type: String,
7+
default: ''
8+
},
9+
});
10+
const colorpickerMenu = ref(false);
11+
const color = defineModel({ default: 'rgb(0, 0, 0)'});
12+
function updateColor(colorVal: string) {
13+
const d3Color = d3.color(colorVal);
14+
if (d3Color) {
15+
color.value = d3Color.formatRgb();
16+
}
17+
}
18+
</script>
19+
20+
<template>
21+
<v-menu
22+
v-model="colorpickerMenu"
23+
:close-on-content-click="false"
24+
offset-y
25+
>
26+
<template #activator="{ props: subProps }">
27+
<v-tooltip :text="tooltipText">
28+
<template #activator="{ props: tooltipProps }">
29+
<v-btn
30+
v-bind="{ ...subProps, ...tooltipProps }"
31+
:style="{ backgroundColor: color }"
32+
class="color-square mx-2"
33+
/>
34+
</template>
35+
</v-tooltip>
36+
</template>
37+
<v-card>
38+
<v-card-text>
39+
<v-color-picker
40+
v-model="color"
41+
mode="rgb"
42+
elevation="0"
43+
@update:model-value="updateColor"
44+
/>
45+
</v-card-text>
46+
</v-card>
47+
</v-menu>
48+
</template>
49+
50+
<style scoped>
51+
.color-square {
52+
width: 32px;
53+
height: 32px;
54+
min-width: 32px;
55+
border-radius: 4px;
56+
}
57+
</style>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<script setup lang="ts">
2+
import { defineModel } from 'vue';
3+
4+
defineProps({
5+
colorSchemes: {
6+
type: Array as () => { value: string; title: string; scheme: (input: number) => string }[],
7+
required: true,
8+
},
9+
label: {
10+
type: String,
11+
default: 'Color Scheme',
12+
},
13+
returnObject: {
14+
type: Boolean,
15+
default: true,
16+
}
17+
});
18+
19+
const colorScheme = defineModel();
20+
</script>
21+
22+
<template>
23+
<v-select
24+
v-model="colorScheme"
25+
:label="label"
26+
:items="colorSchemes"
27+
item-title="title"
28+
item-value="value"
29+
variant="outlined"
30+
density="compact"
31+
:return-object="returnObject"
32+
hide-details
33+
>
34+
<template #item="{ item, props }">
35+
<div
36+
v-bind="props"
37+
class="custom-hover"
38+
>
39+
<v-row
40+
dense
41+
align="center"
42+
justify="center"
43+
no-gutters
44+
>
45+
<span>{{ item.title }}</span>
46+
</v-row>
47+
<v-row
48+
dense
49+
no-gutters
50+
align="center"
51+
justify="center"
52+
class="pb-2"
53+
>
54+
<div
55+
v-for="n in 11"
56+
:key="n"
57+
size="10"
58+
:style="{ backgroundColor: item.raw.scheme((n - 1) / 10) }"
59+
class="color-swatch"
60+
/>
61+
</v-row>
62+
</div>
63+
<v-divider />
64+
</template>
65+
66+
<template #selection="{ item }">
67+
<div class="d-flex align-center">
68+
<span>{{ item.title }}</span>
69+
</div>
70+
</template>
71+
</v-select>
72+
</template>
73+
74+
<style scoped>
75+
.color-swatch {
76+
width:15px;
77+
height: 10px;
78+
}
79+
.custom-hover {
80+
cursor: pointer;
81+
}
82+
.custom-hover:hover {
83+
font-weight: bold;;
84+
}
85+
</style>

0 commit comments

Comments
 (0)