diff --git a/.gitignore b/.gitignore
index 3041cdd6fdf6d57b130d817efce0261adcbc6467..51fbad32bf30b3c45a40f8900f8619fcf8952b4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -142,3 +142,4 @@ cython_debug/
 # custom
 weights/
 creds.json
+src/creds.py
diff --git a/README.md b/README.md
index 2620539fb913381262bb3767e0c756bbbd2b64c6..ed1529ea1bfb04780ba820a95dfd83aa9a1fcfb7 100644
--- a/README.md
+++ b/README.md
@@ -1,75 +1,131 @@
 # A YOLO-based fruit picking system for FarmBot
+
 ## Brief introduction
+
 https://pjreddie.com/darknet/yolo/
-## YOLO, implemented by Darknet
-Darknet is a deep learning framework created by the author of YOLO. It is written in C and provides nessary Python APIs for object detection. 
 
+## YOLO, implemented by Darknet
 
+Darknet is a deep learning framework created by the author of YOLO. It is
+written in C and provides necessary Python APIs for object detection.
 
 ### Detector Training
-Check the [instruction](https://github.com/AlexeyAB/darknet#how-to-train-to-detect-your-custom-objects) for how to train the YOLO detector.
 
-## Camera Calibration
-Think YOLO as a black box, it consumes photos that contains objects or not and outputs objects' positions in the pixel coordinate. However, we want to know their positions on the planting bed, namely global coordinate. That is why we need camera calibration. This step was done seperately on MATLAB and offered us the intrinsic matrix of the camera. You **do not need to redo** this unless you want to change to a new model of camera. Check MATLAB [instruction](https://ww2.mathworks.cn/help/vision/ug/using-the-single-camera-calibrator-app.html;jsessionid=1a474c11e3e6063620885c4ae708) for how to use the camera calibration app.
+Check the
+[instruction](https://github.com/AlexeyAB/darknet#how-to-train-to-detect-your-custom-objects)
+for how to train the YOLO detector.
 
-**Note**: The result have to be transferred to `struct`, a MATLAB data type, before saving.  
+## Camera Calibration
 
-If you follow the instruction above, you will end up with a MATLAB object, `cameraParameters`. If you save this to a .mat file and read it by Scipy, you will unfortunately end up with an empty dictionary. You should always use the function `toStruct` to convert it to a struct, which is close to Python dictionary, before saving it to a .mat file. Check the function [here](https://ww2.mathworks.cn/help/vision/ref/tostruct.html).
+Think YOLO as a black box, it consumes photos that contains objects or not and
+outputs objects' positions in the pixel coordinate. However, we want to know
+their positions on the planting bed, namely global coordinate. That is why we
+need camera calibration. This step was done separately on MATLAB and offered us
+the intrinsic matrix of the camera. You **do not need to redo** this unless you
+want to change to a new model of camera. Check MATLAB
+[instruction](https://ww2.mathworks.cn/help/vision/ug/using-the-single-camera-calibrator-app.html;jsessionid=1a474c11e3e6063620885c4ae708)
+for how to use the camera calibration app.
+
+**Note**: The result have to be transferred to `struct`, a MATLAB data type,
+before saving.
+
+If you follow the instruction above, you will end up with a MATLAB object,
+`cameraParameters`. If you save this to a .mat file and read it by SciPy, you
+will unfortunately end up with an empty dictionary. You should always use the
+function `toStruct` to convert it to a struct, which is close to Python
+dictionary, before saving it to a .mat file. Check the function
+[here](https://ww2.mathworks.cn/help/vision/ref/tostruct.html).
 
 ## Coordinate Tranform
-Pixel Coordinate $\Rightarrow^{1}$ Camera Coordinate $\Rightarrow^{2}$ Global Coordinate  
-- Pixel coordinate: Output bounding boxes from YOLO in the form of $<x, y, w,h>$. Here we only use $<x, y>$ currently. $w, h$ might be useful for estmating the size of obnjects in the future.
-- Camera coordinate: The coordinate system determined by the camera, whose original point is at the camera pin hole.
-- Gloabl coordinate: The coordinate system for Farmbot. The original point is at the upper right corner of the planting bed.  
 
-Note: Due to mechanical limitation, the orginal point of global coordinate system does not perfectly align with the planting bed's corner.
+Pixel Coordinate $\Rightarrow^{1}$ Camera Coordinate $\Rightarrow^{2}$ Global
+Coordinate
+
+- Pixel coordinate: Output bounding boxes from YOLO in the form of $<x, y,
+  w,h>$. Here we only use $<x, y>$ currently. $w, h$ might be useful for
+  estmating the size of object's in the future.
+- Camera coordinate: The coordinate system determined by the camera, whose
+  original point is at the camera pin hole.
+- Global coordinate: The coordinate system for Farmbot. The original point is at
+  the upper right corner of the planting bed.
+
+Note: Due to mechanical limitation, the original point of global coordinate
+system does not perfectly align with the planting bed's corner.
 
 ### 1. From Pixel to Camera Coordinate
 
 ### 2. From Pixel to Camera Coordinate
-The camera coordinate can be transformed to the gloabl one via a transistion and a rotation. From the picture, we can easily write the formula
-$$
 
-$$ 
+The camera coordinate can be transformed to the global one via a transition and
+a rotation. From the picture, we can easily write the formula
 
 ## Install, Compile
-1. `git clone git@gitlab.control.lth.se:alexp/farmbot`
-2. `make` to download darknet and compile
-3. `conda create --name farm --file requirements.txt`, this will install all required python packages. Change the enviroment name to whatever you like. 
 
-**Note**: I assume conda, a package management software, has been installed before.
+1. `git clone git@gitlab.control.lth.se:alexp/farmbot`
+1. Initialize submodules: `git submodules init && git submodules update`.
+1. Change `LIBSO=0` to `LIBSO=1` in `darknet/Makefile`.
+1. `make` to compile
+1. Install anaconda using [official
+   installer](https://www.anaconda.com/products/distribution) and
+   [instructions](https://docs.conda.io/projects/conda/en/latest/user-guide/install/download.html).
+1. Set [conda-forge](https://conda-forge.org/) as the package repo: `conda config --add channels conda-forge && conda config --set channel_priority strict`
+1. `conda env create --name farm --f environment.yml`, this will install all
+   dependencies. Change the environment name to whatever you like.
+1. Install local package: `pip install -e .`
 
 ### Compile Darknet
-Check the [instruction](https://github.com/AlexeyAB/darknet#how-to-compile-on-linux-using-make) for how to use `Make` to compile on Linux. 
 
-In the top Makefile `LIBSO=1` is set before the Makefile in darknet is run. This will ensure libdarknet.so be generated, which will be used in darknet.py.
+Check the
+[instruction](https://github.com/AlexeyAB/darknet#how-to-compile-on-linux-using-make)
+for how to use `Make` to compile on Linux.
+
+In the top Makefile `LIBSO=1` is set before the Makefile in darknet is run. This
+will ensure libdarknet.so be generated, which will be used in darknet.py.
 
 ## Before starting the system
+
 **Always calibrate the position before using!**  
-The purpose is to reset the zero positions of x, y, z axis. This step should be done manually first and then use the webapp, i.e., the user should push the grantry and z axis to the upper right corner. Check the [official instruction](https://software.farm.bot/v14/FarmBot-Software/how-to-guides/axis-setup) for how to use the web app to set zeros. 
+The purpose is to reset the zero positions of x, y, z axis. This step should be
+done manually first and then use the webapp, i.e., the user should push the
+grantry and z axis to the upper right corner. Check the [official
+instruction](https://software.farm.bot/v14/FarmBot-Software/how-to-guides/axis-setup)
+for how to use the web app to set zeros.
 
 ## How to run this system?
-The software has three main modules: 
+
+The software has three main modules:
+
 1. `move.py`: drive FarmBot to move, take photos, and open/close the gripper
-2. `detection.py`: run darknet as a dynamic library for detecting, output bounding boxes
-3. `location.py`: input bounding boxes, transfer to real-world coordinate  
+2. `detection.py`: run darknet as a dynamic library for detecting, output
+   bounding boxes
+3. `location.py`: input bounding boxes, transfer to real-world coordinate
 
-We also provide `main.py` as a warpper for all the modules above. By runing it, you can make Farmbot automatically conduct the whole process. The three modules can also be run sperately, mostly for debugging purpose.
+We also provide `main.py` as a warpper for all the modules above. By running it,
+you can make Farmbot automatically conduct the whole process. The three modules
+can also be run separately, mostly for debugging purpose.
+
+First go to `farmbot_yolo` and `conda activate <env>` to run the following scripts.
+`<env>` is the same as the one you created in _Install, Compile_
 
-First go to `/src/` and `conda activate <env>` to run the following scripts. `<env>` is the same as the one you created in *Install, Compile*
 ### Move Famrbot, take photos, and open/close the gripper
+
 ### YOLO detection
-All the arguments for file path are set to default. 
+
+All the arguments for file path are set to default.
+
 ```
 python ./detect.py --dont_show --ext_output --save_labels --input ../img --weights ../weights/yolov3-vattenhallen_best.weights  --config_file ../cfg/yolov3-vattenhallen-test.cfg --data_file ../data/vattenhallen.data
 ```
+
 ### Calculate location
+
 ```
 python location.py -v -cam ../static/camera_no_distortion.mat -loc ../img/locations/ -a ../img/annotations -o ../static/distance.txt -l ../log/location.log
 ```
-All the arguments has default values, which means they can be all omitted if you don't change the document tree structure.
 
-### Scan the bed and pick
+All the arguments has default values, which means they can be all omitted if you
+don't change the document tree structure.
 
-重新生成requirement!!
+### Scan the bed and pick
 
+重新生成 requirement!!
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dea26e0b2c5956902abf55b1cf544fc03aa803e2
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,15 @@
+name: false
+channels:
+  - conda-forge
+dependencies:
+  - python==3.8
+  - black
+  - mypy
+  - flake8
+  - opencv
+  - paho-mqtt
+  - numpy
+  - scipy
+  - pandas
+  - requests
+prefix: /local/home/tetov/miniforge3/envs/farm-dev
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..270af2b1e36c18f45e06e835fa944b0305f9d835
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,28 @@
+[build-system]
+requires = ["setuptools>=61.0.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "farmbot_yolo"
+description = "farmbot object identification using YOLO/Darknet"
+version = "0.1.0"
+readme = "README.md"
+license = {text = "TODO"}
+authors = [
+    { name = "Ziliang Xiong", email = "13718722639leo@gmail.com" }
+]
+classifiers = [
+    "Programming Language :: Python :: 3",
+]
+requires-python = ">=3.8"
+dependencies = [
+    "requests",
+    "numpy",
+    "opencv-python",
+    "scipy",
+    "pandas",
+    "paho-mqtt",
+]
+
+[project.optional-dependencies]
+dev = ["black", "mypy", "flake8", "isort"]
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 57c08ca1be2dc2a714502c11c5615bf3dc56bdb5..0000000000000000000000000000000000000000
--- a/requirements.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-# This file may be used to create an environment using:
-# $ conda create --name <env> --file <this file>
-# platform: linux-64
-_libgcc_mutex=0.1=main
-_openmp_mutex=4.5=1_gnu
-blas=1.0=mkl
-bottleneck=1.3.2=py37heb32a55_1
-brotlipy=0.7.0=py37h27cfd23_1003
-bzip2=1.0.8=h7b6447c_0
-ca-certificates=2021.10.26=h06a4308_2
-cairo=1.16.0=hf32fb01_1
-certifi=2021.10.8=py37h06a4308_0
-cffi=1.14.5=py37h261ae71_0
-charset-normalizer=2.0.4=pyhd3eb1b0_0
-cryptography=35.0.0=py37hd23ed53_0
-ffmpeg=4.0=hcdf2ecd_0
-fontconfig=2.13.1=h6c09931_0
-freeglut=3.0.0=hf484d3e_5
-freetype=2.10.4=h5ab3b9f_0
-glib=2.69.1=h5202010_0
-graphite2=1.3.14=h23475e2_0
-h5py=2.8.0=py37h989c5e5_3
-harfbuzz=1.8.8=hffaf4a1_0
-hdf5=1.10.2=hba1933b_1
-icu=58.2=he6710b0_3
-idna=3.2=pyhd3eb1b0_0
-intel-openmp=2021.3.0=h06a4308_3350
-jasper=2.0.14=h07fcdf6_1
-jpeg=9d=h7f8727e_0
-ld_impl_linux-64=2.35.1=h7274673_9
-libffi=3.3=he6710b0_2
-libgcc-ng=9.3.0=h5101ec6_17
-libgfortran-ng=7.5.0=ha8ba4b0_17
-libgfortran4=7.5.0=ha8ba4b0_17
-libglu=9.0.0=hf484d3e_1
-libgomp=9.3.0=h5101ec6_17
-libopencv=3.4.2=hb342d67_1
-libopus=1.3.1=h7b6447c_0
-libpng=1.6.37=hbc83047_0
-libstdcxx-ng=9.3.0=hd4cf53a_17
-libtiff=4.2.0=h85742a9_0
-libuuid=1.0.3=h1bed415_2
-libvpx=1.7.0=h439df22_0
-libwebp-base=1.2.0=h27cfd23_0
-libxcb=1.14=h7b6447c_0
-libxml2=2.9.12=h03d6c58_0
-lz4-c=1.9.3=h295c915_1
-mkl=2021.3.0=h06a4308_520
-mkl-service=2.4.0=py37h7f8727e_0
-mkl_fft=1.3.0=py37h42c9631_2
-mkl_random=1.2.2=py37h51133e4_0
-ncurses=6.2=he6710b0_1
-numexpr=2.7.3=py37h22e1b3c_1
-numpy=1.20.3=py37hf144106_0
-numpy-base=1.20.3=py37h74d4b33_0
-opencv=3.4.2=py37h6fd60c2_1
-openssl=1.1.1l=h7f8727e_0
-paho-mqtt=1.6.1=pypi_0
-pandas=1.3.4=py37h8c16a72_0
-pcre=8.45=h295c915_0
-pip=21.0.1=py37h06a4308_0
-pixman=0.40.0=h7b6447c_0
-py-opencv=3.4.2=py37hb342d67_1
-pycparser=2.21=pyhd3eb1b0_0
-pyopenssl=21.0.0=pyhd3eb1b0_1
-pysocks=1.7.1=py37_1
-python=3.7.11=h12debd9_0
-python-dateutil=2.8.2=pyhd3eb1b0_0
-pytz=2021.3=pyhd3eb1b0_0
-readline=8.1=h27cfd23_0
-requests=2.26.0=pyhd3eb1b0_0
-scipy=1.7.1=py37h292c36d_2
-setuptools=58.0.4=py37h06a4308_0
-six=1.16.0=pyhd3eb1b0_0
-sqlite=3.36.0=hc218d9a_0
-tk=8.6.10=hbc83047_0
-urllib3=1.26.7=pyhd3eb1b0_0
-wheel=0.37.0=pyhd3eb1b0_1
-xz=5.2.5=h7b6447c_0
-zlib=1.2.11=h7b6447c_3
-zstd=1.4.9=haebb681_0
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..606849326a4002007fd42060b51e69a19c18675c
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,3 @@
+from setuptools import setup
+
+setup()