Skip to content

Commit

Permalink
Merge pull request #145 from hotosm/feature/feedback_v1
Browse files Browse the repository at this point in the history
Enhance : Feedback Implented New Models & Styles
  • Loading branch information
kshitijrajsharma authored Aug 3, 2023
2 parents 9598563 + eb15e63 commit ea61b47
Show file tree
Hide file tree
Showing 15 changed files with 489 additions and 280 deletions.
1 change: 0 additions & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,5 @@ RUN pip install setuptools --upgrade
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt


WORKDIR /app
COPY . /app
41 changes: 31 additions & 10 deletions backend/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class DownloadStatus(models.IntegerChoices):

dataset = models.ForeignKey(Dataset, to_field="id", on_delete=models.CASCADE)
geom = geomodels.PolygonField(srid=4326)
download_status = models.IntegerField(default=-1, choices=DownloadStatus.choices)
last_fetched_date = models.DateTimeField(null=True, blank=True)
label_status = models.IntegerField(default=-1, choices=DownloadStatus.choices)
label_fetched = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)

Expand All @@ -42,7 +42,6 @@ class Label(models.Model):
geom = geomodels.GeometryField(srid=4326)
osm_id = models.BigIntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)


class Model(models.Model):
Expand Down Expand Up @@ -89,19 +88,41 @@ class Training(models.Model):


