-
Notifications
You must be signed in to change notification settings - Fork 15
fix: ED-2306 Make dynamic attributes unaware of spaces #1084
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
Changes from 2 commits
5c27b2b
25ce831
9f1a62d
d64a464
4989d0b
3bb7c36
ea5bdb0
b5fc876
445abc4
84f0ac9
75d0f98
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -342,101 +342,13 @@ def _remove_all_answers_from_frames(self, object_instance: ObjectInstance, frame | |
| object_instance: The object instance to remove answers from. | ||
| frames: List of frame numbers to remove answers from. | ||
| """ | ||
| dynamic_answer_manager = self._object_hash_to_dynamic_answer_manager.get(object_instance.object_hash) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of this was initially used in |
||
| if dynamic_answer_manager is None: | ||
| # No dynamic answers to remove | ||
| return | ||
|
|
||
| # Get all dynamic attributes for this object | ||
| dynamic_attributes = [attr for attr in object_instance._ontology_object.attributes if attr.dynamic] | ||
|
|
||
| # Remove answers for each dynamic attribute on the specified frames | ||
| for attribute in dynamic_attributes: | ||
| dynamic_answer_manager.delete_answer(attribute, frames=frames) | ||
|
|
||
| # This implementation is copied mostly from ObjectInstance.set_answer_from_list | ||
| def _set_answer_from_list(self, object_hash: str, answers_list: List[Dict[str, Any]]): | ||
| grouped_answers = defaultdict(list) | ||
| object_instance = self._objects_map[object_hash] | ||
| dynamic_answer_manager = self._object_hash_to_dynamic_answer_manager[object_instance.object_hash] | ||
|
|
||
| for answer_dict in answers_list: | ||
| attribute = _get_attribute_by_hash(answer_dict["featureHash"], object_instance._ontology_object.attributes) | ||
| if attribute is None: | ||
| raise LabelRowError( | ||
| "One of the attributes does not exist in the ontology. Cannot create a valid LabelRow." | ||
| ) | ||
| if not object_instance._is_attribute_valid_child_of_object_instance(attribute): | ||
| raise LabelRowError( | ||
| "One of the attributes set for a classification is not a valid child of the classification. " | ||
| "Cannot create a valid LabelRow." | ||
| ) | ||
|
|
||
| grouped_answers[attribute.feature_node_hash].append(answer_dict) | ||
|
|
||
| for feature_hash, answers_list in grouped_answers.items(): | ||
| attribute = _get_attribute_by_hash(feature_hash, object_instance._ontology_object.attributes) | ||
| assert attribute # we already checked that attribute is not null above. So just silencing this for now | ||
| self._set_answer_from_grouped_list(dynamic_answer_manager, attribute, answers_list) | ||
|
|
||
| def _set_answer_from_grouped_list( | ||
| self, dynamic_answer_manager: DynamicAnswerManager, attribute: Attribute, answers_list: List[Dict[str, Any]] | ||
| ) -> None: | ||
| if isinstance(attribute, ChecklistAttribute): | ||
| if not attribute.dynamic: | ||
| raise LabelRowError("This method should not be called for non-dynamic attributes.") | ||
| else: | ||
| all_feature_hashes: Set[str] = set() | ||
| ranges = [] | ||
| for answer_dict in answers_list: | ||
| feature_hashes: Set[str] = {answer["featureHash"] for answer in answer_dict["answers"]} | ||
| all_feature_hashes.update(feature_hashes) | ||
| for frame_range in ranges_list_to_ranges(answer_dict["range"]): | ||
| ranges.append((frame_range, feature_hashes)) | ||
|
|
||
| options_cache = { | ||
| feature_hash: attribute.get_child_by_hash(feature_hash, type_=Option) | ||
| for feature_hash in all_feature_hashes | ||
| } | ||
|
|
||
| for frame_range, feature_hashes in ObjectInstance._merge_answers_to_non_overlapping_ranges(ranges): | ||
| options = [options_cache[feature_hash] for feature_hash in feature_hashes] | ||
| dynamic_answer_manager.set_answer(options, attribute, [frame_range]) | ||
| else: | ||
| for answer in answers_list: | ||
| self._set_answer_from_dict(dynamic_answer_manager, answer, attribute) | ||
|
|
||
| def _set_answer_from_dict( | ||
| self, dynamic_answer_manager: DynamicAnswerManager, answer_dict: Dict[str, Any], attribute: Attribute | ||
| ) -> None: | ||
| if not attribute.dynamic: | ||
| raise LabelRowError("This method should not be called for non-dynamic attributes.") | ||
|
|
||
| ranges = ranges_list_to_ranges(answer_dict["range"]) | ||
|
|
||
| if isinstance(attribute, TextAttribute): | ||
| dynamic_answer_manager.set_answer(answer_dict["answers"], attribute, ranges) | ||
| elif isinstance(attribute, RadioAttribute): | ||
| if len(answer_dict["answers"]) == 1: | ||
| feature_hash = answer_dict["answers"][0]["featureHash"] | ||
| option = attribute.get_child_by_hash(feature_hash, type_=Option) | ||
| dynamic_answer_manager.set_answer(option, attribute, ranges) | ||
| elif isinstance(attribute, ChecklistAttribute): | ||
| options = [] | ||
| for answer in answer_dict["answers"]: | ||
| feature_hash = answer["featureHash"] | ||
| option = attribute.get_child_by_hash(feature_hash, type_=Option) | ||
| options.append(option) | ||
| dynamic_answer_manager.set_answer(options, attribute, ranges) | ||
| elif isinstance(attribute, NumericAttribute): | ||
| value: float = answer_dict["answers"] | ||
|
|
||
| if not isinstance(value, float) and not isinstance(value, int): | ||
| raise LabelRowError(f"The answer for a numeric attribute must be a float or an int. Found {value}.") | ||
|
|
||
| dynamic_answer_manager.set_answer(value, attribute, ranges) | ||
| else: | ||
| raise NotImplementedError(f"The attribute type {type(attribute)} is not supported.") | ||
| object_instance._dynamic_answer_manager.delete_answer(attribute, frames=frames) | ||
|
|
||
| def set_dynamic_answer( | ||
| self, | ||
|
|
@@ -464,21 +376,21 @@ def set_dynamic_answer( | |
| or if the object doesn't exist on the space yet. | ||
| """ | ||
| self._label_row._check_labelling_is_initalised() | ||
| if attribute is None: | ||
| attribute = _infer_attribute_from_answer(object_instance._ontology_object.attributes, answer) | ||
| if not object_instance._is_attribute_valid_child_of_object_instance(attribute): | ||
| raise LabelRowError("The attribute is not a valid child of the object.") | ||
| elif not attribute.dynamic and not object_instance._is_selectable_child_attribute(attribute): | ||
| raise LabelRowError( | ||
| "Setting a nested attribute is only possible if all parent attributes have been selected." | ||
| ) | ||
| elif attribute.dynamic is False: | ||
|
|
||
| if object_instance.object_hash not in self._objects_map: | ||
| raise LabelRowError( | ||
| "This method should only be used for dynamic attributes. For static attributes, use `ObjectInstance.set_answer`." | ||
| "Object does not yet exist on this space. Place the object on this space with `Space.place_object`." | ||
| ) | ||
|
|
||
| if attribute is None: | ||
| attribute = _infer_attribute_from_answer(object_instance._ontology_object.attributes, answer) | ||
|
|
||
| if not attribute.dynamic: | ||
| raise LabelRowError("This method should not be called for non-dynamic attributes.") | ||
|
|
||
| frames_list = frames_class_to_frames_list(frames) | ||
|
|
||
| # Check that frames do exist on this object on this space | ||
| valid_frames = [] | ||
| for frame in frames_list: | ||
| annotation_data = self._get_frame_object_annotation_data( | ||
|
|
@@ -487,13 +399,7 @@ def set_dynamic_answer( | |
| if annotation_data is not None: | ||
| valid_frames.append(frame) | ||
|
|
||
| dynamic_answer_manager = self._object_hash_to_dynamic_answer_manager.get(object_instance.object_hash) | ||
| if dynamic_answer_manager is None: | ||
| raise LabelRowError( | ||
| "Object does not yet exist on this space. Place the object on this space with `Space.place_object`." | ||
| ) | ||
|
|
||
| dynamic_answer_manager.set_answer(answer, attribute, frames=valid_frames) | ||
| object_instance.set_answer(answer, attribute, frames=valid_frames) | ||
|
|
||
| def remove_dynamic_answer( | ||
| self, | ||
|
|
@@ -518,13 +424,7 @@ def remove_dynamic_answer( | |
| if not attribute.dynamic: | ||
| raise LabelRowError("This method should not be called for non-dynamic attributes.") | ||
|
|
||
| dynamic_answer_manager = self._object_hash_to_dynamic_answer_manager.get(object_instance.object_hash) | ||
| if dynamic_answer_manager is None: | ||
| raise LabelRowError( | ||
| "Object does not yet exist on this space. Place the object on this space with `Space.place_object`." | ||
| ) | ||
|
|
||
| dynamic_answer_manager.delete_answer(attribute, frames=frame, filter_answer=filter_answer) | ||
| object_instance._dynamic_answer_manager.delete_answer(attribute, frames=frame, filter_answer=filter_answer) | ||
|
|
||
| def get_dynamic_answer( | ||
| self, | ||
|
|
@@ -557,7 +457,7 @@ def get_dynamic_answer( | |
| if not attribute.dynamic: | ||
| raise LabelRowError("This method should only be used for dynamic attributes.") | ||
|
|
||
| return dynamic_answer_manager.get_answer(attribute, filter_answer, filter_frames=frames) | ||
| return object_instance._dynamic_answer_manager.get_answer(attribute, filter_answer, filter_frames=frames) | ||
|
|
||
| def put_classification_instance( | ||
| self, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,8 +9,8 @@ | |
| from encord.objects.frames import Range | ||
| from tests.objects.data.all_types_ontology_structure import all_types_structure | ||
| from tests.objects.data.data_group.two_videos import ( | ||
| DATA_GROUP_METADATA, | ||
| DATA_GROUP_TWO_VIDEOS_NO_LABELS, | ||
| DATA_GROUP_WITH_TWO_VIDEOS_METADATA, | ||
| ) | ||
|
|
||
| keypoint_with_dynamic_attributes_ontology_item = all_types_structure.get_child_by_hash("MTY2MTQx", Object) | ||
|
|
@@ -21,7 +21,7 @@ | |
|
|
||
| def test_add_dynamic_attributes_to_frames_on_object_on_video_space(ontology): | ||
| # Arrange | ||
| label_row = LabelRowV2(DATA_GROUP_METADATA, Mock(), ontology) | ||
| label_row = LabelRowV2(DATA_GROUP_WITH_TWO_VIDEOS_METADATA, Mock(), ontology) | ||
| label_row.from_labels_dict(DATA_GROUP_TWO_VIDEOS_NO_LABELS) | ||
| video_space_1 = label_row.get_space(id="video-1-uuid", type_="video") | ||
|
|
||
|
|
@@ -36,7 +36,9 @@ def test_add_dynamic_attributes_to_frames_on_object_on_video_space(ontology): | |
| answer_on_frame_0 = "Frame 0" | ||
| answer_on_frame_1_and_2 = "Frame 1 and 2" | ||
|
|
||
| # Act | ||
| new_object_instance.set_answer(frames=[0], attribute=key_point_dynamic_text_attribute, answer=answer_on_frame_0) | ||
|
|
||
| # # Act | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| video_space_1.set_dynamic_answer( | ||
| object_instance=new_object_instance, | ||
| frames=[0], | ||
|
|
@@ -49,8 +51,8 @@ def test_add_dynamic_attributes_to_frames_on_object_on_video_space(ontology): | |
| attribute=key_point_dynamic_text_attribute, | ||
| answer=answer_on_frame_1_and_2, | ||
| ) | ||
|
|
||
| # Assert | ||
| # | ||
| # # Assert | ||
|
Comment on lines
+54
to
+55
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| actual_answers = video_space_1.get_dynamic_answer( | ||
| object_instance=new_object_instance, | ||
| frames=[0, 1, 2], | ||
|
|
@@ -70,7 +72,7 @@ def test_add_dynamic_attributes_to_frames_on_object_on_video_space(ontology): | |
|
|
||
| def test_remove_dynamic_attributes_from_frame_on_video_space(ontology): | ||
| # Arrange | ||
| label_row = LabelRowV2(DATA_GROUP_METADATA, Mock(), ontology) | ||
| label_row = LabelRowV2(DATA_GROUP_WITH_TWO_VIDEOS_METADATA, Mock(), ontology) | ||
| label_row.from_labels_dict(DATA_GROUP_TWO_VIDEOS_NO_LABELS) | ||
| video_space_1 = label_row.get_space(id="video-1-uuid", type_="video") | ||
|
|
||
|
|
@@ -111,7 +113,7 @@ def test_remove_dynamic_attributes_from_frame_on_video_space(ontology): | |
|
|
||
| def test_remove_object_from_frame_removes_dynamic_attributes_from_those_frames(ontology): | ||
| # Arrange | ||
| label_row = LabelRowV2(DATA_GROUP_METADATA, Mock(), ontology) | ||
| label_row = LabelRowV2(DATA_GROUP_WITH_TWO_VIDEOS_METADATA, Mock(), ontology) | ||
| label_row.from_labels_dict(DATA_GROUP_TWO_VIDEOS_NO_LABELS) | ||
| video_space_1 = label_row.get_space(id="video-1-uuid", type_="video") | ||
|
|
||
|
|
@@ -146,7 +148,7 @@ def test_remove_object_from_frame_removes_dynamic_attributes_from_those_frames(o | |
|
|
||
| def test_remove_object_removes_dynamic_attributes_for_that_object(ontology): | ||
| # Arrange | ||
| label_row = LabelRowV2(DATA_GROUP_METADATA, Mock(), ontology) | ||
| label_row = LabelRowV2(DATA_GROUP_WITH_TWO_VIDEOS_METADATA, Mock(), ontology) | ||
| label_row.from_labels_dict(DATA_GROUP_TWO_VIDEOS_NO_LABELS) | ||
| video_space_1 = label_row.get_space(id="video-1-uuid", type_="video") | ||
|
|
||
|
|
@@ -177,7 +179,7 @@ def test_remove_object_removes_dynamic_attributes_for_that_object(ontology): | |
|
|
||
| def test_add_dynamic_attributes_to_frames_where_object_does_not_exist_on_video_space(ontology): | ||
| # Arrange | ||
| label_row = LabelRowV2(DATA_GROUP_METADATA, Mock(), ontology) | ||
| label_row = LabelRowV2(DATA_GROUP_WITH_TWO_VIDEOS_METADATA, Mock(), ontology) | ||
| label_row.from_labels_dict(DATA_GROUP_TWO_VIDEOS_NO_LABELS) | ||
| video_space_1 = label_row.get_space(id="video-1-uuid", type_="video") | ||
|
|
||
|
|
@@ -212,7 +214,7 @@ def test_add_dynamic_attributes_to_frames_where_object_does_not_exist_on_video_s | |
|
|
||
| def test_add_dynamic_attributes_object_which_does_not_exist_on_video_space(ontology): | ||
| # Arrange | ||
| label_row = LabelRowV2(DATA_GROUP_METADATA, Mock(), ontology) | ||
| label_row = LabelRowV2(DATA_GROUP_WITH_TWO_VIDEOS_METADATA, Mock(), ontology) | ||
| label_row.from_labels_dict(DATA_GROUP_TWO_VIDEOS_NO_LABELS) | ||
| video_space_1 = label_row.get_space(id="video-1-uuid", type_="video") | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assignment to
answer_listis redundant as it was already assigned on line 3060. You can remove this line for better code clarity.