Skip to content

Commit b55959e

Browse files
jws-1Sveali41m-barkerhaiwei-luofireblonde
authored
Gpsr state machine factory (#247)
Co-authored-by: Siyao <sveali41@gmail.com> Co-authored-by: m-barker <mattbarker322@gmail.com> Co-authored-by: Haiwei L <haiwei8809@gmail.com> Co-authored-by: fireblonde <nicollehchevska@gmail.com> Co-authored-by: Benteng Ma <benteng.ma@kcl.ac.uk>
1 parent 64a7495 commit b55959e

File tree

74 files changed

+6492
-1255
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+6492
-1255
lines changed

100644

Whitespace-only changes.

common/custom_worlds

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 315d43aeb2df031f6757b900503608896987521f

common/helpers/navigation_helpers/src/navigation_helpers/__init__.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1+
import rospy
2+
3+
14
from geometry_msgs.msg import (
25
Point,
36
Pose,
7+
PoseStamped,
8+
Quaternion,
49
)
10+
from nav_msgs.srv import GetPlan
11+
from nav_msgs.msg import Path
512

613
import numpy as np
14+
import math
15+
from scipy.spatial.transform import Rotation as R
716
from itertools import permutations
817

918
from typing import Union, List
@@ -27,3 +36,49 @@ def min_hamiltonian_path(start: Pose, poses: List[Pose]) -> Union[None, List[Pos
2736
best_order = list(perm)
2837

2938
return best_order
39+
40+
41+
def get_pose_on_path(
42+
p1: PoseStamped, p2: PoseStamped, dist_to_goal: float = 1.0, tolerance: float = 0.5
43+
) -> Union[None, PoseStamped]:
44+
make_plan: rospy.ServiceProxy = rospy.ServiceProxy("/move_base/make_plan", GetPlan)
45+
46+
chosen_pose: Union[None, PoseStamped] = None
47+
48+
rospy.loginfo(f"Getting plan from {p1} to {p2}.")
49+
50+
if p1.header.frame_id != p2.header.frame_id != "map":
51+
rospy.loginfo(
52+
f"Frames of reference are not 'map' ({p1.header.frame_id} and {p2.header.frame_id})."
53+
)
54+
return chosen_pose
55+
56+
try:
57+
make_plan.wait_for_service(timeout=rospy.Duration.from_sec(10.0))
58+
except rospy.ROSException:
59+
rospy.loginfo("Service /move_base/make_plan not available.")
60+
return chosen_pose
61+
62+
try:
63+
plan: Path = make_plan(p1, p2, tolerance).plan
64+
except rospy.ServiceException as e:
65+
rospy.loginfo(e)
66+
return chosen_pose
67+
68+
rospy.loginfo(f"Got plan with {len(plan.poses)} poses.")
69+
70+
if len(plan.poses) > 0:
71+
for pose in reversed(plan.poses):
72+
if euclidian_distance(pose.pose.position, p2.pose.position) >= dist_to_goal:
73+
chosen_pose = pose
74+
break
75+
76+
return chosen_pose
77+
78+
79+
def compute_face_quat(p1: Pose, p2: Pose) -> Quaternion:
80+
dx: float = p2.position.x - p1.position.x
81+
dy: float = p2.position.y - p1.position.y
82+
theta_deg = np.degrees(math.atan2(dy, dx))
83+
x, y, z, w = R.from_euler("z", theta_deg, degrees=True).as_quat()
84+
return Quaternion(x, y, z, w)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import numpy as np
2+
3+
4+
def numpy2message(np_array: np.ndarray) -> list[bytes, list[int], str]:
5+
data = np_array.tobytes()
6+
shape = list(np_array.shape)
7+
dtype = str(np_array.dtype)
8+
return data, shape, dtype
9+
10+
11+
def message2numpy(data: bytes, shape: list[int], dtype: str) -> np.ndarray:
12+
array_shape = tuple(shape)
13+
array_dtype = np.dtype(dtype)
14+
15+
deserialized_array = np.frombuffer(data, dtype=array_dtype)
16+
deserialized_array = deserialized_array.reshape(array_shape)
17+
18+
return deserialized_array

common/speech/lasr_speech_recognition_whisper/nodes/transcribe_microphone_server

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class TranscribeSpeechAction(object):
8787
self._listening = False
8888

8989
self._action_server.start()
90+
rospy.loginfo(f"Speech Action server {self._action_name} started")
9091

9192
def _configure_microphone(self) -> sr.Microphone:
9293
"""Configures the microphone for listening to speech based on the
@@ -332,8 +333,8 @@ def configure_model_params(config: dict) -> speech_model_params:
332333
model_params.mic_device = config["mic_device"]
333334
if config["no_warmup"]:
334335
model_params.warmup = False
335-
if config["energy_threshold"]:
336-
model_params.energy_threshold = config["energy_threshold"]
336+
# if config["energy_threshold"]:
337+
# model_params.energy_threshold = config["energy_threshold"]
337338
if config["pause_threshold"]:
338339
model_params.pause_threshold = config["pause_threshold"]
339340

common/speech/lasr_speech_recognition_whisper/scripts/microphone_tuning_test.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def main():
2828
args = parse_args()
2929

3030
recognizer = sr.Recognizer()
31+
recognizer.pause_threshold = 2
3132
microphone = sr.Microphone(device_index=args["device_index"], sample_rate=16000)
3233
threshold = 100
3334
recognizer.dynamic_energy_threshold = False
@@ -39,7 +40,9 @@ def main():
3940
while transcription_result != "":
4041
print(f"Listening...")
4142
with microphone as source:
42-
wav_data = recognizer.listen(source).get_wav_data()
43+
wav_data = recognizer.listen(
44+
source, phrase_time_limit=10, timeout=5
45+
).get_wav_data()
4346
print(f"Processing...")
4447
# Magic number 32768.0 is the maximum value of a 16-bit signed integer
4548
float_data = (

common/speech/lasr_speech_recognition_whisper/scripts/repeat_after_me.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
if USE_ACTIONLIB:
2020
client = actionlib.SimpleActionClient("transcribe_speech", TranscribeSpeechAction)
21+
rospy.loginfo("Waiting for server...")
2122
client.wait_for_server()
2223
repeating = False
2324
rospy.loginfo("Done waiting")

common/speech/lasr_speech_recognition_whisper/scripts/test_microphones.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ def main(args: dict) -> None:
3434
output_dir = args["output_dir"]
3535

3636
r = sr.Recognizer()
37-
with sr.Microphone(device_index=13, sample_rate=16000) as source:
37+
r.pause_threshold = 2
38+
with sr.Microphone(device_index=9, sample_rate=16000) as source:
3839
print("Say something!")
39-
audio = r.listen(source, timeout=5, phrase_time_limit=5)
40+
audio = r.listen(source, timeout=5, phrase_time_limit=10)
4041
print("Finished listening")
4142

4243
with open(os.path.join(output_dir, "microphone.raw"), "wb") as f:

common/vector_databases/lasr_vector_databases_faiss/nodes/txt_index_service

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class TxtIndexService:
3131
f"/tmp/xn.dat",
3232
dtype="float32",
3333
mode="w+",
34-
shape=(11779430, 384),
34+
shape=(8403420, 384),
3535
)
3636
for i, txt_fp in enumerate(txt_fps):
3737
sentences_to_embed: List[str] = parse_txt_file(txt_fp)

common/vector_databases/lasr_vector_databases_faiss/src/lasr_vector_databases_faiss/get_sentence_embeddings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def load_model(model_name: str = "all-MiniLM-L6-v2") -> SentenceTransformer:
1313
Returns:
1414
sentence_transformers.SentenceTransformer: the loaded model
1515
"""
16+
print(f"Loading model...")
1617
return SentenceTransformer(model_name, device=DEVICE)
1718

1819

common/vision/lasr_vision_cropped_detection/src/lasr_vision_cropped_detection/cropped_detection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ def _3d_bbox_crop(
171171
)
172172
for det in detections
173173
]
174+
174175
if crop_method == "closest":
175176
detections = [det for _, det in sorted(zip(distances, detections))]
176177
distances.sort()
177-
178178
elif crop_method == "furthest":
179179
detections = [
180180
det for _, det in sorted(zip(distances, detections), reverse=True)

common/vision/lasr_vision_feature_extraction/src/lasr_vision_feature_extraction/__init__.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import json
22
from os import path
3-
43
import cv2
54
import numpy as np
65
import rospkg
@@ -20,6 +19,25 @@
2019
from lasr_vision_msgs.srv import Vqa, VqaRequest
2120

2221

22+
def gaussian_blur(image, kernel_size, rep=3):
23+
"""
24+
Apply Gaussian blur to an RGB image.
25+
26+
Parameters:
27+
image (numpy.ndarray): The input RGB image.
28+
kernel_size (int): The size of the Gaussian kernel. If an even number is provided, it will be incremented to the next odd number.
29+
30+
Returns:
31+
numpy.ndarray: The blurred RGB image.
32+
"""
33+
if kernel_size % 2 == 0:
34+
kernel_size += 1 # Increment kernel size to make it odd if it's even
35+
36+
for _ in range(rep):
37+
image = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
38+
return image
39+
40+
2341
def X2conv(in_channels, out_channels, inner_channels=None):
2442
inner_channels = out_channels // 2 if inner_channels is None else inner_channels
2543
down_conv = nn.Sequential(

common/vision/lasr_vision_feature_extraction/src/lasr_vision_feature_extraction/image_with_masks_and_attributes.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,120 @@
33
CategoriesAndAttributes,
44
)
55

6+
reference_colours = {
7+
"blue_very_light": np.array([240, 248, 255]), # Alice blue
8+
"blue_light": np.array([173, 216, 230]), # Light blue
9+
"blue_sky": np.array([135, 206, 235]), # Sky blue
10+
"blue_powder": np.array([176, 224, 230]), # Powder blue
11+
"blue_celeste": np.array([178, 255, 255]), # Celeste, very pale blue shade
12+
"blue_periwinkle": np.array(
13+
[204, 204, 255]
14+
), # Periwinkle, a mix of light blue and lavender
15+
"blue_cadet": np.array([95, 158, 160]), # Cadet blue, a muted blue-green
16+
"blue": np.array([0, 0, 255]), # Standard blue
17+
"blue_royal": np.array([65, 105, 225]), # Royal blue
18+
"blue_deep": np.array([0, 0, 139]), # Deep blue
19+
"blue_dark": np.array([0, 0, 128]), # Dark blue
20+
# "blue_navy": np.array([0, 0, 80]), # Navy blue
21+
"yellow_very_light": np.array([255, 255, 204]), # Very light yellow
22+
"yellow_light": np.array([255, 255, 224]), # Light yellow
23+
"yellow": np.array([255, 255, 0]), # Standard yellow
24+
"yellow_gold": np.array([255, 215, 0]), # Gold yellow
25+
"yellow_dark": np.array([204, 204, 0]), # Dark yellow
26+
"yellow_mustard": np.array([255, 219, 88]), # Mustard yellow
27+
"red_very_light": np.array([255, 204, 204]), # Very light red
28+
"red_light": np.array([255, 102, 102]), # Light red
29+
"red": np.array([255, 0, 0]), # Standard red
30+
"red_dark": np.array([139, 0, 0]), # Dark red
31+
"red_maroon": np.array([128, 0, 0]), # Maroon
32+
"orange_very_light": np.array([255, 229, 180]), # Very light orange
33+
"orange_light": np.array([255, 179, 71]), # Light orange
34+
"orange": np.array([255, 165, 0]), # Standard orange
35+
"orange_dark": np.array([255, 140, 0]), # Dark orange
36+
"orange_burnt": np.array([204, 85, 0]), # Burnt orange
37+
"black": np.array([30, 30, 30]), # Black
38+
"white": np.array([255, 255, 255]), # White
39+
"gray": np.array([160, 160, 160]), # Gray
40+
}
41+
42+
colour_group_map = {
43+
"blue_very_light": "blue",
44+
"blue_light": "blue",
45+
"blue_sky": "blue",
46+
"blue_powder": "blue",
47+
"blue_celeste": "blue",
48+
"blue_periwinkle": "blue",
49+
"blue_cadet": "blue",
50+
"blue": "blue",
51+
"blue_royal": "blue",
52+
"blue_deep": "blue",
53+
"blue_dark": "blue",
54+
"yellow_very_light": "yellow",
55+
"yellow_light": "yellow",
56+
"yellow": "yellow",
57+
"yellow_gold": "yellow",
58+
"yellow_dark": "yellow",
59+
"yellow_mustard": "yellow",
60+
"red_very_light": "red",
61+
"red_light": "red",
62+
"red": "red",
63+
"red_dark": "red",
64+
"red_maroon": "red",
65+
"orange_very_light": "orange",
66+
"orange_light": "orange",
67+
"orange": "orange",
68+
"orange_dark": "orange",
69+
"orange_burnt": "orange",
70+
"black": "black",
71+
"white": "white",
72+
"gray": "gray",
73+
}
74+
75+
possible_colours = ["blue", "yellow", "red", "orange", "black", "white", "gray"]
76+
77+
78+
def estimate_colour(rgb_array):
79+
# Calculate distances to each reference colour
80+
# distances = {colour: cie76_distance(rgb_array, ref_rgb) for colour, ref_rgb in reference_colours.items()}
81+
distances = {
82+
colour: np.linalg.norm(rgb_array - ref_rgb)
83+
for colour, ref_rgb in reference_colours.items()
84+
}
85+
86+
# Find the colour with the smallest distance
87+
estimated_colour = min(distances, key=distances.get)
88+
89+
return estimated_colour
90+
91+
92+
def split_and_sample_colours(image, mask, square_size):
93+
height, width, _ = image.shape
94+
squares_colours = {}
95+
valid_squares = set()
96+
97+
square_index = 0
98+
99+
for y in range(0, height, square_size):
100+
for x in range(0, width, square_size):
101+
square = image[y : y + square_size, x : x + square_size]
102+
mask_square = mask[y : y + square_size, x : x + square_size]
103+
104+
# Calculate the average colour
105+
average_colour = square.mean(axis=(0, 1))
106+
107+
# Save the average colour in the dictionary
108+
squares_colours[square_index] = estimate_colour(average_colour)
109+
# squares_colours[square_index] = average_colour # estimate_colour(average_colour)
110+
111+
# Check the mask condition
112+
a = np.sum(mask_square)
113+
if np.sum(mask_square) > 0.5 * square_size * square_size:
114+
valid_squares.add(square_index)
115+
116+
square_index += 1
117+
118+
return squares_colours, valid_squares
119+
6120

7121
def _softmax(x: list[float]) -> list[float]:
8122
"""Compute softmax values for each set of scores in x without using NumPy."""
@@ -91,6 +205,8 @@ def from_parent_instance(
91205
)
92206

93207
def describe(self) -> dict:
208+
# Maybe not keep ‘sleeveless’ anymore but might do this in the actual environment.
209+
94210
result = {
95211
"top": self.attributes["top"] / 2,
96212
"outwear": self.attributes["outwear"] / 2,
@@ -133,4 +249,38 @@ def describe(self) -> dict:
133249
max_attribute = "sleeveless dress"
134250
result["max_dress"] = max_attribute
135251

252+
# QUICK FIX that won't break the code but not returning dress anymore.
253+
result["dress"] = 0.0
254+
255+
# ========= colour estimation below: =========
256+
# blurred_imagge kept here so that we can quickly make it work if we need.
257+
# blurred_image = gaussian_blur(self.image, kernel_size=13, rep=3)
258+
blurred_image = self.image
259+
for cloth in ["top", "outwear", "dress"]: # "down",
260+
mask = self.masks[cloth]
261+
squares_colours, valid_squares = split_and_sample_colours(
262+
blurred_image, mask, 20
263+
)
264+
_squares_colours = {}
265+
for k in squares_colours.keys():
266+
if k in valid_squares:
267+
_squares_colours[k] = squares_colours[k]
268+
squares_colours = {
269+
k: colour_group_map[colour] for k, colour in _squares_colours.items()
270+
}
271+
squares_colours_count = {}
272+
for k, colour in squares_colours.items():
273+
if colour not in squares_colours_count.keys():
274+
squares_colours_count[colour] = 1
275+
else:
276+
squares_colours_count[colour] += 1
277+
print(squares_colours_count)
278+
tag = cloth + " colour"
279+
result[tag] = {}
280+
for k in possible_colours:
281+
if k in squares_colours_count.keys():
282+
result[tag][k] = squares_colours_count[k] / len(squares_colours)
283+
else:
284+
result[tag][k] = 0.0
285+
136286
return result

0 commit comments

Comments
 (0)