Skip to content
This repository has been archived by the owner on Jan 7, 2025. It is now read-only.

Commit

Permalink
Merge pull request #835 from gheinrich/dev/optional-padding
Browse files Browse the repository at this point in the history
Make padding optional in object detection data extension
  • Loading branch information
lukeyeager authored Jun 22, 2016
2 parents f837bf2 + f677af2 commit 5e05455
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 65 deletions.
48 changes: 13 additions & 35 deletions digits/extensions/data/objectDetection/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from ..interface import DataIngestionInterface
from .forms import DatasetForm
from .utils import GroundTruth, GroundTruthObj
from .utils import bbox_to_array, resize_bbox_list
from .utils import bbox_to_array, pad_image, resize_bbox_list

TEMPLATE = "template.html"

Expand All @@ -41,12 +41,6 @@ def __init__(self, **kwargs):
else:
self.class_mappings = None

if ((self.val_image_folder == '') ^ (self.val_label_folder == '')):
raise ValueError("You must specify either both val_image_folder and val_label_folder or none")

if ((self.resize_image_width is None) ^ (self.resize_image_height is None)):
raise ValueError("You must specify either both resize_image_width and resize_image_height or none")

# this will be set when we know the phase we are encoding
self.ground_truth = None

Expand All @@ -59,20 +53,26 @@ def encode_entry(self, entry):

# (1) image part

# load from file
# load from file (this returns a PIL image)
img = digits.utils.image.load_image(image_filename)
if self.channel_conversion != 'none':
if img.mode != self.channel_conversion:
# convert to different image mode if necessary
img = img.convert(self.channel_conversion)

# pad
img = self.pad_image(img)
# note: the form validator ensured that either none
# or both width/height were specified
if self.padding_image_width:
# pad image
img = pad_image(
img,
self.padding_image_height,
self.padding_image_width)

