We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Training crashes if all cameras are deleted while in "Render View"
Steps to replicate:
1. Start train and open viewer
2. Add camera to scene
3. Switch to "RENDER VIEW"
4. Delete camera

Resulting error:
```
  File "/projects/pyrad/scripts/train.py", line 234, in main
    launch(
  File "/projects/pyrad/scripts/train.py", line 173, in launch
    main_func(local_rank=0, world_size=world_size, config=config)
  File "/projects/pyrad/scripts/train.py", line 88, in train_loop
    trainer.train()
  File "/projects/pyrad/nerfstudio/engine/trainer.py", line 166, in train
    self._update_viewer_state(step)
  File "/projects/pyrad/nerfstudio/utils/decorators.py", line 58, in wrapper
    ret = func(self, *args, **kwargs)
  File "/projects/pyrad/nerfstudio/engine/trainer.py", line 229, in _update_viewer_state
    self.viewer_state.update_scene(self, step, self.pipeline.model, num_rays_per_batch)
  File "/projects/pyrad/nerfstudio/utils/decorators.py", line 82, in wrapper
    ret = func(*args, **kwargs)
  File "/projects/pyrad/nerfstudio/viewer/server/viewer_utils.py", line 398, in update_scene
    self._render_image_in_viewer(camera_object, graph, is_training)
  File "/projects/pyrad/nerfstudio/utils/decorators.py", line 82, in wrapper
    ret = func(*args, **kwargs)
  File "/projects/pyrad/nerfstudio/utils/profiler.py", line 43, in wrapper
    ret = func(*args, **kwargs)
  File "/projects/pyrad/nerfstudio/viewer/server/viewer_utils.py", line 745, in _render_image_in_viewer
    intrinsics_matrix, camera_to_world_h = get_intrinsics_matrix_and_camera_to_world_h(
  File "/projects/pyrad/nerfstudio/viewer/server/utils.py", line 77, in get_intrinsics_matrix_and_camera_to_world_h
    focal_length = three_js_perspective_camera_focal_length(fov, image_height)
  File "/projects/pyrad/nerfstudio/viewer/server/utils.py", line 58, in three_js_perspective_camera_focal_length
    focal_length = pp_h / np.tan(fov * (np.pi / 180.0) / 2.0)
TypeError: unsupported operand type(s) for *: 'NoneType' and 'float'
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nerfstudio/viewer/server/utils.py`
Content:
```
1 # Copyright 2022 The Nerfstudio Team. All rights reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """Generic utility functions
16 """
17 
18 import sys
19 from typing import Any, Callable, Dict, List, Optional, Tuple
20 
21 import numpy as np
22 import torch
23 import zmq
24 from aiortc import RTCPeerConnection
25 from aiortc.rtcrtpsender import RTCRtpSender
26 
27 
28 def get_chunks(
29     lst: List[float], num_chunks: Optional[int] = None, size_of_chunk: Optional[int] = None
30 ) -> List[List[float]]:
31     """Returns list of n elements, constaining a sublist.
32 
33     Args:
34         lst: List to be chunked up
35         num_chunks: number of chunks to split list into
36         size_of_chunk: size of each chunk
37     """
38     if num_chunks:
39         assert not size_of_chunk
40         size = len(lst) // num_chunks
41     if size_of_chunk:
42         assert not num_chunks
43         size = size_of_chunk
44     chunks = []
45     for i in range(0, len(lst), size):
46         chunks.append(lst[i : i + size])
47     return chunks
48 
49 
50 def three_js_perspective_camera_focal_length(fov: float, image_height: int):
51     """Returns the focal length of a three.js perspective camera.
52 
53     Args:
54         fov: the field of view of the camera in degrees.
55         image_height: the height of the image in pixels.
56     """
57     pp_h = image_height / 2.0
58     focal_length = pp_h / np.tan(fov * (np.pi / 180.0) / 2.0)
59     return focal_length
60 
61 
62 def get_intrinsics_matrix_and_camera_to_world_h(
63     camera_object: Dict[str, Any], image_height: int
64 ) -> Tuple[torch.Tensor, torch.Tensor]:
65     """Returns the camera intrinsics matrix and the camera to world homogeneous matrix.
66 
67     Args:
68         camera_object: a Camera object.
69         image_size: the size of the image (height, width)
70     """
71     # intrinsics
72     fov = camera_object["fov"]
73     aspect = camera_object["aspect"]
74     image_width = aspect * image_height
75     pp_w = image_width / 2.0
76     pp_h = image_height / 2.0
77     focal_length = three_js_perspective_camera_focal_length(fov, image_height)
78     intrinsics_matrix = torch.tensor([[focal_length, 0, pp_w], [0, focal_length, pp_h], [0, 0, 1]]).float()
79 
80     # extrinsics
81     camera_to_world_h = torch.tensor(get_chunks(camera_object["matrix"], size_of_chunk=4)).T.float()
82     camera_to_world_h = torch.stack(
83         [
84             camera_to_world_h[0, :],
85             camera_to_world_h[2, :],
86             camera_to_world_h[1, :],
87             camera_to_world_h[3, :],
88         ],
89         dim=0,
90     )
91 
92     return intrinsics_matrix, camera_to_world_h
93 
94 
95 def find_available_port(func: Callable, default_port: int, max_attempts: int = 1000, **kwargs) -> None:
96     """Finds and attempts to connect to a port
97 
98     Args:
99         func: function used on connecting to port
100         default_port: the default port
101         max_attempts: max number of attempts to try connection. Defaults to MAX_ATTEMPTS.
102     """
103     for i in range(max_attempts):
104         port = default_port + i
105         try:
106             return func(port, **kwargs), port
107         except (OSError, zmq.error.ZMQError):
108             print(f"Port: {port:d} in use, trying another...", file=sys.stderr)
109         except Exception as e:
110             print(type(e))
111             raise
112     raise (
113         Exception(f"Could not find an available port in the range: [{default_port:d}, {max_attempts + default_port:d})")
114     )
115 
116 
117 def force_codec(pc: RTCPeerConnection, sender: RTCRtpSender, forced_codec: str) -> None:
118     """Sets the codec preferences on a connection between sender and reciever
119 
120     Args:
121         pc: peer connection point
122         sender: sender that will send to connection point
123         forced_codec: codec to set
124     """
125     kind = forced_codec.split("/")[0]
126     codecs = RTCRtpSender.getCapabilities(kind).codecs
127     transceiver = next(t for t in pc.getTransceivers() if t.sender == sender)
128     transceiver.setCodecPreferences([codec for codec in codecs if codec.mimeType == forced_codec])
129
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/nerfstudio/viewer/server/utils.py b/nerfstudio/viewer/server/utils.py
--- a/nerfstudio/viewer/server/utils.py
+++ b/nerfstudio/viewer/server/utils.py
@@ -54,6 +54,9 @@
         fov: the field of view of the camera in degrees.
         image_height: the height of the image in pixels.
     """
+    if fov is None:
+        print("Warning: fov is None, using default value")
+        return 50
     pp_h = image_height / 2.0
     focal_length = pp_h / np.tan(fov * (np.pi / 180.0) / 2.0)
     return focal_length

{"golden_diff": "diff --git a/nerfstudio/viewer/server/utils.py b/nerfstudio/viewer/server/utils.py\n--- a/nerfstudio/viewer/server/utils.py\n+++ b/nerfstudio/viewer/server/utils.py\n@@ -54,6 +54,9 @@\n         fov: the field of view of the camera in degrees.\n         image_height: the height of the image in pixels.\n     \"\"\"\n+    if fov is None:\n+        print(\"Warning: fov is None, using default value\")\n+        return 50\n     pp_h = image_height / 2.0\n     focal_length = pp_h / np.tan(fov * (np.pi / 180.0) / 2.0)\n     return focal_length\n", "issue": "Training crashes if all cameras are deleted while in \"Render View\"\nSteps to replicate:\r\n1. Start train and open viewer\r\n2. Add camera to scene\r\n3. Switch to \"RENDER VIEW\"\r\n4. Delete camera\r\n\r\nResulting error:\r\n```\r\n  File \"/projects/pyrad/scripts/train.py\", line 234, in main\r\n    launch(\r\n  File \"/projects/pyrad/scripts/train.py\", line 173, in launch\r\n    main_func(local_rank=0, world_size=world_size, config=config)\r\n  File \"/projects/pyrad/scripts/train.py\", line 88, in train_loop\r\n    trainer.train()\r\n  File \"/projects/pyrad/nerfstudio/engine/trainer.py\", line 166, in train\r\n    self._update_viewer_state(step)\r\n  File \"/projects/pyrad/nerfstudio/utils/decorators.py\", line 58, in wrapper\r\n    ret = func(self, *args, **kwargs)\r\n  File \"/projects/pyrad/nerfstudio/engine/trainer.py\", line 229, in _update_viewer_state\r\n    self.viewer_state.update_scene(self, step, self.pipeline.model, num_rays_per_batch)\r\n  File \"/projects/pyrad/nerfstudio/utils/decorators.py\", line 82, in wrapper\r\n    ret = func(*args, **kwargs)\r\n  File \"/projects/pyrad/nerfstudio/viewer/server/viewer_utils.py\", line 398, in update_scene\r\n    self._render_image_in_viewer(camera_object, graph, is_training)\r\n  File \"/projects/pyrad/nerfstudio/utils/decorators.py\", line 82, in wrapper\r\n    ret = func(*args, **kwargs)\r\n  File \"/projects/pyrad/nerfstudio/utils/profiler.py\", line 43, in wrapper\r\n    ret = func(*args, **kwargs)\r\n  File \"/projects/pyrad/nerfstudio/viewer/server/viewer_utils.py\", line 745, in _render_image_in_viewer\r\n    intrinsics_matrix, camera_to_world_h = get_intrinsics_matrix_and_camera_to_world_h(\r\n  File \"/projects/pyrad/nerfstudio/viewer/server/utils.py\", line 77, in get_intrinsics_matrix_and_camera_to_world_h\r\n    focal_length = three_js_perspective_camera_focal_length(fov, image_height)\r\n  File \"/projects/pyrad/nerfstudio/viewer/server/utils.py\", line 58, in three_js_perspective_camera_focal_length\r\n    focal_length = pp_h / np.tan(fov * (np.pi / 180.0) / 2.0)\r\nTypeError: unsupported operand type(s) for *: 'NoneType' and 'float'\r\n```\n", "before_files": [{"content": "# Copyright 2022 The Nerfstudio Team. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Generic utility functions\n\"\"\"\n\nimport sys\nfrom typing import Any, Callable, Dict, List, Optional, Tuple\n\nimport numpy as np\nimport torch\nimport zmq\nfrom aiortc import RTCPeerConnection\nfrom aiortc.rtcrtpsender import RTCRtpSender\n\n\ndef get_chunks(\n    lst: List[float], num_chunks: Optional[int] = None, size_of_chunk: Optional[int] = None\n) -> List[List[float]]:\n    \"\"\"Returns list of n elements, constaining a sublist.\n\n    Args:\n        lst: List to be chunked up\n        num_chunks: number of chunks to split list into\n        size_of_chunk: size of each chunk\n    \"\"\"\n    if num_chunks:\n        assert not size_of_chunk\n        size = len(lst) // num_chunks\n    if size_of_chunk:\n        assert not num_chunks\n        size = size_of_chunk\n    chunks = []\n    for i in range(0, len(lst), size):\n        chunks.append(lst[i : i + size])\n    return chunks\n\n\ndef three_js_perspective_camera_focal_length(fov: float, image_height: int):\n    \"\"\"Returns the focal length of a three.js perspective camera.\n\n    Args:\n        fov: the field of view of the camera in degrees.\n        image_height: the height of the image in pixels.\n    \"\"\"\n    pp_h = image_height / 2.0\n    focal_length = pp_h / np.tan(fov * (np.pi / 180.0) / 2.0)\n    return focal_length\n\n\ndef get_intrinsics_matrix_and_camera_to_world_h(\n    camera_object: Dict[str, Any], image_height: int\n) -> Tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Returns the camera intrinsics matrix and the camera to world homogeneous matrix.\n\n    Args:\n        camera_object: a Camera object.\n        image_size: the size of the image (height, width)\n    \"\"\"\n    # intrinsics\n    fov = camera_object[\"fov\"]\n    aspect = camera_object[\"aspect\"]\n    image_width = aspect * image_height\n    pp_w = image_width / 2.0\n    pp_h = image_height / 2.0\n    focal_length = three_js_perspective_camera_focal_length(fov, image_height)\n    intrinsics_matrix = torch.tensor([[focal_length, 0, pp_w], [0, focal_length, pp_h], [0, 0, 1]]).float()\n\n    # extrinsics\n    camera_to_world_h = torch.tensor(get_chunks(camera_object[\"matrix\"], size_of_chunk=4)).T.float()\n    camera_to_world_h = torch.stack(\n        [\n            camera_to_world_h[0, :],\n            camera_to_world_h[2, :],\n            camera_to_world_h[1, :],\n            camera_to_world_h[3, :],\n        ],\n        dim=0,\n    )\n\n    return intrinsics_matrix, camera_to_world_h\n\n\ndef find_available_port(func: Callable, default_port: int, max_attempts: int = 1000, **kwargs) -> None:\n    \"\"\"Finds and attempts to connect to a port\n\n    Args:\n        func: function used on connecting to port\n        default_port: the default port\n        max_attempts: max number of attempts to try connection. Defaults to MAX_ATTEMPTS.\n    \"\"\"\n    for i in range(max_attempts):\n        port = default_port + i\n        try:\n            return func(port, **kwargs), port\n        except (OSError, zmq.error.ZMQError):\n            print(f\"Port: {port:d} in use, trying another...\", file=sys.stderr)\n        except Exception as e:\n            print(type(e))\n            raise\n    raise (\n        Exception(f\"Could not find an available port in the range: [{default_port:d}, {max_attempts + default_port:d})\")\n    )\n\n\ndef force_codec(pc: RTCPeerConnection, sender: RTCRtpSender, forced_codec: str) -> None:\n    \"\"\"Sets the codec preferences on a connection between sender and reciever\n\n    Args:\n        pc: peer connection point\n        sender: sender that will send to connection point\n        forced_codec: codec to set\n    \"\"\"\n    kind = forced_codec.split(\"/\")[0]\n    codecs = RTCRtpSender.getCapabilities(kind).codecs\n    transceiver = next(t for t in pc.getTransceivers() if t.sender == sender)\n    transceiver.setCodecPreferences([codec for codec in codecs if codec.mimeType == forced_codec])\n", "path": "nerfstudio/viewer/server/utils.py"}], "after_files": [{"content": "# Copyright 2022 The Nerfstudio Team. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Generic utility functions\n\"\"\"\n\nimport sys\nfrom typing import Any, Callable, Dict, List, Optional, Tuple\n\nimport numpy as np\nimport torch\nimport zmq\nfrom aiortc import RTCPeerConnection\nfrom aiortc.rtcrtpsender import RTCRtpSender\n\n\ndef get_chunks(\n    lst: List[float], num_chunks: Optional[int] = None, size_of_chunk: Optional[int] = None\n) -> List[List[float]]:\n    \"\"\"Returns list of n elements, constaining a sublist.\n\n    Args:\n        lst: List to be chunked up\n        num_chunks: number of chunks to split list into\n        size_of_chunk: size of each chunk\n    \"\"\"\n    if num_chunks:\n        assert not size_of_chunk\n        size = len(lst) // num_chunks\n    if size_of_chunk:\n        assert not num_chunks\n        size = size_of_chunk\n    chunks = []\n    for i in range(0, len(lst), size):\n        chunks.append(lst[i : i + size])\n    return chunks\n\n\ndef three_js_perspective_camera_focal_length(fov: float, image_height: int):\n    \"\"\"Returns the focal length of a three.js perspective camera.\n\n    Args:\n        fov: the field of view of the camera in degrees.\n        image_height: the height of the image in pixels.\n    \"\"\"\n    if fov is None:\n        print(\"Warning: fov is None, using default value\")\n        return 50\n    pp_h = image_height / 2.0\n    focal_length = pp_h / np.tan(fov * (np.pi / 180.0) / 2.0)\n    return focal_length\n\n\ndef get_intrinsics_matrix_and_camera_to_world_h(\n    camera_object: Dict[str, Any], image_height: int\n) -> Tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Returns the camera intrinsics matrix and the camera to world homogeneous matrix.\n\n    Args:\n        camera_object: a Camera object.\n        image_size: the size of the image (height, width)\n    \"\"\"\n    # intrinsics\n    fov = camera_object[\"fov\"]\n    aspect = camera_object[\"aspect\"]\n    image_width = aspect * image_height\n    pp_w = image_width / 2.0\n    pp_h = image_height / 2.0\n    focal_length = three_js_perspective_camera_focal_length(fov, image_height)\n    intrinsics_matrix = torch.tensor([[focal_length, 0, pp_w], [0, focal_length, pp_h], [0, 0, 1]]).float()\n\n    # extrinsics\n    camera_to_world_h = torch.tensor(get_chunks(camera_object[\"matrix\"], size_of_chunk=4)).T.float()\n    camera_to_world_h = torch.stack(\n        [\n            camera_to_world_h[0, :],\n            camera_to_world_h[2, :],\n            camera_to_world_h[1, :],\n            camera_to_world_h[3, :],\n        ],\n        dim=0,\n    )\n\n    return intrinsics_matrix, camera_to_world_h\n\n\ndef find_available_port(func: Callable, default_port: int, max_attempts: int = 1000, **kwargs) -> None:\n    \"\"\"Finds and attempts to connect to a port\n\n    Args:\n        func: function used on connecting to port\n        default_port: the default port\n        max_attempts: max number of attempts to try connection. Defaults to MAX_ATTEMPTS.\n    \"\"\"\n    for i in range(max_attempts):\n        port = default_port + i\n        try:\n            return func(port, **kwargs), port\n        except (OSError, zmq.error.ZMQError):\n            print(f\"Port: {port:d} in use, trying another...\", file=sys.stderr)\n        except Exception as e:\n            print(type(e))\n            raise\n    raise (\n        Exception(f\"Could not find an available port in the range: [{default_port:d}, {max_attempts + default_port:d})\")\n    )\n\n\ndef force_codec(pc: RTCPeerConnection, sender: RTCRtpSender, forced_codec: str) -> None:\n    \"\"\"Sets the codec preferences on a connection between sender and reciever\n\n    Args:\n        pc: peer connection point\n        sender: sender that will send to connection point\n        forced_codec: codec to set\n    \"\"\"\n    kind = forced_codec.split(\"/\")[0]\n    codecs = RTCRtpSender.getCapabilities(kind).codecs\n    transceiver = next(t for t in pc.getTransceivers() if t.sender == sender)\n    transceiver.setCodecPreferences([codec for codec in codecs if codec.mimeType == forced_codec])\n", "path": "nerfstudio/viewer/server/utils.py"}]}