Skip to content

AJP-3 introduce AI predictions #136

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
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
46 changes: 46 additions & 0 deletions ajapaik/ajapaik/migrations/0026_auto_20231231_0026.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Generated by Django 3.2.7 on 2023-12-30 22:26

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('ajapaik', '0025_importblacklist'),
]

operations = [
migrations.CreateModel(
name='PhotoModelSuggestionResult',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
('viewpoint_elevation', models.PositiveSmallIntegerField(blank=True, choices=[(0, 'Ground'), (1, 'Raised'), (2, 'Aerial')], null=True, verbose_name='Viewpoint elevation')),
('scene', models.PositiveSmallIntegerField(blank=True, choices=[(0, 'Interior'), (1, 'Exterior')], null=True, verbose_name='Scene')),
('photo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ajapaik.photo')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='PhotoModelSuggestionAlternativeCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
('viewpoint_elevation', models.PositiveSmallIntegerField(blank=True, choices=[(0, 'Ground'), (1, 'Raised'), (2, 'Aerial')], null=True, verbose_name='Viewpoint elevation')),
('scene', models.PositiveSmallIntegerField(blank=True, choices=[(0, 'Interior'), (1, 'Exterior')], null=True, verbose_name='Scene')),
('photo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ajapaik.photo')),
('proposer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='photo_scene_suggestions_alternation', to='ajapaik.profile')),
],
),
migrations.AddConstraint(
model_name='photomodelsuggestionalternativecategory',
constraint=models.UniqueConstraint(condition=models.Q(('scene__isnull', True)), fields=('proposer', 'viewpoint_elevation'), name='unique_proposer_viewpoint_elevation_without_scene'),
),
migrations.AddConstraint(
model_name='photomodelsuggestionalternativecategory',
constraint=models.UniqueConstraint(condition=models.Q(('viewpoint_elevation__isnull', True)), fields=('proposer', 'scene'), name='unique_proposer_scene_without_viewpoint_elevation'),
),
]
71 changes: 48 additions & 23 deletions ajapaik/ajapaik/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db.models import CASCADE, DateField, FileField, Lookup, Transform, OneToOneField, Q, F, Sum, Index
from django.db.models import CASCADE, DateField, FileField, Lookup, Transform, OneToOneField, Q, F, Sum, Index, \
UniqueConstraint
from django.db.models.fields import Field
from django.db.models.query import QuerySet
from django.db.models.signals import post_save
Expand All @@ -53,6 +54,18 @@
# https://wiki.postgresql.org/wiki/Count_estimate
# https://stackoverflow.com/questions/41467751/how-to-override-queryset-count-method-in-djangos-admin-list

INTERIOR, EXTERIOR = range(2)
GROUND_LEVEL, RAISED, AERIAL = range(3)
SCENE_CHOICES = (
(INTERIOR, _('Interior')),
(EXTERIOR, _('Exterior'))
)
VIEWPOINT_ELEVATION_CHOICES = (
(GROUND_LEVEL, _('Ground')),
(RAISED, _('Raised')),
(AERIAL, _('Aerial'))
)

class EstimatedCountQuerySet(QuerySet):

# Get count from cache if it is available
Expand Down Expand Up @@ -638,18 +651,7 @@ class Photo(Model):
similar_photos = ManyToManyField('self', through='ImageSimilarity', symmetrical=False)
back_of = ForeignKey('self', blank=True, null=True, related_name='back', on_delete=CASCADE)
front_of = ForeignKey('self', blank=True, null=True, related_name='front', on_delete=CASCADE)
INTERIOR, EXTERIOR = range(2)
SCENE_CHOICES = (
(INTERIOR, _('Interior')),
(EXTERIOR, _('Exterior'))
)
scene = PositiveSmallIntegerField(_('Scene'), choices=SCENE_CHOICES, blank=True, null=True)
GROUND_LEVEL, RAISED, AERIAL = range(3)
VIEWPOINT_ELEVATION_CHOICES = (
(GROUND_LEVEL, _('Ground')),
(RAISED, _('Raised')),
(AERIAL, _('Aerial'))
)
viewpoint_elevation = PositiveSmallIntegerField(_('Viewpoint elevation'), choices=VIEWPOINT_ELEVATION_CHOICES,
blank=True, null=True)
description_original_language = CharField(_('Description original language'), max_length=255, blank=True, null=True)
Expand Down Expand Up @@ -2053,27 +2055,50 @@ class Meta:


