Repository: allenai/ai2thor (language: python)

Issue:
Simulator freeze when 'ChangeResolution' called after initializing with renderObjectImage=True
Minimum to reproduce in ipython:

`In [1]: import ai2thor`
`In [2]: ai2thor.__version__`
`Out[2]: '2.4.10'`
`In [3]: from ai2thor.controller import Controller`
`In [4]: c = Controller(renderObjectImage=True)`
`Initialize return: {'cameraNearPlane': 0.1, 'cameraFarPlane': 20.0}`
`In [5]: c.step(action="ChangeResolution", x=500, y=500)`

The final call ([5]) never returns.


Discussion / hints:
Hey, I was able to reproduce this error. The quick fix is to use `height` and `width` parameters upon initialization, as in:
```python
from ai2thor.controller import Controller
c = Controller(renderObjectImage=True, width=500, height=500)
```
Thanks! That is a much cleaner solution than my workaround (to initialize without `renderObjectImage`, then change the resolution, then re-initialize via an `Initialize` step with `renderObjectImage`).

Fix patch:
diff --git a/unity/Assets/Scripts/AgentManager.cs b/unity/Assets/Scripts/AgentManager.cs
index 04c5be662b..ab9f58bcd9 100644
--- a/unity/Assets/Scripts/AgentManager.cs
+++ b/unity/Assets/Scripts/AgentManager.cs
@@ -50,7 +50,7 @@ private enum serverTypes { WSGI, FIFO};
     private bool fastActionEmit = true;
 
     // it is public to be accessible from the debug input field.
-    public HashSet<string> agentManagerActions = new HashSet<string>{"Reset", "Initialize", "AddThirdPartyCamera", "UpdateThirdPartyCamera"};
+    public HashSet<string> agentManagerActions = new HashSet<string>{"Reset", "Initialize", "AddThirdPartyCamera", "UpdateThirdPartyCamera", "ChangeResolution"};
 
     public const float DEFAULT_FOV = 90;
     public const float MAX_FOV = 180;
@@ -766,6 +766,42 @@ private void addImage(List<KeyValuePair<string, byte[]>> payload, BaseFPSAgentCo
             payload.Add(new KeyValuePair<string, byte[]>("image", captureScreen()));
 		}
 	}
