Repository: voxel51/fiftyone

Issue:
[BUG] Can't visualize images on D:\\ drive in Windows
### Instructions
Not getting image visualization on Windows localhost.

### System information

-   **OS Platform and Distribution**: Windows 10
-   **FiftyOne installed from (pip or source)**: pip3 install fiftyone
-   **FiftyOne version (run `fiftyone --version`)**: Default
-   **Python version**: python 3.9

### Commands to reproduce
I tried to create a session after adding dataset
```
dataset = fo.Dataset.from_images_dir("D:\\Datacluster_algo\\Duplicate_Similarity_image_dataset\\5PK3KvYIeQgVza6P1hkL", recursive=True, name="DC-images")
print(dataset) # this works fine as I can see the same number of images loaded with correct paths
session = fo.launch_app(dataset)
```

### Describe the problem

On http://localhost:5151/ I do not get thumbnails of the images not even on double-clicking it.

![Capture](https://user-images.githubusercontent.com/5621042/106779185-c145a480-666c-11eb-8340-f8922bd3e409.PNG)

### What areas of FiftyOne does this bug affect?

-   [ ] `App`: FiftyOne application issue
-   [ ] `Core`: Core `fiftyone` Python library issue
-   [x] `Server`: Fiftyone server issue

### Willingness to contribute

The FiftyOne Community encourages bug fix contributions. Would you or another
member of your organization be willing to contribute a fix for this bug to the
FiftyOne codebase?

-   [ ] Yes. I can contribute a fix for this bug independently.
-   [ ] Yes. I would be willing to contribute a fix for this bug with guidance
        from the FiftyOne community.
-   [x] No. I cannot contribute a bug fix at this time.



Discussion:
![Capture](https://user-images.githubusercontent.com/5621042/106780912-7a58ae80-666e-11eb-8ff9-c5dc365f4054.PNG)

I can see that the **filepath is not correct** on Windows. It should have started with ```D"\\Datacluster....```   

However, on the command line it seems correct:  
```
>>> dataset.first()
<Sample: {
    'id': '601ace484a853f57ac54262c',
    'media_type': 'image',
    'filepath': 'D:\\Datacluster_algo\\Duplicate_Similarity_image_dataset\\5PK3KvYIeQgVza6P1hkL\\1589964287376.jpg',
    'tags': BaseList([]),
    'metadata': None,
```  

I hope this might give a clue to find the bug.
Hi @sourabhyadav. Apologies for the trouble. For now, visualization should work if the images are on your `C:` drive.

I'll have to write a bug fix that supports any drive on Windows. I'll respond here when a fix is out. Monday or Tuesday of next week at latest. Thanks for making the issue!

Resolving patch:
diff --git a/fiftyone/server/main.py b/fiftyone/server/main.py
--- a/fiftyone/server/main.py
+++ b/fiftyone/server/main.py
@@ -12,6 +12,7 @@
 import os
 import posixpath
 import traceback
+import urllib
 
 from bson import ObjectId
 import tornado.escape
@@ -19,6 +20,7 @@
 import tornado.iostream
 import tornado.options
 import tornado.web
+from tornado.web import HTTPError
 import tornado.websocket
 
 import eta.core.labels as etal
@@ -556,12 +558,6 @@ async def on_page(cls, self, page, page_length=20):
             r["height"] = h
             # default to image
 
-        for r in results:
-            s = r["sample"]
-            s["filepath"] = (
-                s["filepath"].replace(os.sep, posixpath.sep).split(":")[-1]
-            )
-
         message = {
             "type": "page",
             "page": page,
@@ -1200,11 +1196,31 @@ def set_headers(self):
         self.set_header("x-colab-notebook-cache-control", "no-cache")
 
 
+class MediaHandler(FileHandler):
+    @classmethod
+    def get_absolute_path(cls, root, path):
+        return path
+
+    def validate_absolute_path(self, root, absolute_path):
+        if os.path.isdir(absolute_path) and self.default_filename is not None:
+            if not self.request.path.endswith("/"):
+                self.redirect(self.request.path + "/", permanent=True)
+                return None
+
+            absolute_path = os.path.join(absolute_path, self.default_filename)
+        if not os.path.exists(absolute_path):
+            raise HTTPError(404)
+
+        if not os.path.isfile(absolute_path):
+            raise HTTPError(403, "%s is not a file", self.path)
+
+        return absolute_path
+
+
 class Application(tornado.web.Application):
     """FiftyOne Tornado Application"""
 
     def __init__(self, **settings):
-        static_path = "C:/" if os.name == "nt" else "/"
         server_path = os.path.dirname(os.path.abspath(__file__))
         rel_web_path = "static"
         web_path = os.path.join(server_path, rel_web_path)
@@ -1212,7 +1228,7 @@ def __init__(self, **settings):
             (r"/fiftyone", FiftyOneHandler),
             (r"/polling", PollingHandler),
             (r"/feedback", FeedbackHandler),
-            (r"/filepath/(.*)", FileHandler, {"path": static_path},),
+            (r"/filepath/(.*)", MediaHandler, {"path": ""},),
             (r"/notebook", NotebookHandler),
             (r"/stages", StagesHandler),
             (r"/state", StateHandler),
diff --git a/fiftyone/server/util.py b/fiftyone/server/util.py
--- a/fiftyone/server/util.py
+++ b/fiftyone/server/util.py
@@ -41,17 +41,20 @@ def get_file_dimensions(file_path):
     Returns:
         tuple: (width, height) as integers
     """
-    mime_type, _ = mimetypes.guess_type(file_path)
-    if mime_type is None:
-        raise UnknownFileFormat(
-            "Cannot identify mime type from %r" % file_path
-        )
-    category = mime_type.split("/")[0]
-    if category == "image":
-        return get_image_size(file_path)
-    elif category == "video":
-        return etav.get_frame_size(file_path)
-    raise UnknownFileFormat("Unhandled mime type: %r" % mime_type)
+    try:
+        mime_type, _ = mimetypes.guess_type(file_path)
+        if mime_type is None:
+            raise UnknownFileFormat(
+                "Cannot identify mime type from %r" % file_path
+            )
+        category = mime_type.split("/")[0]
+        if category == "image":
+            return get_image_size(file_path)
+        elif category == "video":
+            return etav.get_frame_size(file_path)
+        raise UnknownFileFormat("Unhandled mime type: %r" % mime_type)
+    except:
+        return 512, 512
 
 
 types = collections.OrderedDict()


Test patch:
diff --git a/tests/isolated/server_tests.py b/tests/isolated/server_tests.py
--- a/tests/isolated/server_tests.py
+++ b/tests/isolated/server_tests.py
@@ -51,11 +51,10 @@ def test_fiftyone(self):
     def test_filepath(self):
         data = {"hello": "world"}
         with etau.TempDir() as tmp:
-            path = os.path.join(tmp, "data.json")
+            path = os.path.join(tmp, "data%20.json")
             etas.write_json(data, path)
             response = self.fetch_and_parse(
-                "/filepath%s"
-                % path.replace(os.sep, posixpath.sep).split(":")[-1]
+                "/filepath/%s" % urllib.parse.quote(path, safe="")
             )
 
         self.assertEqual(response, data)
@@ -77,7 +76,7 @@ class StateTests(TestCase):
     image_url = "https://user-images.githubusercontent.com/3719547/74191434-8fe4f500-4c21-11ea-8d73-555edfce0854.png"
     test_one = os.path.abspath("./test_one.png")
     test_two = os.path.abspath("./test_two.png")
-    dataset = fo.Dataset("test")
+    dataset = fo.Dataset()
     sample1 = fo.Sample(filepath=test_one)
     sample2 = fo.Sample(filepath=test_two)