diff --git a/.env b/.env
new file mode 100644
index 0000000000000000000000000000000000000000..a363ff7205584052db0e653874ad0c04a8a4ef5d
--- /dev/null
+++ b/.env
@@ -0,0 +1,6 @@
+DISPLAY=:1
+UID=1000
+GID=1000
+DOCKER_ID=eroniki
+DOCKER_TAG=0.0.1
+MPLCONFIGDIR=/tmp/matplotlib_cache
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..8b6a040eaaec438f1cee7e3e701df41f0542b622
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+# Use the official Python image as the base image
+FROM python:3.9-slim
+RUN apt-get update -y
+RUN apt-get install -y libx11-dev
+RUN apt-get install -y python3-tk
+
+# Set the working directory in the container
+WORKDIR /app
+
+RUN pip install pip --upgrade
+# Copy the Python script into the container
+COPY requirements.txt /tmp/requirements.txt
+RUN pip install --no-cache-dir  -r /tmp/requirements.txt
+
+# Run the Python script
+# CMD ["python", "plot.py"]
+CMD ["/bin/bash"]
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b5279a6458f4e98003b8876d3d4085ff112ce20d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,64 @@
+SHELL=/bin/bash
+env_file = .env
+include ${env_file}
+
+.PHONY: clean up down restart stop reload ps logs logsf push env
+
+all: reload
+
+default: all
+
+unsupervised-segmentation:
+	docker exec -it $@ bash
+	
+clean: pyclean prune
+
+up:
+	@echo "Starting up containers for $(PROJECT_NAME)..."
+	docker-compose up --build --detach --remove-orphans
+
+down:
+	@echo "Removing containers."
+	docker-compose down	--remove-orphans
+
+restart:
+	@echo "Restarting containers."
+	docker-compose restart
+
+stop:
+	@echo "Stopping containers for $(PROJECT_NAME)..."
+	docker-compose stop
+
+reload: reload_env reload_conts
+
+reload_conts: stop down up
+
+reload_env:
+	@echo "Reloading the environment variables."
+	@source ${env_file}
+
+ps:
+	@docker ps --filter name="$(PROJECT_NAME)*"
+
+logs:
+	@echo "Displaying past containers logs"
+	docker-compose logs
+
+logsf:
+	@echo "Follow containers logs output"
+	docker-compose logs -f
+
+push:
+	@docker login
+	@docker-compose push
+
+pyclean:
+	@sudo find . -regex '^.*\(__pycache__\|\.py[co]\)$$' -delete
+
+prune: prune_conts prune_vols
+
+prune_conts:
+	docker system prune --all --force
+
+prune_vols:
+	docker volume prune --force
diff --git a/README.md b/README.md
index 9d291a6781c7c38c0a5dccaa8276f686f94a4669..fad4371cafbe93fae9850e3b0cbc07b6e0bb1df0 100644
--- a/README.md
+++ b/README.md
@@ -91,3 +91,56 @@ For open source projects, say how it is licensed.
 
 ## Project status
 If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