class PhotoSceneSuggestion(Suggestion):
INTERIOR, EXTERIOR = range(2)
SCENE_CHOICES = (
(INTERIOR, _('Interior')),
(EXTERIOR, _('Exterior'))
)
scene = PositiveSmallIntegerField(_('Scene'), choices=SCENE_CHOICES, blank=True, null=True)
proposer = ForeignKey('Profile', blank=True, null=True, related_name='photo_scene_suggestions', on_delete=CASCADE)


class PhotoViewpointElevationSuggestion(Suggestion):
GROUND_LEVEL, RAISED, AERIAL = range(3)
VIEWPOINT_ELEVATION_CHOICES = (
(GROUND_LEVEL, _('Ground')),
(RAISED, _('Raised')),
(AERIAL, _('Aerial'))
)
viewpoint_elevation = PositiveSmallIntegerField(_('Viewpoint elevation'), choices=VIEWPOINT_ELEVATION_CHOICES,
blank=True, null=True)
proposer = ForeignKey('Profile', blank=True, null=True, related_name='photo_viewpoint_elevation_suggestions',
on_delete=CASCADE)

class PhotoModelSuggestionResult(Suggestion):
viewpoint_elevation = PositiveSmallIntegerField(_('Viewpoint elevation'), choices=VIEWPOINT_ELEVATION_CHOICES, blank=True, null=True)
scene = PositiveSmallIntegerField(_('Scene'), choices=SCENE_CHOICES, blank=True, null=True)


class PhotoModelSuggestionAlternativeCategory(Suggestion):

viewpoint_elevation = PositiveSmallIntegerField(_('Viewpoint elevation'),
choices=VIEWPOINT_ELEVATION_CHOICES, blank=True,
null=True)
scene = PositiveSmallIntegerField(_('Scene'), choices=SCENE_CHOICES, blank=True, null=True)

proposer = ForeignKey('Profile', blank=True, null=True, related_name='photo_scene_suggestions_alternation',
on_delete=CASCADE)
class Meta:
constraints = [
UniqueConstraint(
fields=['proposer', 'viewpoint_elevation'],
condition=Q(scene__isnull=True),
name='unique_proposer_viewpoint_elevation_without_scene'
),
UniqueConstraint(
fields=['proposer', 'scene'],
condition=Q(viewpoint_elevation__isnull=True),
name='unique_proposer_scene_without_viewpoint_elevation'
),
]

def validate_unique(self, exclude=None):
super().validate_unique(exclude)

def save(self, *args, **kwargs):
if self.validate_unique():
super().save(*args, **kwargs)

