diff --git a/README.md b/README.md
index ebe0f8307b6753070000924de6a0d0fc538254ee..1907714e593ec71f117ec15632cb5cf264b40a85 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,9 @@
   </p>
 </div>
 
+[![forthebadge](https://forthebadge.com/images/badges/made-with-kotlin.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/built-for-android.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com)
+[![Android](https://img.shields.io/badge/Android-%233DDC84.svg?&style=for-the-badge&logo=android&logoColor=white)](https://developer.android.com/) [![Kotlin](https://img.shields.io/badge/Kotlin-%230095D5.svg?&style=for-the-badge&logo=kotlin&logoColor=white)](https://kotlinlang.org/) [![Gradle](https://img.shields.io/badge/Gradle-%2302303A.svg?&style=for-the-badge&logo=gradle&logoColor=white)](https://gradle.org/) [![OpenCV](https://img.shields.io/badge/OpenCV-%23opencv.svg?&style=for-the-badge&logo=opencv&logoColor=white)](https://opencv.org/)
+
 <!-- TABLE OF CONTENTS -->
 <details>
   <summary>Table of Contents</summary>
@@ -32,13 +35,42 @@
         <li><a href="#installation">Installation</a></li>
       </ul>
     </li>
+    <li><a href="#how-to-use">How To Use</a></li>
     <li><a href="#Development">Development</a></li>
-    <li><a href="#usage">Usage</a></li>
-    <li><a href="#contact">Contact</a></li>
+    <li><a href="#contributors">Contributors</a></li>
     <li><a href="#acknowledgments">Acknowledgments</a></li>
   </ol>
 </details>
 
+
+# About The Project
+OMRekap adalah aplikasi rekapitulasi pemilihan umum yang dibuat untuk memudahkan proses rekapitulasi suara pada pemilihan umum.
+Aplikasi ini dibuat menggunakan bahasa pemrograman Kotlin dan menggunakan OpenCV untuk mendeteksi dan mengenali lembar suara.
+
+<p align="right">(<a href="#readme-top">back to top</a>)</p>
+
+# Features
+1. Mengambil foto kertas plano menggunakan kamera 📷 atau menggunakan galeri 🖼
+2. Melihat hasil foto yang diambil pada halaman berikutnya
+3. Mendeteksi dan mengenali lembar suara
+4. Menampilkan hasil rekapitulasi suara
+5. Menyimpan hasil rekapitulasi suara dalam bentuk foto 🖼 dan JSON 📃
+![Application](screenshots/screenshot.png)
+
+<p align="right">(<a href="#readme-top">back to top</a>)</p>
+
+# How To Use
+1. Navigasi ke folder apk
+2. Unduh file apk lalu pindahkan ke perangkat android
+3. Buka file apk di perangkat android
+4. Install aplikasi
+5. Buka aplikasi
+
+Alternatif lain untuk menjalankan aplikasi:
+1. Clone repository https://gitlab.informatika.org/k-02-09/omrekap.git
+2. Buka project menggunakan Android Studio
+3. Run aplikasi menggunakan emulator atau perangkat android
+
 # Development
 ### Clone the repository
 ```bash
@@ -64,20 +96,22 @@ git clone https://gitlab.informatika.org/k-02-09/omrekap
 
 <p align="right">(<a href="#readme-top">back to top</a>)</p>
 
-# Built With
-* [![Android](https://img.shields.io/badge/Android-%233DDC84.svg?&style=flat&logo=android&logoColor=white)](https://developer.android.com/)
-* [![Kotlin](https://img.shields.io/badge/Kotlin-%230095D5.svg?&style=flat&logo=kotlin&logoColor=white)](https://kotlinlang.org/)
-* [![Gradle](https://img.shields.io/badge/Gradle-%2302303A.svg?&style=flat&logo=gradle&logoColor=white)](https://gradle.org/)
-* [![OpenCV](https://img.shields.io/badge/OpenCV-%23opencv.svg?&style=flat&logo=opencv&logoColor=white)](https://opencv.org/)
-
-# About The Project
-OMRekap adalah aplikasi rekapitulasi pemilihan umum yang dibuat untuk memudahkan proses rekapitulasi suara pada pemilihan umum.
-Aplikasi ini dibuat menggunakan bahasa pemrograman Kotlin dan menggunakan OpenCV untuk mendeteksi dan mengenali lembar suara.
-
+# Contributors
+<table>
+  <tbody>
+    <tr>
+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/Altair1618"><img src="https://github.com/Altair1618" width="100px;" alt="Farhan Nabil Suryono"/><br /><sub><b>Farhan Nabil Suryono</b></sub></a><br /><a href="https://github.com/codesandbox/codesandbox-client/issues?q=author%3ACompuIves" title="Bug reports">🐛</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Code">💻</a> <a href="#design-CompuIves" title="Design">🎨</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Documentation">📖</a> <a href="#infra-CompuIves" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/codesandbox/codesandbox-client/pulls?q=is%3Apr+reviewed-by%3ACompuIves" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Tests">⚠️</a> <a href="#tool-CompuIves" title="Tools">🔧</a></td>
+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/Enliven26"><img src="https://github.com/Enliven26" width="100px;" alt="Johanes Lee"/><br /><sub><b>Johanes Lee</b></sub></a><br /><a href="https://github.com/codesandbox/codesandbox-client/issues?q=author%3ACompuIves" title="Bug reports">🐛</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Code">💻</a> <a href="#design-CompuIves" title="Design">🎨</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Documentation">📖</a> <a href="#infra-CompuIves" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/codesandbox/codesandbox-client/pulls?q=is%3Apr+reviewed-by%3ACompuIves" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Tests">⚠️</a> <a href="#tool-CompuIves" title="Tools">🔧</a></td>
+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/dhanikanovlisa"><img src="https://github.com/dhanikanovlisa" width="100px;" alt="Dhanika Novlisariyanti"/><br /><sub><b>Dhanika Novlisariyanti</b></sub></a><br /><a href="https://github.com/codesandbox/codesandbox-client/issues?q=author%3ACompuIves" title="Bug reports">🐛</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Code">💻</a> <a href="#design-CompuIves" title="Design">🎨</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Documentation">📖</a> <a href="#infra-CompuIves" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/codesandbox/codesandbox-client/pulls?q=is%3Apr+reviewed-by%3ACompuIves" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Tests">⚠️</a> <a href="#tool-CompuIves" title="Tools">🔧</a></td>
+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/Genvictus"><img src="https://github.com/Genvictus" width="100px;" alt="Johann Christian Kandani"/><br /><sub><b>Johann Christian Kandani</b></sub></a><br /><a href="https://github.com/codesandbox/codesandbox-client/issues?q=author%3ACompuIves" title="Bug reports">🐛</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Code">💻</a> <a href="#design-CompuIves" title="Design">🎨</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Documentation">📖</a> <a href="#infra-CompuIves" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/codesandbox/codesandbox-client/pulls?q=is%3Apr+reviewed-by%3ACompuIves" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Tests">⚠️</a> <a href="#tool-CompuIves" title="Tools">🔧</a></td>
+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/Michaelu670"><img src="https://github.com/Michaelu670" width="100px;" alt="Michael Utama"/><br /><sub><b>Michael Utama</b></sub></a><br /><a href="https://github.com/codesandbox/codesandbox-client/issues?q=author%3ACompuIves" title="Bug reports">🐛</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Code">💻</a> <a href="#design-CompuIves" title="Design">🎨</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Documentation">📖</a> <a href="#infra-CompuIves" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/codesandbox/codesandbox-client/pulls?q=is%3Apr+reviewed-by%3ACompuIves" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/codesandbox/codesandbox-client/commits?author=CompuIves" title="Tests">⚠️</a> <a href="#tool-CompuIves" title="Tools">🔧</a></td>
+  </tbody>
+</table>
 <p align="right">(<a href="#readme-top">back to top</a>)</p>
 
-# Usage
-
-# Contact
+# Acknowledgments
+* [OpenCV](https://opencv.org/)
+* [Kotlin](https://kotlinlang.org/)
+* [Android](https://developer.android.com/)
 
-# Acknowledgments
\ No newline at end of file
+<p align="right">(<a href="#readme-top">back to top</a>)</p>
\ No newline at end of file
diff --git a/app/src/main/assets/omr_config.json b/app/src/main/assets/omr_config.json
index e16411192251b88ebfbcc2acba3e8be1b2e91ad7..2fb34a2a37a02d97b771c462a9942714f82291c3 100644
--- a/app/src/main/assets/omr_config.json
+++ b/app/src/main/assets/omr_config.json
@@ -2,9 +2,9 @@
   "omrConfigs": {
     "102": {
       "contents": {
-        "FIRST": "Anis",
-        "SECOND": "Bowo",
-        "THIRD": "Janggar"
+        "FIRST": "Anies Rasyid Baswedan",
+        "SECOND": "Prabowo Subianto",
+        "THIRD": "Ganjar Pranowo"
       },
       "contourOMRHelperConfig": {
         "darkIntensityThreshold": 150,
diff --git a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/CircleTemplateLoader.kt b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/CircleTemplateLoader.kt
index f2deec73f6eedfca7503d262ac9a0c3c07b565a3..c1bac3f1706db8e7d5883b55ea56b4d72f835815 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/CircleTemplateLoader.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/CircleTemplateLoader.kt
@@ -6,7 +6,16 @@ import org.opencv.core.MatOfByte
 import org.opencv.imgcodecs.Imgcodecs
 import java.io.InputStream
 
+/**
+ * Load the circle template image
+ * @param appContext application context
+ * @param resId resource id of the circle template image
+ */
 class CircleTemplateLoader(private val appContext: Context, private val resId: Int) {
+	/**
+	 * Load the template image
+	 * @return template image
+	 */
 	fun loadTemplateImage(): Mat {
 		val inputStream: InputStream = appContext.resources.openRawResource(resId)
 		val byteArray = inputStream.readBytes()
diff --git a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/ContourOMRHelperConfig.kt b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/ContourOMRHelperConfig.kt
index 4053bca16b8d67e8257f02100f6df05c50644608..911d92de906d3a2500131ade2b1bced7f15a1441 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/ContourOMRHelperConfig.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/ContourOMRHelperConfig.kt
@@ -1,5 +1,15 @@
 package com.k2_9.omrekap.data.configs.omr
 
+/**
+ * Configuration for the OMR helper
+ * @param omrCropper cropper for the OMR section
+ * @param minRadius minimum radius of the circle
+ * @param maxRadius maximum radius of the circle
+ * @param minAspectRatio minimum aspect ratio of the circle
+ * @param maxAspectRatio maximum aspect ratio of the circle
+ * @param darkPercentageThreshold threshold for the percentage of dark pixels in the circle
+ * @param darkIntensityThreshold threshold for the intensity of dark pixels in the circle
+ */
 class ContourOMRHelperConfig(
 	omrCropper: OMRCropper,
 	minRadius: Int,
diff --git a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRCropper.kt b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRCropper.kt
index 7ee53ff4e727bd4262169b5e000e10cd1867b3d7..66f5e63e64be8740cceddaa5f82065fa3fdbb4ca 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRCropper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRCropper.kt
@@ -3,7 +3,16 @@ package com.k2_9.omrekap.data.configs.omr
 import org.opencv.core.Mat
 import org.opencv.core.Rect
 
+/**
+ * Cropper for OMR section
+ * @param config configuration for the cropper
+ */
 class OMRCropper(val config: OMRCropperConfig) {
+	/**
+	 * Crop the image to the section
+	 * @param section section to be cropped
+	 * @return cropped image
+	 */
 	fun crop(section: OMRSection): Mat {
 		val (x, y) = config.getSectionPosition(section)
 		val (width, height) = config.omrSectionSize
@@ -13,6 +22,11 @@ class OMRCropper(val config: OMRCropperConfig) {
 		return Mat(config.image, roi)
 	}
 
+	/**
+	 * Get the position of the section
+	 * @param section section to get the position
+	 * @return position of the section
+	 */
 	fun sectionPosition(section: OMRSection): Rect {
 		val (x, y) = config.getSectionPosition(section)
 		val (width, height) = config.omrSectionSize
diff --git a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRCropperConfig.kt b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRCropperConfig.kt
index fabe92619a12e5b9fb6d70a870eb82248b5a1cb7..9a9be96683bdda6d5c7f9208dfff9337db843edd 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRCropperConfig.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRCropperConfig.kt
@@ -2,6 +2,12 @@ package com.k2_9.omrekap.data.configs.omr
 
 import org.opencv.core.Mat
 
+/**
+ * Configuration for the cropper
+ * @param image image to be cropped
+ * @param omrSectionSize size of the OMR section
+ * @param omrSectionPosition position of the OMR section
+ */
 class OMRCropperConfig(
 	image: Mat?,
 	val omrSectionSize: Pair<Int, Int>,
@@ -39,10 +45,19 @@ class OMRCropperConfig(
 		}
 	}
 
+	/**
+	 * Get the position of the section
+	 * @param section section to get the position
+	 * @return position of the section
+	 */
 	fun getSectionPosition(section: OMRSection): Pair<Int, Int> {
 		return omrSectionPosition[section]!!
 	}
 
+	/**
+	 * Set the image
+	 * @param image image to be set
+	 */
 	fun setImage(image: Mat) {
 		require(omrSectionSize.first <= image.width() && omrSectionSize.second <= image.height()) {
 			"OMR section size must be less than or equal to the image size"
diff --git a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRHelperConfig.kt b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRHelperConfig.kt
index 0022677d2746eec2256441f2115ce5caf0f7ff90..8a72269ca793ab161edb79929fb849fd2ea13878 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRHelperConfig.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRHelperConfig.kt
@@ -1,5 +1,9 @@
 package com.k2_9.omrekap.data.configs.omr
 
+/**
+ * Configuration for the OMR helper
+ * @param omrCropper cropper for the OMR section
+ */
 open class OMRHelperConfig(
 	val omrCropper: OMRCropper,
 )
diff --git a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRSection.kt b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRSection.kt
index d6f0ce8be6cbb4c13adda1c8c5760bfed2995b3b..3fc63501aad63dc15c66fff2f9bba645d5c487c7 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRSection.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/OMRSection.kt
@@ -1,5 +1,8 @@
 package com.k2_9.omrekap.data.configs.omr
 
+/**
+ * Enum for OMR section
+ */
 enum class OMRSection {
 	FIRST,
 	SECOND,
diff --git a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/TemplateMatchingOMRHelperConfig.kt b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/TemplateMatchingOMRHelperConfig.kt
index f3d83c34d2ece54916ac5368670d518ffbdd704d..59d62fb4c337f8a0a2f9bf870073b634669b7843 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/configs/omr/TemplateMatchingOMRHelperConfig.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/configs/omr/TemplateMatchingOMRHelperConfig.kt
@@ -2,6 +2,12 @@ package com.k2_9.omrekap.data.configs.omr
 
 import org.opencv.core.Mat
 
+/**
+ * Configuration for the OMR helper using template matching
+ * @param omrCropper cropper for the OMR section
+ * @param templateLoader loader for the template image
+ * @param similarityThreshold threshold for the similarity between the template and the cropped image
+ */
 class TemplateMatchingOMRHelperConfig(
 	omrCropper: OMRCropper,
 	templateLoader: CircleTemplateLoader?,
diff --git a/app/src/main/java/com/k2_9/omrekap/data/models/CornerPoints.kt b/app/src/main/java/com/k2_9/omrekap/data/models/CornerPoints.kt
index e3f8f5c8efd4dbf98b66475e340d3b026a7b652d..af17fa9ff64c843614f2f2e9549c6cd38df47751 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/models/CornerPoints.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/models/CornerPoints.kt
@@ -2,6 +2,13 @@ package com.k2_9.omrekap.data.models
 
 import org.opencv.core.Point
 
+/**
+ * Data class for corner points of a document
+ * @param topLeft top left corner point
+ * @param topRight top right corner point
+ * @param bottomRight bottom right corner point
+ * @param bottomLeft bottom left corner point
+ */
 data class CornerPoints(
 	val topLeft: Point,
 	val topRight: Point,
diff --git a/app/src/main/java/com/k2_9/omrekap/data/models/ImageSaveData.kt b/app/src/main/java/com/k2_9/omrekap/data/models/ImageSaveData.kt
index e8186b427965953e6d3a871588ebb36ae17977b8..0002067620ea33fd1cf20a94bb353de878a5d61c 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/models/ImageSaveData.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/models/ImageSaveData.kt
@@ -3,6 +3,13 @@ package com.k2_9.omrekap.data.models
 import android.graphics.Bitmap
 import java.time.Instant
 
+/**
+ * Data class for saving image data
+ * @param rawImage raw image
+ * @param annotatedImage image with annotations
+ * @param data map of candidate names and their vote counts
+ * @param timestamp timestamp of the image
+ */
 data class ImageSaveData(
 	val rawImage: Bitmap,
 	var annotatedImage: Bitmap,
diff --git a/app/src/main/java/com/k2_9/omrekap/data/models/OMRBaseConfiguration.kt b/app/src/main/java/com/k2_9/omrekap/data/models/OMRBaseConfiguration.kt
index f5cc6f3d16a24b0c378d4888bfa920251d6e2284..040dab38380013093d29be26b45452ecfdccb748 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/models/OMRBaseConfiguration.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/models/OMRBaseConfiguration.kt
@@ -11,6 +11,12 @@ data class OMRBaseConfiguration(
 	val omrConfigs: Map<String, OMRConfigurationParameter>,
 )
 
+/**
+ * Configuration parameters for OMR detection
+ * @param contents map of OMR section and candidate name
+ * @param contourOMRHelperConfig configuration for contour-based OMR detection
+ * @param templateMatchingOMRHelperConfig configuration for template matching-based OMR detection
+ */
 data class OMRConfigurationParameter(
 	val contents: Map<OMRSection, String>,
 	val contourOMRHelperConfig: ContourOMRHelperConfig,
diff --git a/app/src/main/java/com/k2_9/omrekap/data/repository/OMRConfigRepository.kt b/app/src/main/java/com/k2_9/omrekap/data/repository/OMRConfigRepository.kt
index 05dbebd85b7a681ee9c63483e9cca47cd8dd8cc1..94433d83c2722836a493bd0dbccb7a2408def592 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/repository/OMRConfigRepository.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/repository/OMRConfigRepository.kt
@@ -8,7 +8,14 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import java.io.IOException
 
+/** Repository for loading OMR configurations from JSON */
+
 object OMRConfigRepository {
+	/**
+	 * Load OMR configurations from JSON file
+	 * @param context application context
+	 * @return OMRBaseConfiguration object
+	 */
 	suspend fun loadConfigurations(context: Context): OMRBaseConfiguration? {
 		val jsonString =
 			withContext(Dispatchers.IO) {
@@ -23,12 +30,22 @@ object OMRConfigRepository {
 		}
 	}
 
+	/**
+	 * Get the JSON string of the OMR configuration
+	 * @param omrBaseConfiguration OMR configuration object
+	 * @return JSON string of the configuration
+	 */
 	fun printConfigurationJson(omrBaseConfiguration: OMRBaseConfiguration): String {
 		val jsonString = OMRJsonConfigLoader.toJson(omrBaseConfiguration)
 		Log.d("JSONConfigRepo", jsonString)
 		return jsonString
 	}
 
+	/**
+	 * Read the JSON configuration file
+	 * @param context application context
+	 * @return JSON string of the configuration
+	 */
 	private fun readConfigString(context: Context): String? {
 		val inputStream = context.assets.open("omr_config.json")
 		return try {
diff --git a/app/src/main/java/com/k2_9/omrekap/data/repository/OMRJsonConfigLoader.kt b/app/src/main/java/com/k2_9/omrekap/data/repository/OMRJsonConfigLoader.kt
index 9cf3d98d63238614c21d4ec83682d2d6ea66ef62..2a3d64f215e5d6c764163f6560bf919d89540676 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/repository/OMRJsonConfigLoader.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/repository/OMRJsonConfigLoader.kt
@@ -3,13 +3,26 @@ package com.k2_9.omrekap.data.repository
 import com.google.gson.Gson
 import com.k2_9.omrekap.data.models.OMRBaseConfiguration
 
+/**
+ * JSON configuration loader for OMR configurations
+ */
 object OMRJsonConfigLoader {
 	private val gson = Gson()
 
+	/**
+	 * Parse JSON string to OMRBaseConfiguration object
+	 * @param jsonString JSON string
+	 * @return OMRBaseConfiguration object
+	 */
 	fun parseJson(jsonString: String): OMRBaseConfiguration? {
 		return gson.fromJson(jsonString, OMRBaseConfiguration::class.java)
 	}
 
+	/**
+	 * Convert OMRBaseConfiguration object to JSON string
+	 * @param omrBaseConfiguration OMRBaseConfiguration object
+	 * @return JSON string
+	 */
 	fun toJson(omrBaseConfiguration: OMRBaseConfiguration): String {
 		return gson.toJson(omrBaseConfiguration)
 	}
diff --git a/app/src/main/java/com/k2_9/omrekap/data/view_models/ImageDataViewModel.kt b/app/src/main/java/com/k2_9/omrekap/data/view_models/ImageDataViewModel.kt
index 3f3696f69d6b899efbc3b27931cff9f98d1561d0..bfa8ed238f76c2884a1175ba201b4a9c17ebfe83 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/view_models/ImageDataViewModel.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/view_models/ImageDataViewModel.kt
@@ -21,10 +21,18 @@ import org.opencv.core.Mat
 import org.opencv.imgproc.Imgproc
 import java.time.Instant
 
+/**
+ * ViewModel for image data processing in Result page
+ */
 class ImageDataViewModel : ViewModel() {
 	private val _data = MutableLiveData<ImageSaveData?>()
 	val data = _data as LiveData<ImageSaveData?>
 
+	/**
+	 * Process the image data with OMR detection
+	 * @param data image data to be processed
+	 * @param circleTemplateLoader loader for circle template
+	 */
 	fun processImage(
 		data: ImageSaveData,
 		circleTemplateLoader: CircleTemplateLoader,
@@ -86,11 +94,12 @@ class ImageDataViewModel : ViewModel() {
 				for ((section, value) in it) {
 					stringKeyResult[pageContent[section]!!] = value
 
-					annotatedImage = ImageAnnotationHelper.annotateOMR(
-						annotatedImage,
-						contourOMRHelper.getSectionPosition(section),
-						value
-					)
+					annotatedImage =
+						ImageAnnotationHelper.annotateOMR(
+							annotatedImage,
+							contourOMRHelper.getSectionPosition(section),
+							value,
+						)
 					Log.d("Result", "${pageContent[section]}: $value")
 				}
 			}
diff --git a/app/src/main/java/com/k2_9/omrekap/data/view_models/PreviewViewModel.kt b/app/src/main/java/com/k2_9/omrekap/data/view_models/PreviewViewModel.kt
index 89451f9810cb009b0616a64bfe160e24281fcf3e..6176644ccc0759b69e373c436c82124b6b6b6658 100644
--- a/app/src/main/java/com/k2_9/omrekap/data/view_models/PreviewViewModel.kt
+++ b/app/src/main/java/com/k2_9/omrekap/data/view_models/PreviewViewModel.kt
@@ -10,10 +10,17 @@ import com.k2_9.omrekap.utils.PreprocessHelper
 import kotlinx.coroutines.launch
 import java.time.Instant
 
+/**
+ * ViewModel for image data processing in Preview page
+ */
 class PreviewViewModel : ViewModel() {
 	private val _data = MutableLiveData<ImageSaveData>()
 	val data = _data as LiveData<ImageSaveData>
 
+	/**
+	 * Preprocess the image data
+	 * @param img image to be preprocessed
+	 */
 	fun preprocessImage(img: Bitmap) {
 		viewModelScope.launch {
 			val data = ImageSaveData(img, img, mapOf(), Instant.now())
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/AprilTagHelper.kt b/app/src/main/java/com/k2_9/omrekap/utils/AprilTagHelper.kt
index ecd7aac2e6f4ad0d645ecfe2496faae0f671195f..7a61a3b2899176f2c868376d5cd734bf2d6254e0 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/AprilTagHelper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/AprilTagHelper.kt
@@ -74,6 +74,11 @@ object AprilTagHelper {
 		return (idList to corners)
 	}
 
+	/**
+	 * Annotate the detected april tag and its ID to the image
+	 * @param imageBitmap image to be annotated
+	 * @return annotated image in OpenCV's Mat type
+	 */
 	fun annotateImage(imageBitmap: Bitmap): Mat {
 		val res = getAprilTagId(imageBitmap)
 		val cornerPoints = res.second
@@ -81,6 +86,11 @@ object AprilTagHelper {
 		return ImageAnnotationHelper.annotateAprilTag(prepareImage(imageBitmap), cornerPoints, ids)
 	}
 
+	/**
+	 * Prepare detector with given dictionary
+	 * @param detectorDictionary dictionary to be used for detection
+	 * @return prepared ArucoDetector
+	 */
 	private fun prepareDetector(detectorDictionary: Dictionary): ArucoDetector {
 		// initialize detector parameters
 		val detectorParameters = DetectorParameters()
@@ -88,6 +98,11 @@ object AprilTagHelper {
 		return ArucoDetector(detectorDictionary, detectorParameters)
 	}
 
+	/**
+	 * Prepare image for ArucoDetector with preprocessing
+	 * @param imageBitmap image to be prepared
+	 * @return prepared image in OpenCV's Mat type
+	 */
 	private fun prepareImage(imageBitmap: Bitmap): Mat {
 		// transform to OpenCV Mat data
 		val imageMat = Mat()
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/CropHelper.kt b/app/src/main/java/com/k2_9/omrekap/utils/CropHelper.kt
index 1ff85ab9f1d9b408bcd941ce7794f09f61c50aed..57b8e465b689af083551e8e6d1c084e63c54a8a9 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/CropHelper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/CropHelper.kt
@@ -4,18 +4,24 @@ import android.graphics.Bitmap
 import android.util.Log
 import com.k2_9.omrekap.data.models.CornerPoints
 import org.opencv.android.Utils
+import org.opencv.core.Core
 import org.opencv.core.CvType
 import org.opencv.core.Mat
 import org.opencv.core.MatOfPoint2f
 import org.opencv.core.Point
+import org.opencv.core.Size
 import org.opencv.imgproc.Imgproc
 import org.opencv.imgproc.Imgproc.COLOR_BGR2GRAY
 import org.opencv.imgproc.Imgproc.cvtColor
 import org.opencv.imgproc.Imgproc.getPerspectiveTransform
 import org.opencv.imgproc.Imgproc.warpPerspective
+import kotlin.math.min
 import kotlin.math.pow
 import kotlin.math.sqrt
 
+/**
+ * Helper class for cropping image based on corner points detection
+ */
 object CropHelper {
 	private const val UPPER_LEFT: Int = 0
 	private const val UPPER_RIGHT: Int = 1
@@ -24,6 +30,10 @@ object CropHelper {
 
 	private lateinit var pattern: Mat
 
+	/**
+	 * Load corner pattern image
+	 * @param patternBitmap pattern image in Bitmap
+	 */
 	fun loadPattern(patternBitmap: Bitmap) {
 		// Load only if pattern hasn't been loaded
 		if (::pattern.isInitialized) return
@@ -36,15 +46,23 @@ object CropHelper {
 		this.pattern = PreprocessHelper.preprocessPattern(this.pattern)
 	}
 
+	/**
+	 * Detect corner points in the image
+	 * @param img image to be processed
+	 * @return corner points
+	 */
 	fun detectCorner(img: Mat): CornerPoints {
 		// If pattern hasn't been loaded, throw exception
 		if (!::pattern.isInitialized) {
 			throw Exception("Pattern not loaded!")
 		}
 
-		val imgGray = img.clone()
+		var imgGray = img.clone()
 		cvtColor(img, imgGray, COLOR_BGR2GRAY)
 
+		// do local normalization here
+		imgGray = localNormalize(imgGray)
+
 		val resultMatrix =
 			Mat(
 				img.height() - pattern.height() + 1,
@@ -69,10 +87,22 @@ object CropHelper {
 		)
 
 		val pointsList: MutableList<PointsAndWeight> = mutableListOf()
-
-		for (i in 0 until resultMatrix.height() step 4) {
-			for (j in 0 until resultMatrix.width() step 4) {
-				pointsList.add(PointsAndWeight(i, j, resultMatrix.get(i, j)[0]))
+		val diagonalLength = (resultMatrix.height().toDouble().pow(2) * resultMatrix.width().toDouble().pow(2)).pow(1 / 2)
+
+		for (i in 0 until resultMatrix.height() step 2) {
+			for (j in 0 until resultMatrix.width() step 2) {
+				pointsList.add(
+					PointsAndWeight(
+						i,
+						j,
+						resultMatrix.get(i, j)[0] *
+							getWeight(
+								(
+									min(i, resultMatrix.height() - i).toDouble().pow(2) * min(j, resultMatrix.width() - j).toDouble().pow(2)
+								).pow(1 / 2) / diagonalLength,
+							),
+					),
+				)
 			}
 		}
 
@@ -81,9 +111,22 @@ object CropHelper {
 		pointsList.forEach {
 			if (needChange == 0) return@forEach
 
-			val corner = nearWhichCorner(it.x, it.y, resultMatrix.height(), resultMatrix.width(), limFrac = 0.1F)
+			val corner = nearWhichCorner(it.x, it.y, resultMatrix.height(), resultMatrix.width(), limFrac = 0.6F)
 			if (corner == -1) return@forEach
 
+			if (it.weight > 0.45) {
+				// Corner not found, throw exception
+				val exceptionMessage =
+					"Not all corners found: {" +
+						(if (needed[0]) "Upper left," else "") +
+						(if (needed[1]) "Upper right," else "") +
+						(if (needed[2]) "Lower right," else "") +
+						(if (needed[3]) "Lower left," else "") +
+						"}"
+				// throw NotFoundException(exceptionMessage);
+				Log.e("Corner", exceptionMessage)
+			}
+
 			if (needed[corner]) {
 				needed[corner] = false
 				needChange--
@@ -117,6 +160,12 @@ object CropHelper {
 		return CornerPoints(upperLeftPoint, upperRightPoint, lowerRightPoint, lowerLeftPoint)
 	}
 
+	/**
+	 * Transform image based on corner points into a rectangle
+	 * @param img image to be processed
+	 * @param points corner points of the image
+	 * @return tilted corrected image
+	 */
 	fun fourPointTransform(
 		img: Mat,
 		points: CornerPoints,
@@ -220,4 +269,56 @@ object CropHelper {
 			else -> -1
 		}
 	}
+
+	/**
+	 * Apply local normalization to an image
+	 * source: https://stackoverflow.com/questions/43240604/python-local-normalization-in-opencv
+	 * @see - https://bigwww.epfl.ch/demo/ip/demos/local-normalization/
+	 *
+	 * @param img input matrix
+	 * @return local normalized img
+	 */
+	private fun localNormalize(img: Mat): Mat {
+		// convert img to CV_32F
+		val gray = Mat()
+		img.convertTo(gray, CvType.CV_32F, 1.0 / 255.0)
+
+		val blur = Mat()
+		Imgproc.GaussianBlur(gray, blur, Size(0.0, 0.0), 2.0, 2.0)
+
+		val num = Mat()
+		Core.subtract(gray, blur, num)
+
+		val numSquared = Mat()
+		Core.multiply(num, num, numSquared)
+		val blur2 = Mat()
+		Imgproc.GaussianBlur(numSquared, blur2, Size(0.0, 0.0), 20.0, 20.0)
+
+		val den = Mat()
+		Core.sqrt(blur2, den)
+
+		val div = Mat()
+		Core.divide(num, den, div)
+
+		Core.normalize(div, div, 0.0, 1.0, Core.NORM_MINMAX)
+
+		// Convert back to uint8
+		val result = Mat()
+		div.convertTo(result, CvType.CV_8U, 255.0)
+
+		return result
+	}
+
+	/**
+	 * Weight for a point
+	 * Far from corner means bigger weight
+	 *
+	 * Smaller weight are more likely to be chosen as corner
+	 *
+	 * @param normDistance distance from nearest corner divided by diagonal
+	 * @return weight
+	 */
+	private fun getWeight(normDistance: Double): Double {
+		return 1 + 1 * normDistance.pow(1.5)
+	}
 }
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/ImageAnnotationHelper.kt b/app/src/main/java/com/k2_9/omrekap/utils/ImageAnnotationHelper.kt
index 7ae0919b1ac5c1853ee617ee699afc7463c54d2e..5aef2454dd5950a3fad16c20be3b13670a5d03ba 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/ImageAnnotationHelper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/ImageAnnotationHelper.kt
@@ -8,7 +8,16 @@ import org.opencv.core.Rect
 import org.opencv.core.Scalar
 import org.opencv.imgproc.Imgproc
 
+/**
+ * Helper class for annotating image with detected objects
+ */
 object ImageAnnotationHelper {
+	/**
+	 * Annotate the corner points of a document
+	 * @param img image to be annotated
+	 * @param cornerPoints corner points of the document
+	 * @return image with annotations
+	 */
 	fun annotateCorner(
 		img: Mat,
 		cornerPoints: CornerPoints,
@@ -21,6 +30,13 @@ object ImageAnnotationHelper {
 		return imgWithAnnotations
 	}
 
+	/**
+	 * Annotate the detected AprilTag
+	 * @param img image to be annotated
+	 * @param cornerPoints corner points of the AprilTag
+	 * @param id ID of the AprilTag
+	 * @return image with annotations
+	 */
 	fun annotateAprilTag(
 		img: Mat,
 		cornerPoints: List<Mat>,
@@ -76,6 +92,13 @@ object ImageAnnotationHelper {
 		return imgWithAnnotations
 	}
 
+	/**
+	 * Annotate the detected vote count in the image gained from template matching
+	 * @param img image to be annotated
+	 * @param cornerPoints corner points of the detected object
+	 * @param contourNumber number of the detected object
+	 * @return image with annotations
+	 */
 	fun annotateTemplateMatchingOMR(
 		img: Mat,
 		cornerPoints: List<Rect>,
@@ -99,6 +122,13 @@ object ImageAnnotationHelper {
 		return imgWithAnnotations
 	}
 
+	/**
+	 * Annotate the detected vote count in the image gained from contour detection
+	 * @param img image to be annotated
+	 * @param cornerPoints corner points of the detected object
+	 * @param contourNumber number of the detected object
+	 * @return image with annotations
+	 */
 	fun annotateContourOMR(
 		img: Mat,
 		cornerPoints: List<MatOfPoint>,
@@ -121,6 +151,13 @@ object ImageAnnotationHelper {
 		return imgWithAnnotations
 	}
 
+	/**
+	 * Annotate the detected OMR result in the image
+	 * @param img image to be annotated
+	 * @param section section of the detected OMR
+	 * @param result result of the detected OMR
+	 * @return image with annotations
+	 */
 	fun annotateOMR(
 		img: Mat,
 		section: Rect,
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/ImageSaveDataHolder.kt b/app/src/main/java/com/k2_9/omrekap/utils/ImageSaveDataHolder.kt
index 2f396779aefa96385121e8ab3ca48b759fc545b0..2e6590e651cd94615fc6c6da12ab9973dc1723bb 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/ImageSaveDataHolder.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/ImageSaveDataHolder.kt
@@ -3,13 +3,24 @@ package com.k2_9.omrekap.utils
 import android.util.Log
 import com.k2_9.omrekap.data.models.ImageSaveData
 
+/**
+ * Decorator for ImageSaveData
+ */
 object ImageSaveDataHolder {
 	private var imageSaveData: ImageSaveData? = null
 
+	/**
+	 * Set the ImageSaveData
+	 * @param data ImageSaveData to be saved
+	 */
 	fun save(data: ImageSaveData) {
 		imageSaveData = data
 	}
 
+	/**
+	 * Get the ImageSaveData
+	 * @return ImageSaveData
+	 */
 	fun get(): ImageSaveData {
 		if (imageSaveData == null) {
 			Log.e("ImageSaveDataHolder", "ImageSaveData is null")
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/PermissionHelper.kt b/app/src/main/java/com/k2_9/omrekap/utils/PermissionHelper.kt
index 70e9154a83d6f9a32ac8eab7e6eeb9993ad334d5..32384e66b55bdce4b69abfacb6383c3f3de209fe 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/PermissionHelper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/PermissionHelper.kt
@@ -6,7 +6,17 @@ import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.content.ContextCompat
 
+/**
+ * Helper class for handling permissions
+ */
 object PermissionHelper {
+	/**
+	 * Request permission from the user
+	 * @param activity activity context
+	 * @param permission permission to be requested
+	 * @param verbose show toast message if permission is denied
+	 * @param operation operation to be executed if permission is granted
+	 */
 	fun requirePermission(
 		activity: AppCompatActivity,
 		permission: String,
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/PreprocessHelper.kt b/app/src/main/java/com/k2_9/omrekap/utils/PreprocessHelper.kt
index 6858fea2f2763f3614b355552d129cb58e959607..cef13167020d753bd20b9bbf11a1bd4c1e6bf181 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/PreprocessHelper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/PreprocessHelper.kt
@@ -9,10 +9,18 @@ import org.opencv.core.Size
 import org.opencv.imgproc.Imgproc
 import java.time.Instant
 
+/**
+ * Helper class for preprocessing image
+ */
 object PreprocessHelper {
 	private const val FINAL_WIDTH = 900.0
 	private const val FINAL_HEIGHT = 1600.0
 
+	/**
+	 * Preprocess the image data
+	 * @param data image data to be preprocessed
+	 * @return preprocessed image data
+	 */
 	fun preprocessImage(data: ImageSaveData): ImageSaveData {
 		// Initialize Mats
 		val mainImageMat = Mat()
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/SaveHelper.kt b/app/src/main/java/com/k2_9/omrekap/utils/SaveHelper.kt
index 793eec738fbf85cd17276948700c49df932fc3c7..e612364b09b79a49c8ac4dd25e15ab1e4cb30eda 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/SaveHelper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/SaveHelper.kt
@@ -19,7 +19,15 @@ import java.text.SimpleDateFormat
 import java.util.Date
 import java.util.Locale
 
+/**
+ * Helper class for saving images and JSON data
+ */
 object SaveHelper {
+	/**
+	 * Save the image and JSON data to the device
+	 * @param context the application context
+	 * @param data the image and JSON data to be saved
+	 */
 	suspend fun save(
 		context: Context,
 		data: ImageSaveData,
@@ -48,6 +56,12 @@ object SaveHelper {
 		}
 	}
 
+	/**
+	 * Convert the selected file URI to a Bitmap
+	 * @param context the application context
+	 * @param selectedFileUri the URI of the selected file
+	 * @return the converted Bitmap
+	 */
 	fun uriToBitmap(
 		context: Context,
 		selectedFileUri: Uri,
@@ -59,11 +73,22 @@ object SaveHelper {
 		return image
 	}
 
+	/**
+	 * Generate a folder name based on the current date and time
+	 * @return the generated folder name
+	 */
 	private fun generateFolderName(): String {
 		val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
 		return sdf.format(Date())
 	}
 
+	/**
+	 * Save the image to the Documents/OMRekap/folderName directory
+	 * @param context the application context
+	 * @param image the image to be saved
+	 * @param folderName the folder name where the image will be saved
+	 * @param fileName the file name of the image
+	 */
 	fun saveImage(
 		context: Context,
 		image: Bitmap,
@@ -79,6 +104,13 @@ object SaveHelper {
 		}
 	}
 
+	/**
+	 * Save the JSON to the Documents/OMRekap/folderName directory
+	 * @param context the application context
+	 * @param data the JSON data to be saved
+	 * @param folderName the folder name where the JSON will be saved
+	 * @param fileName the file name of the JSON
+	 */
 	private fun saveJSON(
 		context: Context,
 		data: Map<String, Int?>,
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/omr/ContourOMRHelper.kt b/app/src/main/java/com/k2_9/omrekap/utils/omr/ContourOMRHelper.kt
index fd04515845871f632a9db5e4e3af06518fbb598e..c60aaea0cfdacaf3a0f5131cce2b206254350625 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/omr/ContourOMRHelper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/omr/ContourOMRHelper.kt
@@ -21,11 +21,21 @@ import kotlin.math.floor
 import kotlin.math.max
 import kotlin.math.sin
 
+/**
+ * Helper for Optical Mark Recognition (OMR) using contours
+ * @param config configuration for the OMR helper
+ */
 class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(config) {
 	private var currentSectionGray: Mat? = null
 	private var currentSectionBinary: Mat? = null
 	public var appContext: Context? = null
 
+	/**
+	 * Create information object about the contour
+	 * @param center center of the contour
+	 * @param size size of the contour
+	 * @return ContourInfo object
+	 */
 	private fun createContourInfo(contour: Mat): ContourInfo {
 		val rect = Imgproc.boundingRect(contour)
 		val centerX = rect.x + rect.width / 2
@@ -33,6 +43,12 @@ class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(c
 		return ContourInfo(Pair(centerX, centerY), Pair(rect.width, rect.height))
 	}
 
+	/**
+	 * Filter contours based on the intensities and return the filtered contour infos
+	 * @param contourInfos list of contour infos
+	 * @param intensities list of intensities
+	 * @return filtered list of contour infos
+	 */
 	private fun getContourInfo(
 		filledContours: List<Mat>,
 		filledIntensities: List<Int>,
@@ -57,6 +73,11 @@ class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(c
 		return filterContourInfos(contourInfos, sortedIntensities.map { it.toDouble() })
 	}
 
+	/**
+	 * Predict the number based on the detected filled circle contours
+	 * @param contours list of filled circle contours
+	 * @return predicted number
+	 */
 	private fun predictForFilledCircle(contours: List<MatOfPoint>): Int {
 		// Predict the number based on the filled circle contours
 
@@ -97,6 +118,11 @@ class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(c
 		return contourInfosToNumbers(contourInfos)
 	}
 
+	/**
+	 * Get the darkest row in the column
+	 * @param colContours list of contours in the column
+	 * @return index of the darkest row
+	 */
 	private fun getDarkestRow(colContours: List<MatOfPoint>): Int? {
 		// Initialize variables to store the darkest row and its intensity
 		var darkestRow: Int? = null
@@ -137,20 +163,36 @@ class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(c
 		return darkestRow
 	}
 
-	private fun getPerfectCircle(x: Double, y: Double, radius: Double): MatOfPoint {
-		val numPoints = 100  // Adjust as needed
+	/**
+	 * Create a perfect circle contour
+	 * @param x x-coordinate of the center
+	 * @param y y-coordinate of the center
+	 * @param radius radius of the circle
+	 * @return perfect circle contour
+	 */
+	private fun getPerfectCircle(
+		x: Double,
+		y: Double,
+		radius: Double,
+	): MatOfPoint {
+		val numPoints = 100 // Adjust as needed
 		val theta = DoubleArray(numPoints) { it * 2 * Math.PI / numPoints }
 		val circleX = DoubleArray(numPoints) { x + radius * cos(theta[it]) }
 		val circleY = DoubleArray(numPoints) { y + radius * sin(theta[it]) }
 
 		val circleContour = MatOfPoint()
 		for (i in 0 until numPoints) {
-			circleContour.push_back(MatOfPoint(Point(circleX[i], circleY[i])));
+			circleContour.push_back(MatOfPoint(Point(circleX[i], circleY[i])))
 		}
 
 		return circleContour
 	}
 
+	/**
+	 * Replace the contour with a perfect circle
+	 * @param contour contour to be replaced
+	 * @return perfect circle contour
+	 */
 	private fun replaceWithPerfectCircle(contour: MatOfPoint): MatOfPoint {
 		val rect = Imgproc.boundingRect(contour)
 		val centroidX = rect.x + rect.width.toDouble() / 2
@@ -160,6 +202,11 @@ class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(c
 		return getPerfectCircle(centroidX, centroidY, radius)
 	}
 
+	/**
+	 * Get the combined number from the darkest rows of each column, given 10 contours for each column
+	 * @param darkestRows list of 10 detected contours for each column
+	 * @return combined number
+	 */
 	private fun compareAll(contours: List<MatOfPoint>): Int {
 		// Sort contours by column and then by row
 		val contoursSorted = contours.sortedBy { Imgproc.boundingRect(it).x }
@@ -189,6 +236,12 @@ class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(c
 		}
 		return getCombinedNumbers(darkestRows.map { it ?: 0 })
 	}
+
+	/**
+	 * Complete missing contours by filling the missing circles
+	 * @param contours list of detected contours
+	 * @return list of completed contours
+	 */
 	private fun completeMissingContours(contours: List<MatOfPoint>): List<MatOfPoint> {
 		val sortedContours = contours.sortedBy { Imgproc.boundingRect(it).y }
 		val columnMap = Array(3) { mutableListOf<MatOfPoint>() }
@@ -283,6 +336,10 @@ class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(c
 		return result
 	}
 
+	/**
+	 * Detect the circles in the OMR section
+	 * @return list of detected contours
+	 */
 	private fun getAllContours(): List<MatOfPoint> {
 		// Find circle contours in cropped OMR section
 		val contours = mutableListOf<MatOfPoint>()
@@ -335,12 +392,16 @@ class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(c
 
 			Utils.matToBitmap(display, bitmap)
 			SaveHelper.saveImage(appContext!!, bitmap, "test", "lol.png")
-
 		}
 
 		return filteredContours
 	}
 
+	/**
+	 * Detect the number for the OMR section
+	 * @param contours list of detected contours
+	 * @return detected number
+	 */
 	override fun detect(section: OMRSection): Int {
 		val omrSectionImage = config.omrCropper.crop(section)
 
@@ -382,12 +443,20 @@ class ContourOMRHelper(private val config: ContourOMRHelperConfig) : OMRHelper(c
 		}
 	}
 
-	// Get Section Position For Annotating Purpose
+	/**
+	 * Get the position of the OMR section
+	 * @param section OMR section
+	 * @return position of the OMR section
+	 */
 	fun getSectionPosition(section: OMRSection): Rect {
 		return config.omrCropper.sectionPosition(section)
 	}
 
-	// Annotating Image For Testing Purpose
+	/**
+	 * Annotate the image with the detected contour
+	 * @param contourNumber detected contour number
+	 * @return annotated image
+	 */
 	fun annotateImage(contourNumber: Int): Bitmap {
 		var annotatedImg = currentSectionGray!!.clone()
 		val contours = getAllContours()
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/omr/OMRConfigDetector.kt b/app/src/main/java/com/k2_9/omrekap/utils/omr/OMRConfigDetector.kt
index 81b7e87d87cc409b0b7ba48873deb4f9c25c5aad..e31612eb5f049c0a02f20ecdc0d4db1641b3cad6 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/omr/OMRConfigDetector.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/omr/OMRConfigDetector.kt
@@ -13,6 +13,9 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import org.opencv.core.Mat
 
+/**
+ * Detector for OMR configuration
+ */
 object OMRConfigDetector {
 	private lateinit var loadedConfig: OMRBaseConfiguration
 	private var job: Job? = null
@@ -20,6 +23,8 @@ object OMRConfigDetector {
 	/**
 	 * Initialize and load the detection configuration data.
 	 * Make sure to run this before detecting configurations
+	 * @param context application context
+	 * @throws Exception if failed to load the configuration
 	 */
 	fun loadConfiguration(context: Context) {
 		if (!this::loadedConfig.isInitialized) {
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/omr/OMRHelper.kt b/app/src/main/java/com/k2_9/omrekap/utils/omr/OMRHelper.kt
index efdbb5eff0a190c90484b056296a60a0b4dc5840..adbf0748ef303e9fb4a0a2c34cdb2d859d7a8712 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/omr/OMRHelper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/omr/OMRHelper.kt
@@ -5,12 +5,29 @@ import com.k2_9.omrekap.data.configs.omr.OMRSection
 import kotlin.math.abs
 import kotlin.math.floor
 
+/**
+ * Helper for Optical Mark Recognition (OMR)
+ * @param config configuration for the OMR helper
+ */
 abstract class OMRHelper(private val config: OMRHelperConfig) {
+	/**
+	 * Information about the contour
+	 * @param center center of the contour
+	 * @param size size of the contour
+	 */
 	class ContourInfo(val center: Pair<Int, Int>, val size: Pair<Int, Int>) {
+		/** Check if the contour is overlapping with another contour
+		 * @param other other contour to check
+		 * @return true if the contour is overlapping with the other contour, false otherwise
+		 */
 		fun isOverlapping(other: ContourInfo): Boolean {
 			return isColumnOverlapping(other) && isRowOverlapping(other)
 		}
 
+		/** Check if the contour is overlapping with another contour horizontally
+		 * @param other other contour to check
+		 * @return true if the contour is overlapping with the other contour horizontally, false otherwise
+		 */
 		fun isColumnOverlapping(other: ContourInfo): Boolean {
 			val x1 = center.first
 			val x2 = other.center.first
@@ -20,6 +37,10 @@ abstract class OMRHelper(private val config: OMRHelperConfig) {
 			return abs(x1 - x2) * 2 < w1 + w2
 		}
 
+		/** Check if the contour is overlapping with another contour vertically
+		 * @param other other contour to check
+		 * @return true if the contour is overlapping with the other contour vertically, false otherwise
+		 */
 		fun isRowOverlapping(other: ContourInfo): Boolean {
 			val y1 = center.second
 			val y2 = other.center.second
@@ -30,13 +51,27 @@ abstract class OMRHelper(private val config: OMRHelperConfig) {
 		}
 	}
 
+	/**
+	 * Error when detecting the filled circles
+	 * @param message error message
+	 */
 	class DetectionError(message: String) : Exception(message)
 
+	/**
+	 * Combine the detected numbers into a single integer
+	 * @param numbers list of detected numbers
+	 * @return combined numbers
+	 */
 	protected fun getCombinedNumbers(numbers: List<Int>): Int {
 		// Combine the detected numbers into a single integer
 		return numbers.joinToString("").toInt()
 	}
 
+	/**
+	 * Convert contour infos to numbers
+	 * @param contourInfos list of contour infos
+	 * @return detected numbers
+	 */
 	protected fun contourInfosToNumbers(contourInfos: List<ContourInfo?>): Int {
 		// Return the detected numbers based on the vertical position of the filled circles for each column
 		if (contourInfos.size != 3) {
@@ -62,6 +97,14 @@ abstract class OMRHelper(private val config: OMRHelperConfig) {
 		return getCombinedNumbers(result)
 	}
 
+	/**
+	 * Filter contour infos:
+	 * remove overlapping contour infos and choose the one with the highest intensity
+	 * automatically assign null to the column with no filled circle
+	 * @param contourInfos list of contour infos
+	 * @param filledIntensities list of filled intensities
+	 * @return filtered contour infos
+	 */
 	protected fun filterContourInfos(
 		contourInfos: List<ContourInfo>,
 		filledIntensities: List<Double>,
diff --git a/app/src/main/java/com/k2_9/omrekap/utils/omr/TemplateMatchingOMRHelper.kt b/app/src/main/java/com/k2_9/omrekap/utils/omr/TemplateMatchingOMRHelper.kt
index d6a481447dd3c0196607dc6daf28611fca841452..1f5e7956b8876f11062ec1a7fc5a402eda6e0068 100644
--- a/app/src/main/java/com/k2_9/omrekap/utils/omr/TemplateMatchingOMRHelper.kt
+++ b/app/src/main/java/com/k2_9/omrekap/utils/omr/TemplateMatchingOMRHelper.kt
@@ -10,11 +10,18 @@ import org.opencv.core.Point
 import org.opencv.core.Rect
 import org.opencv.imgproc.Imgproc
 
+/**
+ * Helper for Optical Mark Recognition (OMR) using template matching
+ * @param config configuration for the OMR helper
+ */
 class TemplateMatchingOMRHelper(private val config: TemplateMatchingOMRHelperConfig) :
 	OMRHelper(config) {
 	private var currentSectionGray: Mat? = null
 	private var currentSectionBinary: Mat? = null
 
+	/** Get the rectangles of the matched template in the current section
+	 * @return list of pairs of rectangles and their similarity scores
+	 */
 	private fun getMatchRectangles(): List<Pair<Rect, Double>> {
 		// Load the template image
 		val template = config.template
@@ -67,6 +74,10 @@ class TemplateMatchingOMRHelper(private val config: TemplateMatchingOMRHelperCon
 		return matchedRectangles
 	}
 
+	/** Get the contour information from the matched rectangles
+	 * @param matchedRectangles list of pairs of rectangles and their similarity scores
+	 * @return pair of list of contour information and list of similarity scores
+	 */
 	private fun getContourInfos(matchedRectangles: List<Pair<Rect, Double>>): Pair<List<ContourInfo>, List<Double>> {
 		// Initialize a set to keep track of added rectangles
 		val addedRectangles = mutableSetOf<Rect>()
@@ -115,6 +126,10 @@ class TemplateMatchingOMRHelper(private val config: TemplateMatchingOMRHelperCon
 		return Pair(sortedContours, sortedSimilarities)
 	}
 
+	/** Annotation for the image with the detected filled circles
+	 * @param contourNumber detected number for the filled circles
+	 * @return annotated image as Bitmap
+	 */
 	fun annotateImage(contourNumber: Int): Bitmap {
 		val annotatedImg = currentSectionGray!!.clone()
 		val matchedRectangles = getMatchRectangles()
@@ -136,6 +151,10 @@ class TemplateMatchingOMRHelper(private val config: TemplateMatchingOMRHelperCon
 		return annotatedImageBitmap
 	}
 
+	/** Detect the filled circles in the section
+	 * @param section the OMR section to detect
+	 * @return detected number for the filled circles
+	 */
 	override fun detect(section: OMRSection): Int {
 		val omrSectionImage = config.omrCropper.crop(section)
 
diff --git a/app/src/main/java/com/k2_9/omrekap/views/activities/PreviewActivity.kt b/app/src/main/java/com/k2_9/omrekap/views/activities/PreviewActivity.kt
index 240353e7fb36bdba05d31a7b1bf4440eb645ca60..bd37f1d804eb529ed06dceffd24edd6f1dcd1d07 100644
--- a/app/src/main/java/com/k2_9/omrekap/views/activities/PreviewActivity.kt
+++ b/app/src/main/java/com/k2_9/omrekap/views/activities/PreviewActivity.kt
@@ -50,7 +50,7 @@ class PreviewActivity : AppCompatActivity() {
 		val bitmapOptions = BitmapFactory.Options()
 		bitmapOptions.inPreferredConfig = Bitmap.Config.ALPHA_8
 		bitmapOptions.inScaled = false
-		val cornerPatternBitmap: Bitmap = BitmapFactory.decodeResource(resources, R.raw.corner_pattern, bitmapOptions)
+		val cornerPatternBitmap: Bitmap = BitmapFactory.decodeResource(resources, R.raw.mark20, bitmapOptions)
 
 		CropHelper.loadPattern(cornerPatternBitmap)
 
diff --git a/app/src/main/res/drawable/template_kotak.png b/app/src/main/res/drawable/template_kotak.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b6d7026d84edab7bbc8b4278f6be4ba4639db1b
Binary files /dev/null and b/app/src/main/res/drawable/template_kotak.png differ
diff --git a/app/src/main/res/raw/mark16.png b/app/src/main/res/raw/mark16.png
new file mode 100644
index 0000000000000000000000000000000000000000..e34e5106f50567a9baecca15a4ba92bd1c58670f
Binary files /dev/null and b/app/src/main/res/raw/mark16.png differ
diff --git a/app/src/main/res/raw/mark20.png b/app/src/main/res/raw/mark20.png
new file mode 100644
index 0000000000000000000000000000000000000000..7c65cbf6ea9c87669d1c11dec0d6d510b7ac3561
Binary files /dev/null and b/app/src/main/res/raw/mark20.png differ
diff --git a/screenshots/screenshot.png b/screenshots/screenshot.png
new file mode 100644
index 0000000000000000000000000000000000000000..32e8c622bc2b037092480c6259c72388b890412b
Binary files /dev/null and b/screenshots/screenshot.png differ