Glowing led decks in sim.#45
Conversation
amacati
left a comment
There was a problem hiding this comment.
This seems to be a cool functionality, but there are several things that need to change.
For every new addition to the sim that is relatively confined, let's also try to add tests to make sure it works and doesn't break with future iterations.
For these kind of tests, please also check if it does not succeed for problematic inputs. See e.g. https://docs.pytest.org/en/7.1.x/how-to/assert.html#assertions-about-expected-exceptions
| raise ValueError(f"drone_ids must be in range [0, {sim.n_drones - 1}], got {drone_ids}") | ||
|
|
||
| if rgba is not None: | ||
| rgba = np.asarray(rgba, dtype=float) |
There was a problem hiding this comment.
The function signature already sais we require a numpy array. Or is the cast because we need dtype to be float64?
| try: | ||
| # this returns itself if rgba is already the right shape | ||
| rgba = np.broadcast_to(rgba, (len(drone_ids), 4)).copy() | ||
| except Exception: | ||
| raise ValueError(f"rgba must be shape (4,) or ({len(drone_ids)}, 4), got {rgba.shape}") |
There was a problem hiding this comment.
Why the try block? If this fails, just let it fail. Also, try to avoid bare Exceptions at all cost. They can be extremely confusing for users. Anything could go wrong, but they wouldn't see any of it because the bare except just swallows the error.
There was a problem hiding this comment.
Removed.
The np.broadcast_to() will report:
"ValueError: operands could not be broadcast together with remapped shapes [original->remapped]: (3,4) and requested shape (13,4)"
if shape is incorrect, which is also clear.
| rgba = np.broadcast_to(rgba, (len(drone_ids), 4)).copy() | ||
| except Exception: | ||
| raise ValueError(f"rgba must be shape (4,) or ({len(drone_ids)}, 4), got {rgba.shape}") | ||
| rgba = np.clip(rgba, 0.0, 1.0) |
There was a problem hiding this comment.
Do we need the clip? Can't mujoco deal with [2 0 0 0]?
There was a problem hiding this comment.
No, we don't. Removed.
There was a problem hiding this comment.
Btw, I found that mujoco can also handle emission<0, so I removed "emission = np.maximum(emission, 0.0)" as well.
| rgba = np.clip(rgba, 0.0, 1.0) | ||
|
|
||
| if emission is not None: | ||
| emission = np.asarray(emission, dtype=float) |
There was a problem hiding this comment.
Same thing. Do we need the cast?
|
|
||
| if emission is not None: | ||
| emission = np.asarray(emission, dtype=float) | ||
| try: |
There was a problem hiding this comment.
Just fail fast, it's okay to error in the function.
|
|
||
| mj_model = sim.mj_model | ||
|
|
||
| for i, id in enumerate(drone_ids): |
There was a problem hiding this comment.
Do not use id, it's a built-in function that you are shadowing (https://docs.python.org/3/library/functions.html#id).
There was a problem hiding this comment.
Changed to drone_id
| if rgba is not None: | ||
| mj_model.mat_rgba[mat_id, :] = rgba[i] | ||
|
|
||
| if emission is not None: | ||
| mj_model.mat_emission[mat_id] = emission[i] |
There was a problem hiding this comment.
Can we broadcast this? I.e. create an index of the drones and then set mj_model.mat_rgba[mat_id, drone_ids] = rgba?
There was a problem hiding this comment.
Yes, we can.
However, we still need to loop over the drone_ids to find the corresponding mat_id.
I changed it to mj_model.mat_rgba[mat_ids, :] = rgba
| if emission is not None: | ||
| mj_model.mat_emission[mat_id] = emission[i] | ||
|
|
||
|
|
There was a problem hiding this comment.
Please add tests for this function! Even if we are not rendering in the tests, we should at least try to see if this errors as expected and succeeds for the correct input.
There was a problem hiding this comment.
I added 2 small tests under test_sim.py
One mainly to make sure all the drone models have "led_top" and "led_bot" in xml file.
One to test the error output of the function.
| n_worlds, n_drones = 1, 25 | ||
| sim = Sim( | ||
| n_worlds=n_worlds, | ||
| n_drones=n_drones, | ||
| drone_model="cf21B_500", | ||
| control=Control.state, | ||
| physics=Physics.so_rpy, | ||
| device="cpu", | ||
| ) |
There was a problem hiding this comment.
The basic scripts made these kwargs explicit to show users the options we provide with the sim. In specialized examples such as this one, we can skip this.
| n_worlds, n_drones = 1, 25 | |
| sim = Sim( | |
| n_worlds=n_worlds, | |
| n_drones=n_drones, | |
| drone_model="cf21B_500", | |
| control=Control.state, | |
| physics=Physics.so_rpy, | |
| device="cpu", | |
| ) | |
| sim = Sim(n_drones=25, drone_model="cf21B_500") |
| rgba: NDArray | None = None, | ||
| emission: NDArray | None = None, | ||
| ): | ||
| """Change the material of all drones matching the mask. |
There was a problem hiding this comment.
Not a mask, but the drone_ids, right?
|
|
||
| def main(): | ||
| """Spawn 25 drones in one world and activate led decks.""" | ||
| sim = Sim(n_drones=25, drone_model="cf21B_500", control=Control.state) |
There was a problem hiding this comment.
We can use the new ones, they look great. When I commented on removing the defaults I was referring to repeating stuff that is already set by default. I.e. n_worlds is 1 by default, so there is no need to repeat it.
There was a problem hiding this comment.
I understood. I was trying to align with other examples.
I reverted this in the latest commit.
Use new drones in example.
|
Thanks a lot for your reviews! I learnt a lot from that. |
amacati
left a comment
There was a problem hiding this comment.
Suggested some very minor changes, otherwise this looks good.
| raise ValueError(f"drone_ids must be in range [0, {sim.n_drones - 1}], got {drone_ids}") | ||
|
|
||
| if rgba is not None: | ||
| # this returns itself if rgba is already the right shape |
There was a problem hiding this comment.
| # this returns itself if rgba is already the right shape |
| mat_ids.append(mat_id) | ||
|
|
||
| if rgba is not None: | ||
| mj_model.mat_rgba[mat_ids, :] = rgba |
There was a problem hiding this comment.
Is [mat_ids, :] necessary?
| mj_model.mat_rgba[mat_ids, :] = rgba | |
| mj_model.mat_rgba[mat_ids] = rgba |
| sim, mat_name="bad_mat", drone_ids=drone_ids, rgba=rgba, emission=emission | ||
| ) | ||
|
|
||
| with pytest.raises(ValueError, match=r"drone_ids must be 1D array"): |
There was a problem hiding this comment.
I don't think you need the r-string. But please check.
| with pytest.raises(ValueError, match=r"drone_ids must be 1D array"): | |
| with pytest.raises(ValueError, match="drone_ids must be 1D array"): |
| emission=emission, | ||
| ) | ||
|
|
||
| with pytest.raises(ValueError, match=r"drone_ids must be in range \[0, 1\]"): |
There was a problem hiding this comment.
Same here, but again, please check.
| with pytest.raises(ValueError, match=r"drone_ids must be in range \[0, 1\]"): | |
| with pytest.raises(ValueError, match="drone_ids must be in range [0, 1]"): |
There was a problem hiding this comment.
I looked up, so "r" s needed when "[]" appears in a regular expression.
I'll leave it for the second check.
Yes, that was what I had in mind. Thanks for adding those. |
|
I applied the minor suggestions. Sorry for the delay. |
p.s. User can dim the scene by specifying their own scene.xml when creating the Sim.