+
+# Unsupervised Segmentation
+
+## Problem Description
+
+You have a LiDAR scanning the environment and you received a pointcloud with each point having attributes (x, y, z) in sensor coordinates.
+The goal is to segment the pointcloud so as to identify the objects in the scene.
+
+- Each segment should be assigned a unique ID.
+- All points belonging to a segment should share the same ID as the segment.
+- Each point should be associated with only one segment.
+
+## Things to Consider
+
+- Consider only the points that don't belong to the ground plane for the segmentation task.
+
+## Implementation Details
+
+- Folder `data` contains a pointcloud data.
+- File `implementation.py` has been setup to load pointcloud from folder `data` into a numpy array and visualization has been provided to visualize how input 3D pointcloud looks like.
+
+## Expectation
+
+- It is expected that the core-parts of the algorithm to be written from scratch.
+- 3rd-party libraries can be used for parts of the algorithm, like Linear Algebra routines, optimization routines, etc.
+- Provide explanation as to why a specific algorithm has been selected over others methods.
+
+## Example
+
+Input:
+
+```python
+pointcloud = [
+              [0.0, 0.0, 0.0],    # Point-1
+              [2.0, 2.0, 2.0],    # Point-2
+              [2.16, 4.89, 5.78], # Point-3
+              [0.01, 0.1, 0.03],  # Point-4
+              # ... 
+              ]
+```
+
+Output:
+
+```python
+segment_ids = [0,     # id = 0 as Point-1 belongs to segment-0
+               1,     # id = 1 as Point-2 belongs to segment-1
+               2,     # id = 2 as Point-3 belongs to segment-2
+               0,     # id = 0 as Point-4 belongs to segment-0
+              #...
+              ]
+```
+
+`len(segment_ids)` is same as `len(pointcloud)`
diff --git a/data/cyngn_interview_question_pointcloud_data.npy b/data/cyngn_interview_question_pointcloud_data.npy
new file mode 100644
index 0000000000000000000000000000000000000000..5640bc14768e580dabc99447fcdfc3ea4dcb38f9
Binary files /dev/null and b/data/cyngn_interview_question_pointcloud_data.npy differ
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b88d9157c72baaae900a62d09ea788ef7bce824e
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,23 @@
+version: '3.8'
+x-service: &service
+  init: true
+  tty: true
+  stdin_open: true
+  user: "${UID}:${GID}"
+  environment:
+    - DISPLAY=${DISPLAY}
+    - MPLCONFIGDIR=${MPLCONFIGDIR}
+  volumes:
+    - .:/app
+    - /tmp/.X11-unix:/tmp/.X11-unix:rw
+    - /etc/group:/etc/group:ro
+    - /etc/passwd:/etc/passwd:ro
+    - /etc/shadow:/etc/shadow:ro
+    - /etc/sudoers.d:/etc/sudoers.d:ro
+
+services:
+  unsupervised-segmentation: 
+    <<: *service
+    build: .
+    image: "${DOCKER_ID}/unsupervised-segmentation:${DOCKER_TAG}"
+    container_name: "unsupervised-segmentation"
diff --git a/implementation.py b/implementation.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e13add3e06188684f3a3d93c5832014b5ae8d41
--- /dev/null
+++ b/implementation.py
@@ -0,0 +1,92 @@
+#%% imports
+import matplotlib
+matplotlib.use('TkAgg')
+import numpy as np
+from enum import IntEnum
+import matplotlib.pyplot as plt
+
+from segmentation.base import BaseSegmenter
+from segmentation.segmentation import SurfaceSegmenter
+#%% types
+class Index(IntEnum):
+    X = 0
+    Y = 1
+    Z = 2
+
+#%% helper functions
+def visualize_pointcloud_downsampled(pc:np.ndarray, downsample_factor:int=10) -> None:
+    fig = plt.figure(figsize=(25, 25))
+    ax = fig.add_subplot(111, projection='3d')
+
+    ax.scatter(pc[::downsample_factor, Index.X],
+               pc[::downsample_factor, Index.Y],
+               pc[::downsample_factor, Index.Z],
+               color="red", s=0.1)
+    ax.set_xlabel("x (m)", fontsize=14)
+    ax.set_ylabel("y (m)", fontsize=14)
+    ax.set_zlabel("z (m)", fontsize=14)
+    ax.set_xlim(-20, 20)
+    ax.set_ylim(-20, 20)
+    ax.set_zlim(-20, 50)
+    ax.set_title("Pointcloud (3D)", fontsize=14)
+    plt.show()
+
+    # make this plot occupy 30% of the figure's width and 100% of its height
+    plt.figure(figsize=(25, 25))
+    plt.plot(pc[:, Index.X], pc[:, Index.Y], "rx", markersize=1, alpha=0.2)
+    plt.xlabel("x (m)", fontsize=14)
+    plt.ylabel("y (m)", fontsize=14)
+    plt.grid(True)
+    plt.gca().set_aspect('equal', adjustable='box')
+    plt.title("Pointcloud (Top View)", fontsize=14)
+    plt.show()
+
+def visualize_pointcloud_downsampled_with_segment_ids(pc: np.ndarray, segment_ids: np.ndarray,
+                                                      downsample_factor:int=10) -> None:
+    fig = plt.figure(figsize=(25, 25))
+    ax = fig.add_subplot(111, projection='3d')
+
+    ax.scatter(pc[::downsample_factor, Index.X],
+               pc[::downsample_factor, Index.Y],
+               pc[::downsample_factor, Index.Z],
+               c=segment_ids[::downsample_factor],
+               cmap="tab20",
+               s=0.2)
+    ax.set_xlabel("x (m)", fontsize=14)
+    ax.set_ylabel("y (m)", fontsize=14)
+    ax.set_zlabel("z (m)", fontsize=14)
+    ax.set_xlim(-20, 20)
+    ax.set_ylim(-20, 20)
+    ax.set_zlim(-20, 50)
+    ax.set_title("Pointcloud (3D)", fontsize=14)
+    plt.show()
+
+    # make this plot occupy 30% of the figure's width and 100% of its height
+    plt.figure(figsize=(25, 25))
+    plt.scatter(pc[:, Index.X], pc[:, Index.Y], c=segment_ids, cmap="tab20", s=1, alpha=0.5)
+    plt.xlabel("x (m)", fontsize=14)
+    plt.ylabel("y (m)", fontsize=14)
+    plt.grid(True)
+    plt.gca().set_aspect('equal', adjustable='box')
+    plt.title("Pointcloud (Top View)", fontsize=14)
+    plt.show()
+
+#%%
+pointcloud = np.load("data/cyngn_interview_question_pointcloud_data.npy")
+visualize_pointcloud_downsampled(pointcloud, downsample_factor=5) # use 'downsample_factor=1' for no downsampling during visualization
+
+##### TODO: REQUIRES IMPLEMENTATION ##############################
+##################################################################
+def segment_pointcloud(pointcloud:np.ndarray) -> np.ndarray:
+    # input is a pointcloud of shape (N, 3)
+    awesome_segmenter = SurfaceSegmenter()
+    mask_surfaces = awesome_segmenter.predict(pointcloud)
+    mask = mask_surfaces 
+    # output is a segmentation mask of shape (N,)
+      # where each element is an integer representing the segment id
+    return np.array(mask)
+
+segment_ids = segment_pointcloud(pointcloud)
+visualize_pointcloud_downsampled_with_segment_ids(pointcloud, segment_ids, downsample_factor=5) # use 'downsample_factor=1' for no downsampling during visualization
+
+# %%
diff --git a/murat.code-workspace b/murat.code-workspace
new file mode 100644
index 0000000000000000000000000000000000000000..898b479d45b700bf5e191fd91900b5b1d325dc88
--- /dev/null
+++ b/murat.code-workspace
@@ -0,0 +1,74 @@
+{
+    "folders": [
+        {
+            "path": "."
+        }
+    ],
+    "settings": {
+        "terminal.integrated.gpuAcceleration": "off",
+        "editor.wordWrap": "on",
+        "latex-workshop.latex.recipe.default": "lastUsed",
+        "latex-workshop.latex.autoBuild.run": "never",
+        "latex-workshop.latex.recipes": [
+            {
+                "name": "magic 🐟",
+                "tools": [
+                    "pdflatex",
+                    "bibtex",
+                    "pdflatex",
+                    "makeglossaries",
+                    // "makeindex",
+                    "pdflatex"
+                ]
+            }
+        ],
+        "files.associations": {
+            "*.tikz": "latex",
+            "*.table": "latex",
+            "*.figure": "latex"
+        },
+        "latex-workshop.latex.tools": [
+            {
+                "name": "makeindex",
+                "command": "makeindex",
+                "args": [
+                    "%DOCFILE%.nlo",
+                    "-s",
+                    "nomencl.ist",
+                    "-o",
+                    "%DOCFILE%.nls"
+                ]
+            },
+            {
+                "name": "pdflatex",
+                "command": "pdflatex",
+                "args": [
+                    "-synctex=1",
+                    "-interaction=nonstopmode",
+                    "-file-line-error",
+                    "%DOC%"
+                ],
+                "env": {}
+            },
+            {
+                "name": "bibtex",
+                "command": "bibtex",
+                "args": [
+                    "%DOCFILE%"
+                ],
+                "env": {}
+            },
+            {
+                "name": "makeglossaries",
+                "command": "makeglossaries",
+                "args": [
+                    "%DOCFILE%"
+                ]
+            }
+        ],
+    },
+    "tasks": {
+        "version": "2.0.0",
+        "tasks": []
+    }
+}
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..21b52ac089518b2aa1f282c7e9b1ca4e9a87427c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,6 @@
+numpy==1.26.4
+scipy==1.12.0
+matplotlib==3.8.3
+seaborn==0.13.2
+scikit-learn==1.4.1.post1
+open3d
\ No newline at end of file
diff --git a/segmentation/__init__.py b/segmentation/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/segmentation/__pycache__/__init__.cpython-312.pyc b/segmentation/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3c6d2652cab58e9423df820474afb35c5ed76fe4
Binary files /dev/null and b/segmentation/__pycache__/__init__.cpython-312.pyc differ
diff --git a/segmentation/__pycache__/base.cpython-312.pyc b/segmentation/__pycache__/base.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b3232ad56dc3c93b19e5ea7394af86be35db38df
Binary files /dev/null and b/segmentation/__pycache__/base.cpython-312.pyc differ
diff --git a/segmentation/__pycache__/segmentation.cpython-312.pyc b/segmentation/__pycache__/segmentation.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..58f99716fc4f72855ef16347dd0d61b613f12060
Binary files /dev/null and b/segmentation/__pycache__/segmentation.cpython-312.pyc differ
diff --git a/segmentation/base.py b/segmentation/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..826d7cecaa2f6388a5c85d14d58ef7df466b69d7
--- /dev/null
+++ b/segmentation/base.py
@@ -0,0 +1,22 @@
+import numpy as np
+import sklearn as sk
+from sklearn.cluster import DBSCAN
+
+
+class BaseSegmenter(sk.base.BaseEstimator):
+    def __init__(self, param=1):
+        super().__init__()
+        self.param = param
+
+    def fit(self, X, y=None):
+        raise NotImplementedError("In case we want to try some supervised method")
+
+    def predict(self, point_cloud):
+        dbscan = DBSCAN(eps=0.2, min_samples=5)
+        clusters = dbscan.fit_predict(point_cloud)
+
+        return clusters
+
+
+if __name__ == '__main__':
+    pass
\ No newline at end of file
diff --git a/segmentation/segmentation.py b/segmentation/segmentation.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e5be8ec41e7b407efe6b1be09aeecfe6ab90ab7
--- /dev/null
+++ b/segmentation/segmentation.py
@@ -0,0 +1,26 @@
+import numpy as np
+from segmentation.base import BaseSegmenter
+import open3d as o3d
+
+
+class SurfaceSegmenter(BaseSegmenter):
+    def __init__(self, param=1):
+        super().__init__(param)
+
+    def fit(self, X, y=None):
+        raise NotImplementedError("In case we want to try some supervised method")
+
+    def predict(self, point_cloud):
+        pcd = o3d.geometry.PointCloud()
+        pcd.points = o3d.utility.Vector3dVector(point_cloud)
+
+        # Use RANSAC to find a plane in the point cloud
+        plane_model, inliers = pcd.segment_plane(distance_threshold=0.01,
+                                                ransac_n=3,
+                                                num_iterations=1000)
+        
+        inlier_cloud = pcd.select_by_index(inliers)
+
+        # Visualize the inlier points
+        o3d.visualization.draw_geometries([inlier_cloud])
+        return inlier_cloud
\ No newline at end of file