Skip to content

Commit

Permalink
feat(multimasks) : Added multimasks input parameter to API and frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
kshitijrajsharma committed Mar 24, 2024
1 parent 8683021 commit fa84c60
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 33 deletions.
54 changes: 38 additions & 16 deletions backend/core/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,14 @@
import os
import shutil
import sys
import tarfile
import traceback
from shutil import rmtree
import tarfile

import hot_fair_utilities
import ramp.utils
import tensorflow as tf
from celery import shared_task
from django.conf import settings
from django.contrib.gis.db.models.aggregates import Extent
from django.contrib.gis.geos import GEOSGeometry
from django.shortcuts import get_object_or_404
from django.utils import timezone
from hot_fair_utilities import preprocess, train
from hot_fair_utilities.training import run_feedback
from predictor import download_imagery, get_start_end_download_coords

from core.models import AOI, Feedback, FeedbackAOI, FeedbackLabel, Label, Training
from core.serializers import (
AOISerializer,
Expand All @@ -29,6 +20,14 @@
LabelFileSerializer,
)
from core.utils import bbox, is_dir_empty
from django.conf import settings
from django.contrib.gis.db.models.aggregates import Extent
from django.contrib.gis.geos import GEOSGeometry
from django.shortcuts import get_object_or_404
from django.utils import timezone
from hot_fair_utilities import preprocess, train
from hot_fair_utilities.training import run_feedback
from predictor import download_imagery, get_start_end_download_coords

logger = logging.getLogger(__name__)

Expand All @@ -37,6 +36,7 @@

DEFAULT_TILE_SIZE = 256


def xz_folder(folder_path, output_filename, remove_original=False):
"""
Compresses a folder and its contents into a .tar.xz file and optionally removes the original folder.
Expand All @@ -47,8 +47,8 @@ def xz_folder(folder_path, output_filename, remove_original=False):
- remove_original: If True, the original folder is removed after compression.
"""

if not output_filename.endswith('.tar.xz'):
output_filename += '.tar.xz'
if not output_filename.endswith(".tar.xz"):
output_filename += ".tar.xz"