class PhotoFlipSuggestion(Suggestion):
proposer = ForeignKey('Profile', blank=True, null=True, related_name='photo_flip_suggestions', on_delete=CASCADE)
Expand Down
98 changes: 98 additions & 0 deletions ajapaik/ajapaik/static/js/ajp-category-suggestion.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
function submitCategorySuggestion(photoIds, isMultiSelect) {
sendCategorySuggestionToAI(photoIds, scene, viewpointElevation)
$('#ajp-loading-overlay').show();
return fetch(photoSceneUrl, {
method: 'POST',
Expand Down Expand Up @@ -99,3 +100,100 @@ function clickSceneCategoryButton(buttonId) {
$('#' + buttonId).removeClass('btn-outline-dark');
$('#' + buttonId).removeClass('btn-light');
}

function displaySmallAIIcon(categoryMap) {
let scene = categoryMap["scene"]
let viewpointElevation = categoryMap["viewpoint_elevation"]

if (scene === "exterior") {
$("#exterior-ai").show();
}
if (scene === "interior") {
$("#interior-ai").show();
}
if (viewpointElevation === "ground") {
$("#ground-ai").show();
}
if (viewpointElevation === "aerial") {
$("#aerial-ai").show();
}
if (viewpointElevation === "raised") {
$("#raised-ai").show();
}
}

function determinePictureCategory(jsonData) {
let modelCategory = {};
if (jsonData && jsonData.length > 0) {
let fields = jsonData[0].fields;
if (fields && "scene" in fields) {
if (fields["scene"] === 0) {
modelCategory["scene"] = "interior";
} else {
modelCategory["scene"] = "exterior";
Copy link
Member

Choose a reason for hiding this comment

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

Usually we have used else if - is there a risk that scene can be null, but we handle it as exterior or do we need a fallback value (exterior) here?

}
}
if (fields && "viewpoint_elevation" in fields) {
if (fields["viewpoint_elevation"] === 0) {
modelCategory["viewpoint_elevation"] = "ground";
} else if (fields["viewpoint_elevation"] === 1) {
modelCategory["viewpoint_elevation"] = "raised";
} else if (fields["viewpoint_elevation"] === 2) {
modelCategory["viewpoint_elevation"] = "aerial";
}
}
}
return modelCategory;
}

function markButtonsWithCategories(scene, viewpointElevation) {
if (scene === "exterior") {
clickSceneCategoryButton('exterior-button');
}
if (scene === "interior") {
clickSceneCategoryButton('interior-button');
}
if (viewpointElevation === "ground") {
clickViewpointElevationCategoryButton('ground-button');
}
if (viewpointElevation === "aerial") {
clickViewpointElevationCategoryButton('aerial-button');
}
if (viewpointElevation === "raised") {
clickViewpointElevationCategoryButton('raised-button');
}
}

function sendCategorySuggestionToAI(photoIds, scene, viewpointElevation) {
let sceneVerdict = scene.toLowerCase();
let viewpointElevationVerdict = viewpointElevation.toLowerCase();

let payload = {
"photo_id": photoIds[0]
};

if (sceneVerdict === "interior") {
payload["scene_to_alternate"] = 0
}
if (sceneVerdict === "exterior") {
payload["scene_to_alternate"] = 1
}
if (viewpointElevationVerdict === "ground") {
payload["viewpoint_elevation_to_alternate"] = 0
}
if (viewpointElevationVerdict === "raised") {
payload["viewpoint_elevation_to_alternate"] = 1
}
if (viewpointElevationVerdict === "raised") {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (viewpointElevationVerdict === "raised") {
if (viewpointElevationVerdict === "aerial") {

payload["viewpoint_elevation_to_alternate"] = 2
Copy link
Member

Choose a reason for hiding this comment

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

Just to clarify, do we need to check for === "raised" twice? In any case, that would mean the value is always 2 - if it is raised.
I guess there is another viewpointElevationVerdict - "aerial" (or something of the sort)?

}

postRequest(
'/object-categorization/confirm-latest-category',
payload,
constants.translations.queries.POST_CATEGORY_CONFIRMATION_SUCCESS,
constants.translations.queries.POST_CATEGORY_CONFIRMATION_FAILED,
function () {
}
);
}
6 changes: 6 additions & 0 deletions ajapaik/ajapaik/static/js/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ var constants = {
},
},
queries: {
POST_CATEGORY_CONFIRMATION_SUCCESS: gettext(
'Successfully posted object category confirmation'
),
POST_CATEGORY_CONFIRMATION_FAILED: gettext(
'Failed to post object category confirmation'
),
GET_ANNOTATION_CLASSES_FAILED: gettext(
'Failed to load object annotation classes'
),
Expand Down
Loading