class Feedback(models.Model):
ACTION_TYPE = (
("CREATE", "CREATE"),
("MODIFY", "MODIFY"),
("ACCEPT", "ACCEPT"),
("INITIAL", "INITIAL"),
FEEDBACK_TYPE = (
("TP", "True Positive"),
("TN", "True Negative"),
("FP", "False Positive"),
("FN", "False Negative"),
)
geom = geomodels.GeometryField(srid=4326)
training = models.ForeignKey(Training, to_field="id", on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
zoom_level = models.PositiveIntegerField(
validators=[MinValueValidator(18), MaxValueValidator(23)]
)
action = models.CharField(choices=ACTION_TYPE, max_length=10)
feedback_type = models.CharField(choices=FEEDBACK_TYPE, max_length=10)
comments = models.TextField(max_length=100,null=True,blank=True)
user = models.ForeignKey(OsmUser, to_field="osm_id", on_delete=models.CASCADE)
source_imagery = models.URLField()


class FeedbackAOI(models.Model):
class DownloadStatus(models.IntegerChoices):
DOWNLOADED = 1
NOT_DOWNLOADED = -1
RUNNING = 0
training = models.ForeignKey(Training, to_field="id", on_delete=models.CASCADE)
geom = geomodels.PolygonField(srid=4326)
label_status = models.IntegerField(default=-1, choices=DownloadStatus.choices)
label_fetched = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
source_imagery = models.URLField()
user = models.ForeignKey(OsmUser, to_field="osm_id", on_delete=models.CASCADE)
validated = models.BooleanField(default=False)


class FeedbackLabel(models.Model):
osm_id = models.BigIntegerField(null=True, blank=True)
feedback_aoi = models.ForeignKey(FeedbackAOI, to_field="id", on_delete=models.CASCADE)
geom = geomodels.PolygonField(srid=4326)
created_at = models.DateTimeField(auto_now_add=True)
39 changes: 33 additions & 6 deletions backend/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,32 @@ class Meta:
read_only_fields = (
"created_at",
"last_modified",
"last_fetched_date",
"download_status",
"label_fetched",
"label_status",
)


class FeedbackAOISerializer(GeoFeatureModelSerializer):
class Meta:
model = FeedbackAOI
geo_field = "geom"
fields = "__all__"
partial = True

read_only_fields = (
"created_at",
"last_modified",
"label_fetched",
"label_status",
"user",
)

def create(self, validated_data):
user = self.context["request"].user
validated_data["user"] = user
return super().create(validated_data)


class FeedbackSerializer(GeoFeatureModelSerializer):
class Meta:
model = Feedback
Expand Down Expand Up @@ -90,10 +111,15 @@ class Meta:
# auto_bbox = True
fields = "__all__" # defining all the fields to be included in curd for now , we can restrict few if we want

read_only_fields = (
"created_at",
"last_modified",
)
read_only_fields = ("created_at", "osm_id")


class FeedbackLabelSerializer(GeoFeatureModelSerializer):
class Meta:
model = FeedbackLabel
geo_field = "geom"
fields = "__all__"
read_only_fields = ("created_at", "osm_id")


class LabelFileSerializer(
Expand Down Expand Up @@ -136,6 +162,7 @@ class FeedbackParamSerializer(serializers.Serializer):
batch_size = serializers.IntegerField(required=False)
freeze_layers = serializers.BooleanField(required=False)


class PredictionParamSerializer(serializers.Serializer):
bbox = serializers.ListField(child=serializers.FloatField(), required=True)
model_id = serializers.IntegerField(required=True)
Expand Down
4 changes: 4 additions & 0 deletions backend/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
AOIViewSet,
APIStatus,
DatasetViewSet,
FeedbackAOIViewset,
FeedbackLabelViewset,
FeedbackView,
FeedbackViewset,
GenerateGpxView,
Expand All @@ -31,6 +33,8 @@
router.register(r"training", TrainingViewSet)
router.register(r"model", ModelViewSet)
router.register(r"feedback", FeedbackViewset)
router.register(r"feedback-aoi", FeedbackAOIViewset)
router.register(r"feedback-label", FeedbackLabelViewset)


urlpatterns = [
Expand Down
46 changes: 40 additions & 6 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,22 @@
from rest_framework.views import APIView
from rest_framework_gis.filters import InBBoxFilter, TMSTileFilter

from .models import AOI, Dataset, Feedback, Label, Model, Training
from .models import (
AOI,
Dataset,
Feedback,
FeedbackAOI,
FeedbackLabel,
Label,
Model,
Training,
)
from .serializers import (
AOISerializer,
DatasetSerializer,
FeedbackAOISerializer,
FeedbackFileSerializer,
FeedbackLabelSerializer,
FeedbackParamSerializer,
FeedbackSerializer,
LabelSerializer,
Expand Down Expand Up @@ -150,7 +161,30 @@ class FeedbackViewset(viewsets.ModelViewSet):
queryset = Feedback.objects.all()
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = FeedbackSerializer # connecting serializer
filterset_fields = ["training", "user", "action", "validated"]
filterset_fields = ["training", "user", "feedback_type"]


class FeedbackAOIViewset(viewsets.ModelViewSet):
authentication_classes = [OsmAuthentication]
permission_classes = [IsOsmAuthenticated]
permission_allowed_methods = ["GET"]
queryset = FeedbackAOI.objects.all()
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = FeedbackAOISerializer
filterset_fields = [
"training",
"user",
]


class FeedbackLabelViewset(viewsets.ModelViewSet):
authentication_classes = [OsmAuthentication]
permission_classes = [IsOsmAuthenticated]
permission_allowed_methods = ["GET"]
queryset = FeedbackLabel.objects.all()
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = FeedbackLabelSerializer
filterset_fields = ["feedback_aoi"]


class ModelViewSet(
Expand Down Expand Up @@ -208,7 +242,7 @@ def post(self, request, aoi_id, *args, **kwargs):
"""
obj = get_object_or_404(AOI, id=aoi_id)
try:
obj.download_status = 0
obj.label_status = 0
obj.save()
raw_data_params = {
"geometry": json.loads(obj.geom.geojson),
Expand All @@ -218,12 +252,12 @@ def post(self, request, aoi_id, *args, **kwargs):
result = request_rawdata(raw_data_params)
file_download_url = result["download_url"]
process_rawdata(file_download_url, aoi_id)
obj.download_status = 1
obj.last_fetched_date = datetime.utcnow()
obj.label_status = 1
obj.label_fetched = datetime.utcnow()
obj.save()
return Response("Success", status=status.HTTP_201_CREATED)
except Exception as ex:
obj.download_status = -1
obj.label_status = -1
obj.save()
# raise ex
return Response("OSM Fetch Failed", status=500)
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ services:
volumes:
- ./backend:/app
- ${RAMP_HOME}:/RAMP_HOME
- ${TRAINING_WORKSPACE}:/TRAINING_WORKSPACE
# - ${TRAINING_WORKSPACE}:/TRAINING_WORKSPACE
depends_on:
- redis
- postgres
Expand All @@ -55,7 +55,7 @@ services:
volumes:
- ./backend:/app
- ${RAMP_HOME}:/RAMP_HOME
- ${TRAINING_WORKSPACE}:/TRAINING_WORKSPACE
# - ${TRAINING_WORKSPACE}:/TRAINING_WORKSPACE
depends_on:
- backend-api
- redis
Expand Down
Binary file added frontend/public/rapid-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions frontend/src/components/Layout/AIModels/AIModelEditor/Feedback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { IconButton, Tooltip } from "@mui/material";
import React, { useContext } from "react";
import InfoIcon from "@mui/icons-material/Info";
import FeedbackIcon from "@mui/icons-material/Feedback";
import { useQuery } from "react-query";
import axios from "../../../../axios";
import AuthContext from "../../../../Context/AuthContext";
import FindReplaceIcon from "@mui/icons-material/FindReplace";
const Feedback = ({ trainingId }) => {
const { accessToken } = useContext(AuthContext);
const getFeedback = async () => {
try {
const headers = {
"access-token": accessToken,
};
const res = await axios.get(`/feedback/?training=${trainingId}`, null, {
headers,
});

if (res.error) {
} else {
// console.log(`/feedback/?training=${trainingId}`, res.data);
return res.data;
}
} catch (e) {
console.log("isError", e);
} finally {
}
};
const { data: feedbackData, isLoading } = useQuery(
"getFeedback" + trainingId,
getFeedback,
{
refetchInterval: 10000,
}
);

return (
<>
{feedbackData &&
feedbackData.features &&
feedbackData.features.length > 0 && (
<Tooltip
title={`Total number of feedback on this training is ${feedbackData.features.length}`}
placement="left"
>
<IconButton aria-label="popup">
<FeedbackIcon size="small" />
</IconButton>
</Tooltip>
)}
{isLoading && (
<IconButton aria-label="popup">
<FindReplaceIcon size="small" />
</IconButton>
)}
</>
);
};
export default Feedback;
27 changes: 17 additions & 10 deletions frontend/src/components/Layout/AIModels/AIModelEditor/Trainings.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Popup from "./Popup";
import InfoIcon from "@mui/icons-material/Info";

import AuthContext from "../../../../Context/AuthContext";
import Feedback from "./Feedback";

const DEFAULT_FILTER = {
items: [{ columnField: "created_date", id: 8537, operatorValue: "contains" }],
Expand Down Expand Up @@ -151,16 +152,22 @@ const TrainingsList = (props) => {
},
{
field: "popup",
headerName: "Info",
width: 10,
renderCell: (params) => (
<IconButton
onClick={() => handlePopupOpen(params.row)}
aria-label="popup"
>
<InfoIcon size="small" />
</IconButton>
),
headerName: "Info/Feedback",
width: 100,
renderCell: (params) => {
// console.log("params in info row", params);
return (
<div>
<IconButton
onClick={() => handlePopupOpen(params.row)}
aria-label="popup"
>
<InfoIcon size="small" />
</IconButton>
<Feedback trainingId={params.row.id}></Feedback>
</div>
);
},
},
{
field: "accuracy",
Expand Down
Loading

0 comments on commit ea61b47

Please sign in to comment.