with tarfile.open(output_filename, "w:xz") as tar:
tar.add(folder_path, arcname=os.path.basename(folder_path))
Expand All @@ -67,6 +67,9 @@ def train_model(
source_imagery,
feedback=None,
freeze_layers=False,
multimasks=False,
input_contact_spacing=0.75,
input_boundary_width=0.5,
):
training_instance = get_object_or_404(Training, id=training_id)
training_instance.status = "RUNNING"
Expand Down Expand Up @@ -182,13 +185,22 @@ def train_model(
# preprocess
model_input_image_path = f"{base_path}/input"
preprocess_output = f"/{base_path}/preprocessed"

if multimasks:
logger.info(
"Using multiple masks for training : background, footprint, boundary, contact"
)
else:
logger.info("Using binary masks for training : background, footprint")
preprocess(
input_path=model_input_image_path,
output_path=preprocess_output,
rasterize=True,
rasterize_options=["binary"],
georeference_images=True,
multimasks=True,
multimasks=multimasks,
input_contact_spacing=input_contact_spacing,
input_boundary_width=input_boundary_width,
)

# train
Expand Down Expand Up @@ -273,9 +285,19 @@ def train_model(
f.write(json.dumps(aoi_serializer.data))

# copy aois and labels to preprocess output before compressing it to tar
shutil.copyfile(os.path.join(output_path, "aois.geojson"), os.path.join(preprocess_output,'aois.geojson'))
shutil.copyfile(os.path.join(output_path, "labels.geojson"), os.path.join(preprocess_output,'labels.geojson'))
xz_folder(preprocess_output, os.path.join(output_path, "preprocessed.tar.xz"), remove_original=True)
shutil.copyfile(
os.path.join(output_path, "aois.geojson"),
os.path.join(preprocess_output, "aois.geojson"),
)
shutil.copyfile(
os.path.join(output_path, "labels.geojson"),
os.path.join(preprocess_output, "labels.geojson"),
)
xz_folder(
preprocess_output,
os.path.join(output_path, "preprocessed.tar.xz"),
remove_original=True,
)

# now remove the ramp-data all our outputs are copied to our training workspace
shutil.rmtree(base_path)
Expand Down
58 changes: 42 additions & 16 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ class DatasetViewSet(
class TrainingSerializer(
serializers.ModelSerializer
): # serializers are used to translate models objects to api

multimasks = serializers.BooleanField(required=False, default=False)
input_contact_spacing = serializers.FloatField(
required=False, default=0.75, min_value=0, max_value=5
)
input_boundary_width = serializers.FloatField(
required=False, default=0.5, min_value=0, max_value=5
)

class Meta:
model = Training
fields = "__all__" # defining all the fields to be included in curd for now , we can restrict few if we want
Expand Down Expand Up @@ -128,6 +137,10 @@ def create(self, validated_data):
# create the model instance
instance = Training.objects.create(**validated_data)

multimasks = validated_data.get("multimasks", False)
input_contact_spacing = validated_data.get("input_contact_spacing", 0.75)
input_boundary_width = validated_data.get("input_boundary_width", 0.5)

# run your function here
task = train_model.delay(
dataset_id=instance.model.dataset.id,
Expand All @@ -138,9 +151,14 @@ def create(self, validated_data):
source_imagery=instance.source_imagery
or instance.model.dataset.source_imagery,
freeze_layers=instance.freeze_layers,
multimasks=multimasks,
input_contact_spacing=input_contact_spacing,
input_boundary_width=input_boundary_width,
)
if not instance.source_imagery:
instance.source_imagery = instance.model.dataset.source_imagery
if multimasks:
instance.description += f" Multimask params (ct/bw): {input_contact_spacing}/{input_boundary_width}"
instance.task_id = task.id
instance.save()
print(f"Saved train model request to queue with id {task.id}")
Expand Down Expand Up @@ -192,7 +210,7 @@ class FeedbackLabelViewset(viewsets.ModelViewSet):
bbox_filter_field = "geom"
filter_backends = (
InBBoxFilter, # it will take bbox like this api/v1/label/?in_bbox=-90,29,-89,35 ,
DjangoFilterBackend
DjangoFilterBackend,
)
bbox_filter_include_overlapping = True
filterset_fields = ["feedback_aoi", "feedback_aoi__training"]
Expand Down Expand Up @@ -343,9 +361,9 @@ def download_training_data(request, dataset_id: int):
response = HttpResponse(open(zip_temp_path, "rb"))
response.headers["Content-Type"] = "application/x-zip-compressed"

response.headers[
"Content-Disposition"
] = f"attachment; filename=training_{dataset_id}_all_data.zip"
response.headers["Content-Disposition"] = (
f"attachment; filename=training_{dataset_id}_all_data.zip"
)
return response
else:
# "error": "File Doesn't Exist or has been cleared up from system",
Expand Down Expand Up @@ -553,12 +571,16 @@ def post(self, request, *args, **kwargs):
zoom_level=zoom_level,
tms_url=source,
tile_size=DEFAULT_TILE_SIZE,
confidence=deserialized_data["confidence"] / 100
if "confidence" in deserialized_data
else 0.5,
tile_overlap_distance=deserialized_data["tile_overlap_distance"]
if "tile_overlap_distance" in deserialized_data
else 0.15,
confidence=(
deserialized_data["confidence"] / 100
if "confidence" in deserialized_data
else 0.5
),
tile_overlap_distance=(
deserialized_data["tile_overlap_distance"]
if "tile_overlap_distance" in deserialized_data
else 0.15
),
)
print(
f"It took {round(time.time()-start_time)}sec for generating predictions"
Expand All @@ -569,12 +591,16 @@ def post(self, request, *args, **kwargs):
if use_josm_q is True:
feature["geometry"] = othogonalize_poly(
feature["geometry"],
maxAngleChange=deserialized_data["max_angle_change"]
if "max_angle_change" in deserialized_data
else 15,
skewTolerance=deserialized_data["skew_tolerance"]
if "skew_tolerance" in deserialized_data
else 15,
maxAngleChange=(
deserialized_data["max_angle_change"]
if "max_angle_change" in deserialized_data
else 15
),
skewTolerance=(
deserialized_data["skew_tolerance"]
if "skew_tolerance" in deserialized_data
else 15
),
)

print(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import axios from "../../../../axios";
import { useMutation, useQuery } from "react-query";
import Popup from "./Popup";

import { Accordion, AccordionSummary, AccordionDetails } from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";

import OSMUser from "../../../Shared/OSMUser";
import SaveIcon from "@material-ui/icons/Save";
import { Checkbox, FormControlLabel } from "@mui/material";
Expand All @@ -35,6 +38,10 @@ const AIModelEditor = (props) => {
const [sourceImagery, setSourceImagery] = React.useState(null);
const [freezeLayers, setFreezeLayers] = useState(false);

const [multimasks, setMultimasks] = React.useState(false);
const [inputContactSpacing, setInputContactSpacing] = React.useState(0.75);
const [inputBoundaryWidth, setInputBoundaryWidth] = React.useState(0.5);

const [popupRowData, setPopupRowData] = useState(null);
const [feedbackCount, setFeedbackCount] = useState(0);
const [feedbackData, setFeedbackData] = useState(null);
Expand Down Expand Up @@ -123,6 +130,9 @@ const AIModelEditor = (props) => {
model: id,
zoom_level: zoomLevel,
description: description,
input_contact_spacing: inputContactSpacing,
input_boundary_width: inputBoundaryWidth,
multimasks: multimasks,
};
const headers = {
"access-token": accessToken,
Expand Down Expand Up @@ -310,7 +320,7 @@ const AIModelEditor = (props) => {
helperText={
<span>
A short description to document why you submitted this
training
training or extra additional info
</span>
}
type="text"
Expand Down Expand Up @@ -339,6 +349,130 @@ const AIModelEditor = (props) => {
</FormGroup>
</FormControl>
</Grid> */}
<Grid item xs={6}>
<Accordion sx={{ boxShadow: "none", background: "none" }}>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={{ fontSize: "1rem" }} />}
aria-controls="panel1a-content"
id="panel1a-header"
sx={{
minHeight: "32px",
height: "32px",
background: "white !important",
"& .MuiAccordionSummary-content": {
margin: "0",
alignItems: "center",
},
"& .MuiAccordionSummary-expandIconWrapper": {
padding: "0",
"&.Mui-expanded": {
transform: "rotate(180deg)",
},
},
// Prevent changes in background or elevation when expanded
"&.Mui-expanded": {
minHeight: "32px",
margin: "0",
},
"&:hover": {
background: "white",
},
"&.Mui-focusVisible": {
backgroundColor: "white",
},
}}
>
<Typography
variant="body2"
sx={{ color: "primary", fontSize: "0.875rem" }}
>
Advanced Parameters
</Typography>
</AccordionSummary>
<AccordionDetails sx={{ padding: "8px 16px 16px" }}>
<Grid container spacing={1}>
<Grid item xs={12}>
<FormControlLabel
control={
<Checkbox
sx={{ transform: "scale(0.8)", marginLeft: "-10px" }}
checked={multimasks}
onChange={(e) => setMultimasks(e.target.checked)}
name="multimasks"
/>
}
label={
<Typography
variant="body2"
sx={{ fontSize: "0.875rem", padding: "1" }}
>
Take boundary of footprints into account during
training
</Typography>
}
sx={{ margin: "0" }}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
size="small"
id="input-contact-spacing"
label="Input Contact Spacing"
type="number"
helperText={
<span>
Enter the distance in meters to extend the area around
each building. This will be used to find points where
buildings come into contact or are in close proximity
to one another. For example, entering '0.75' will
explore areas within 75 centimers outside the original
building shapes to detect nearby buildings
</span>
}
value={inputContactSpacing}
fullWidth
onChange={(e) => setInputContactSpacing(e.target.value)}
InputProps={{
sx: { fontSize: "0.875rem", height: "40px" },
}}
InputLabelProps={{
sx: { fontSize: "0.875rem" },
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
size="small"
id="input-boundary-width"
label="Input Boundary Width"
type="number"
value={inputBoundaryWidth}
helperText={
<span>
Specify the width in meters to reduce the original
building shape inwardly, creating a boundary or margin
around each building. A smaller value creates a
tighter boundary close to the building's edges, while
a larger value creates a wider surrounding area. For
example, entering '0.5' will create a boundary that is
50 centimeters inside from the original building
edges.
</span>
}
fullWidth
onChange={(e) => setInputBoundaryWidth(e.target.value)}
InputProps={{
sx: { fontSize: "0.875rem", height: "40px" },
}}
InputLabelProps={{
sx: { fontSize: "0.875rem" },
}}
/>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
</Grid>

<Grid item xs={12} md={12}></Grid>
<Grid item xs={6} md={6}>
Expand Down

0 comments on commit fa84c60

Please sign in to comment.