Skip to content

Commit 3bf282d

Browse files
authored
Merge pull request #365 from Labelbox/ms/in-line-validation
in-line validation
2 parents a95a93c + 393c8b4 commit 3bf282d

File tree

3 files changed

+87
-21
lines changed

3 files changed

+87
-21
lines changed

labelbox/schema/bulk_import_request.py

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -781,33 +781,60 @@ def is_valid_location(cls, v):
781781
return v
782782

783783

784-
class MaskFeatures(BaseModel):
785-
instanceURI: str
786-
colorRGB: Union[List[int], Tuple[int, int, int]]
784+
class RLEMaskFeatures(BaseModel):
785+
counts: List[int]
786+
size: List[int]
787787

788+
@validator('counts')
789+
def validate_counts(cls, counts):
790+
if not all([count >= 0 for count in counts]):
791+
raise ValueError(
792+
"Found negative value for counts. They should all be zero or positive"
793+
)
794+
return counts
788795

789-
class NDMask(NDBaseTool):
790-
ontology_type: Literal["superpixel"] = "superpixel"
791-
mask: MaskFeatures = pydantic.Field(determinant=True)
796+
@validator('size')
797+
def validate_size(cls, size):
798+
if len(size) != 2:
799+
raise ValueError(
800+
f"Mask `size` should have two ints representing height and with. Found : {size}"
801+
)
802+
if not all([count > 0 for count in size]):
803+
raise ValueError(
804+
f"Mask `size` should be a postitive int. Found : {size}")
805+
return size
792806

793-
@validator('mask')
794-
def is_valid_mask(cls, v):
795-
if isinstance(v, BaseModel):
796-
v = v.dict()
797807

798-
colors = v['colorRGB']
808+
class PNGMaskFeatures(BaseModel):
809+
# base64 encoded png bytes
810+
png: str
811+
812+
813+
class URIMaskFeatures(BaseModel):
814+
instanceURI: str
815+
colorRGB: Union[List[int], Tuple[int, int, int]]
816+
817+
@validator('colorRGB')
818+
def validate_color(cls, colorRGB):
799819
#Does the dtype matter? Can it be a float?
800-
if not isinstance(colors, (tuple, list)):
820+
if not isinstance(colorRGB, (tuple, list)):
801821
raise ValueError(
802-
f"Received color that is not a list or tuple. Found : {colors}")
803-
elif len(colors) != 3:
822+
f"Received color that is not a list or tuple. Found : {colorRGB}"
823+
)
824+
elif len(colorRGB) != 3:
804825
raise ValueError(
805-
f"Must provide RGB values for segmentation colors. Found : {colors}"
826+
f"Must provide RGB values for segmentation colors. Found : {colorRGB}"
806827
)
807-
elif not all([0 <= color <= 255 for color in colors]):
828+
elif not all([0 <= color <= 255 for color in colorRGB]):
808829
raise ValueError(
809-
f"All rgb colors must be between 0 and 255. Found : {colors}")
810-
return v
830+
f"All rgb colors must be between 0 and 255. Found : {colorRGB}")
831+
return colorRGB
832+
833+
834+
class NDMask(NDBaseTool):
835+
ontology_type: Literal["superpixel"] = "superpixel"
836+
mask: Union[URIMaskFeatures, PNGMaskFeatures,
837+
RLEMaskFeatures] = pydantic.Field(determinant=True)
811838

812839

813840
#A union with custom construction logic to improve error messages

tests/integration/annotation_import/conftest.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,33 @@ def segmentation_inference(prediction_id_mapping):
232232
return segmentation
233233

234234

235+
@pytest.fixture
236+
def segmentation_inference_rle(prediction_id_mapping):
237+
segmentation = prediction_id_mapping['superpixel'].copy()
238+
segmentation.update({
239+
'uuid': str(uuid.uuid4()),
240+
'mask': {
241+
'size': [10, 10],
242+
'counts': [1, 0, 10, 100]
243+
}
244+
})
245+
del segmentation['tool']
246+
return segmentation
247+
248+
249+
@pytest.fixture
250+
def segmentation_inference_png(prediction_id_mapping):
251+
segmentation = prediction_id_mapping['superpixel'].copy()
252+
segmentation.update({
253+
'uuid': str(uuid.uuid4()),
254+
'mask': {
255+
'png': "somedata",
256+
}
257+
})
258+
del segmentation['tool']
259+
return segmentation
260+
261+
235262
@pytest.fixture
236263
def checklist_inference(prediction_id_mapping):
237264
checklist = prediction_id_mapping['checklist'].copy()
@@ -283,10 +310,12 @@ def model_run_predictions(polygon_inference, rectangle_inference,
283310
# also used for label imports
284311
@pytest.fixture
285312
def object_predictions(polygon_inference, rectangle_inference, line_inference,
286-
entity_inference, segmentation_inference):
313+
entity_inference, segmentation_inference,
314+
segmentation_inference_rle, segmentation_inference_png):
287315
return [
288316
polygon_inference, rectangle_inference, line_inference,
289-
entity_inference, segmentation_inference
317+
entity_inference, segmentation_inference, segmentation_inference_rle,
318+
segmentation_inference_png
290319
]
291320

292321

tests/integration/annotation_import/test_ndjson_validation.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ def test_subclassification_construction(rectangle_inference):
2828
(fixture_ref('rectangle_inference'), NDRectangle),
2929
(fixture_ref('line_inference'), NDPolyline),
3030
(fixture_ref('entity_inference'), NDTextEntity),
31-
(fixture_ref('segmentation_inference'), NDMask)])
31+
(fixture_ref('segmentation_inference'), NDMask),
32+
(fixture_ref('segmentation_inference_rle'), NDMask),
33+
(fixture_ref('segmentation_inference_png'), NDMask)])
3234
def test_tool_construction(inference, expected_type):
3335
assert isinstance(NDTool.build(inference), expected_type)
3436

@@ -131,6 +133,14 @@ def test_incorrect_mask(segmentation_inference, configured_project):
131133
with pytest.raises(MALValidationError):
132134
_validate_ndjson([seg], configured_project)
133135

136+
seg['mask'] = {'counts': [0], 'size': [0, 1]}
137+
with pytest.raises(MALValidationError):
138+
_validate_ndjson([seg], configured_project)
139+
140+
seg['mask'] = {'counts': [-1], 'size': [1, 1]}
141+
with pytest.raises(MALValidationError):
142+
_validate_ndjson([seg], configured_project)
143+
134144

135145
def test_all_validate_json(configured_project, predictions):
136146
#Predictions contains one of each type of prediction.

0 commit comments

Comments
 (0)