+	
+	private void resetImageSynthesis(Camera camera)
+	{
+		ImageSynthesis imageSynthesis = camera.gameObject.GetComponentInChildren<ImageSynthesis>();
+		if (imageSynthesis != null && imageSynthesis.enabled)
+		{
+			imageSynthesis.OnCameraChange();
+			imageSynthesis.OnSceneChange();
+		}
+	}
+
+	private void resetAllImageSynthesis()
+	{
+		foreach (var agent in this.agents)
+		{
+			resetImageSynthesis(agent.m_Camera);
+		}
+
+		foreach (var camera in this.thirdPartyCameras)
+		{
+			resetImageSynthesis(camera);
+		}
+	}
+	
+	public IEnumerator WaitOnResolutionChange(int width, int height) {
+		while (Screen.width != width || Screen.height != height) {
+			yield return null;
+		}
+		this.resetAllImageSynthesis();
+		this.primaryAgent.actionFinished(true);
+	}
+
+	public void ChangeResolution(int x, int y) {
+		Screen.SetResolution(width:x, height:y, false);
+		StartCoroutine(WaitOnResolutionChange(width:x, height:y));
+	}
 
 	private void addObjectImage(List<KeyValuePair<string, byte[]>> payload, BaseFPSAgentController agent, ref MetadataWrapper metadata) {
 		if (this.renderInstanceSegmentation) {
@@ -777,9 +813,9 @@ private void addObjectImage(List<KeyValuePair<string, byte[]>> payload, BaseFPSA
 
 			Color[] id_image = agent.imageSynthesis.tex.GetPixels();
 			Dictionary<Color, int[]> colorBounds = new Dictionary<Color, int[]> ();
-			for (int yy = 0; yy < tex.height; yy++) {
+			for (int yy = 0; yy < UnityEngine.Screen.height; yy++) {
 				for (int xx = 0; xx < tex.width; xx++) {
-					Color colorOn = id_image [yy * tex.width + xx];
+					Color colorOn = id_image [yy * UnityEngine.Screen.width + xx];
 					if (!colorBounds.ContainsKey (colorOn)) {
 						colorBounds [colorOn] = new int[]{xx, yy, xx, yy};
 					} else {
diff --git a/unity/Assets/Scripts/ImageSynthesis/ImageSynthesis.cs b/unity/Assets/Scripts/ImageSynthesis/ImageSynthesis.cs
index 1f0f4c6fe4..ec113a5cb3 100644
--- a/unity/Assets/Scripts/ImageSynthesis/ImageSynthesis.cs
+++ b/unity/Assets/Scripts/ImageSynthesis/ImageSynthesis.cs
@@ -197,6 +197,11 @@ enum ReplacelementModes {
 	//do, so maybe it's fine for now I dunno
 	public void OnCameraChange()
 	{
+		if (tex != null)
+		{
+			Destroy(tex);
+			tex = null;
+		}
 		var mainCamera = GetComponent<Camera>();
 		mainCamera.depth = 9999; // This ensures the main camera is rendered on screen
 
diff --git a/unity/Assets/Scripts/PhysicsRemoteFPSAgentController.cs b/unity/Assets/Scripts/PhysicsRemoteFPSAgentController.cs
index 0c7262ab01..ad775289f4 100644
--- a/unity/Assets/Scripts/PhysicsRemoteFPSAgentController.cs
+++ b/unity/Assets/Scripts/PhysicsRemoteFPSAgentController.cs
@@ -6322,19 +6322,6 @@ public void ChangeFOV(ServerAction action)
 
         }
 
-        public IEnumerator WaitOnResolutionChange(int width, int height) {
-            while (Screen.width != width || Screen.height != height) {
-                yield return null;
-            }
-            actionFinished(true);
-        }
-
-        public void ChangeResolution(ServerAction action) {
-            int height = Convert.ToInt32(action.y);
-            int width = Convert.ToInt32(action.x);
-            Screen.SetResolution(width, height, false);
-            StartCoroutine(WaitOnResolutionChange(width, height));
-        }
 
         public void ChangeQuality(ServerAction action) {
             string[] names = QualitySettings.names;


Test patch:
diff --git a/ai2thor/tests/test_unity.py b/ai2thor/tests/test_unity.py
index 31cf7635e6..caab961d66 100644
--- a/ai2thor/tests/test_unity.py
+++ b/ai2thor/tests/test_unity.py
@@ -166,6 +166,7 @@ def test_third_party_camera_with_image_synthesis(fifo_controller):
         renderDepthImage=True,
         renderSemanticSegmentation=True,
     )
+
     event = fifo_controller.step(
         dict(
             action="AddThirdPartyCamera",
@@ -1017,6 +1018,36 @@ def test_get_reachable_positions(controller):
         pass
 
 
+#  Test for Issue: 477
+def test_change_resolution_image_synthesis(fifo_controller):
+    fifo_controller.reset(
+        TEST_SCENE,
+        width=300,
+        height=300,
+        renderInstanceSegmentation=True,
+        renderDepthImage=True,
+        renderSemanticSegmentation=True,
+    )
+    fifo_controller.step("RotateRight")
+    first_event = fifo_controller.last_event
+    first_depth_frame = fifo_controller.last_event.depth_frame
+    first_instance_frame = fifo_controller.last_event.instance_segmentation_frame
+    first_sem_frame = fifo_controller.last_event.semantic_segmentation_frame
+    event = fifo_controller.step(action="ChangeResolution", x=500, y=500)
+    assert event.depth_frame.shape == (500, 500)
+    assert event.instance_segmentation_frame.shape == (500, 500, 3)
+    assert event.semantic_segmentation_frame.shape == (500, 500, 3)
+    event = fifo_controller.step(action="ChangeResolution", x=300, y=300)
+    assert event.depth_frame.shape == (300, 300)
+    assert event.instance_segmentation_frame.shape == (300, 300, 3)
+    assert event.semantic_segmentation_frame.shape == (300, 300, 3)
+    assert np.allclose(event.depth_frame, first_depth_frame, atol=0.001)
+    assert np.array_equal(event.instance_segmentation_frame, first_instance_frame)
+    assert np.array_equal(event.semantic_segmentation_frame, first_sem_frame)
+    assert first_event.color_to_object_id == event.color_to_object_id
+    assert first_event.object_id_to_color == event.object_id_to_color
+
+
 @pytest.mark.parametrize("controller", fifo_wsgi)
 def test_change_resolution(controller):
     event = controller.step(dict(action="Pass"), raise_for_failure=True)