if self.resize_image_width is not None:
# resize
resize_ratio_x = float(self.resize_image_width) / self.padding_image_width
resize_ratio_y = float(self.resize_image_height) / self.padding_image_height
resize_ratio_x = float(self.resize_image_width) / img.size[0]
resize_ratio_y = float(self.resize_image_height) / img.size[1]
# resize and convert to numpy HWC
img = digits.utils.image.resize_image(
img,
self.resize_image_height,
Expand Down Expand Up @@ -234,25 +234,3 @@ def make_image_list(self, folder):
# shuffle
random.shuffle(image_files)
return image_files

def pad_image(self, img):
"""
pad a single image to the dimensions specified in form
"""
src_width = img.size[0]
src_height = img.size[1]

if self.padding_image_width < src_width:
raise ValueError("Source image width %d is greater than padding width %d" % (src_width, self.padding_image_width))

if self.padding_image_height < src_height:
raise ValueError("Source image height %d is greater than padding height %d" % (src_height, self.padding_image_height))

padded_img = PIL.Image.new(
img.mode,
(self.padding_image_width, self.padding_image_height),
"black")

padded_img.paste(img, (0, 0)) # copy to top-left corner

return padded_img
18 changes: 9 additions & 9 deletions digits/extensions/data/objectDetection/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from digits import utils
from digits.utils import subclass

from digits.utils.forms import validate_required_if_set

@subclass
class DatasetForm(Form):
Expand Down Expand Up @@ -46,7 +46,7 @@ def validate_folder_path(form, field):
val_image_folder = utils.forms.StringField(
u'Validation image folder',
validators=[
validators.Optional(),
validate_required_if_set('val_label_folder'),
validate_folder_path,
],
tooltip="Indicate a folder of images to use for training"
Expand All @@ -55,7 +55,7 @@ def validate_folder_path(form, field):
val_label_folder = utils.forms.StringField(
u'Validation label folder',
validators=[
validators.Optional(),
validate_required_if_set('val_image_folder'),
validate_folder_path,
],
tooltip="Indicate a folder of validation labels"
Expand All @@ -64,7 +64,7 @@ def validate_folder_path(form, field):
resize_image_width = utils.forms.IntegerField(
u'Resize Image Width',
validators=[
validators.Optional(),
validate_required_if_set('resize_image_height'),
validators.NumberRange(min=1),
],
tooltip="If specified, images will be resized to that dimension after padding"
Expand All @@ -73,7 +73,7 @@ def validate_folder_path(form, field):
resize_image_height = utils.forms.IntegerField(
u'Resize Image Height',
validators=[
validators.Optional(),
validate_required_if_set('resize_image_width'),
validators.NumberRange(min=1),
],
tooltip="If specified, images will be resized to that dimension after padding"
Expand All @@ -83,20 +83,20 @@ def validate_folder_path(form, field):
u'Padding Image Width',
default=1248,
validators=[
validators.DataRequired(),
validate_required_if_set('padding_image_height'),
validators.NumberRange(min=1),
],
tooltip="Images will be padded to that dimension"
tooltip="If specified, images will be padded to that dimension"
)

padding_image_height = utils.forms.IntegerField(
u'Padding Image Height',
default=384,
validators=[
validators.DataRequired(),
validate_required_if_set('padding_image_width'),
validators.NumberRange(min=1),
],
tooltip="Images will be padded to that dimension"
tooltip="If specified, images will be padded to that dimension"
)

channel_conversion = utils.forms.SelectField(
Expand Down
44 changes: 24 additions & 20 deletions digits/extensions/data/objectDetection/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,32 @@ <h3>Object Detection Dataset Options</h3>
{{ form.val_label_folder(class='form-control autocomplete_path', placeholder='folder')}}
</div>

<div class="form-group{{mark_errors([form.padding_image_width])}}">
{{ form.padding_image_width.label }}
{{ form.padding_image_width.tooltip }}
{{ form.padding_image_width(class='form-control')}}
<div class="form-group{{mark_errors([form.padding_image_width, form.padding_image_height])}}">
<label>Pad image (Width x Height)</label>
<span name="pad_dims_explanation"
class="explanation-tooltip glyphicon glyphicon-question-sign"
data-container="body"
title="If specified, input images will be padded to that dimension. Pad dimensions should be greater than those of input images."
></span>
<div class="input-group">
{{ form.padding_image_width(size=4, placeholder='width', class='form-control') }}
<span class="input-group-addon">x</span>
{{ form.padding_image_height(size=4, placeholder='height', class='form-control') }}
</div>
</div>

<div class="form-group{{mark_errors([form.padding_image_height])}}">
{{ form.padding_image_height.label }}
{{ form.padding_image_height.tooltip }}
{{ form.padding_image_height(class='form-control')}}
</div>

<div class="form-group{{mark_errors([form.resize_image_width])}}">
{{ form.resize_image_width.label }}
{{ form.resize_image_width.tooltip }}
{{ form.resize_image_width(class='form-control')}}
</div>

<div class="form-group{{mark_errors([form.resize_image_height])}}">
{{ form.resize_image_height.label }}
{{ form.resize_image_height.tooltip }}
{{ form.resize_image_height(class='form-control')}}
<div class="form-group{{mark_errors([form.resize_image_width, form.resize_image_height])}}">
<label>Resize image (Width x Height)</label>
<span name="resize_dims_explanation"
class="explanation-tooltip glyphicon glyphicon-question-sign"
data-container="body"
title="If specified, input images will be squashed to that dimension after padding."
></span>
<div class="input-group">
{{ form.resize_image_width(size=4, placeholder='width', class='form-control') }}
<span class="input-group-addon">x</span>
{{ form.resize_image_height(size=4, placeholder='height', class='form-control') }}
</div>
</div>

<div class="form-group{{mark_errors([form.channel_conversion])}}">
Expand Down
27 changes: 26 additions & 1 deletion digits/extensions/data/objectDetection/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.

import csv
import numpy as np
import os

import numpy as np
import PIL.Image

class ObjectType:

Dontcare, Car, Van, Truck, Bus, Pickup, VehicleWithTrailer, SpecialVehicle,\
Expand Down Expand Up @@ -268,6 +270,29 @@ def bbox_overlap(abox, bbox):

return overlap_pix, overlap_box


def pad_image(img, padding_image_height, padding_image_width):
"""
pad a single image to the specified dimensions
"""
src_width = img.size[0]
src_height = img.size[1]

if padding_image_width < src_width:
raise ValueError("Source image width %d is greater than padding width %d" % (src_width, padding_image_width))

if padding_image_height < src_height:
raise ValueError("Source image height %d is greater than padding height %d" % (src_height, padding_image_height))

padded_img = PIL.Image.new(
img.mode,
(padding_image_width, padding_image_height),
"black")
padded_img.paste(img, (0, 0)) # copy to top-left corner

return padded_img


def resize_bbox_list(bboxlist, rescale_x=1, rescale_y=1):
# this is expecting x1,y1,w,h:
bboxListNew = []
Expand Down
25 changes: 25 additions & 0 deletions digits/utils/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ def _validator(form, field):

return _validator

def validate_required_if_set(other_field, **kwargs):
"""
Used as a validator within a wtforms.Form
This implements a conditional DataRequired
`other_field` is a field name; if set, the other field makes it mandatory
to set the field being tested
"""
def _validator(form, field):
other_field_value = getattr(form, other_field).data
if other_field_value is not None and other_field_value is not "":
# Verify that data exists
if field.data is None \
or (isinstance(field.data, (str, unicode))
and not field.data.strip()) \
or (isinstance(field.data, FileStorage)
and not field.data.filename.strip()):
raise validators.ValidationError('This field is required if %s is set.' % other_field)
else:
# This field is not required, ignore other errors
field.errors[:] = []
raise validators.StopValidation()

return _validator

def validate_greater_than(fieldname):
"""
Compares the value of two fields the value of self is to be greater than the supplied field.
Expand Down

0 comments on commit 5e05455

Please sign in to comment.