<repo_name>SciEco/IntelliEco<file_sep>/Server/RequestProcess.cpp
#include <string>
#include <string.h>
#include <fstream>
#include <iostream>
#include <windows.h>
#include <list>
#include "json.hpp"

using namespace std;
using namespace nlohmann;

int execute(const string & cmd)
{
    DWORD    dwExitCode = -1;

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
	
    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    char cmd_[100] = {'\0'};
    strcpy(cmd_, cmd.c_str());
	
    // Start the child process. 
    if( !CreateProcess( NULL, // an exe file. 
        cmd_,        // parameter for your exe file. 
        NULL,             // Process handle not inheritable. 
        NULL,             // Thread handle not inheritable. 
        FALSE,            // Set handle inheritance to FALSE. 
        0,                // No creation flags. 
        NULL,             // Use parent's environment block. 
        NULL,             // Use parent's starting directory. 
        &si,              // Pointer to STARTUPINFO structure.
        &pi )             // Pointer to PROCESS_INFORMATION structure.
		) 
    {
        MessageBox(NULL,  "CreateProcess failed.",  "ERROR",NULL );
    }
	
    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );
	
    GetExitCodeProcess(pi.hProcess, & dwExitCode);
	
    // Close process and thread handles. 
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );

    return dwExitCode;
}

list<string> listFiles(const string & dir)
{
    HANDLE hFind;
    WIN32_FIND_DATA findData;
    LARGE_INTEGER size;

    char dir_[100] = {'\0'};
    strcpy(dir_, dir.c_str());
    strcat(dir_, "\\*.*");

    hFind = FindFirstFile(dir_, &findData);
    if (hFind == INVALID_HANDLE_VALUE)
    {
        //cout << "Failed to find first file!\n";
        return list<string>();
    }
    list<string> filenames;
    do
    {
        // 忽略"."和".."两个结果 
        if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0) continue;
        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;    // 是否是目录 
        filenames.emplace_back(findData.cFileName);
    } while (FindNextFile(hFind, &findData));
    return filenames;
}

string getExtName(const string & filename)
{
    string extName;
    auto pos = filename.find_last_of('.');
    if (pos == string::npos) return string();
    return filename.substr(pos + 1);
}

const string RootDir = "C:\\IntelliEco";
const string RecordDir = "C:\\IntelliEco\\record";

int main(int argc, char ** argv)
{
    ifstream usersStream(RootDir + "\\users.json");
    json users;
    usersStream >> users;

    string uuidDir = argv[1];
    ifstream requestStream(uuidDir + string("\\request.json"));
    json request;

    requestStream >> request;
    ofstream responseStream(uuidDir + string("\\response.json"));
    json response;

    string username, authority;
    request["username"].get_to(username);

    if (users.find(username) != users.end())
    {
        if (users[username]["password"] == request["password"])
        {
            users[username]["authority"].get_to(authority);
            response["authority"] = authority;

            cout << authority << ' ' << username << " logged in. \n";

            string requestType;
            request["request"].get_to(requestType);
            if (requestType == "login");
            if (requestType == "upload")
            {
                cout << username << " has uploaded a data sample. \n";

                long long int time = 0;
                double longtitude = 0, latitude = 0;
                string imageFilename, newFilename;
                request["time"].get_to(time);
                request["longtitude"].get_to(longtitude);
                request["latitude"].get_to(latitude);
                request["imageFilename"].get_to(imageFilename);
                int mothCount = execute("MothCount.exe " + string(uuidDir) + "\\" + imageFilename);
                newFilename = to_string(time) + '_' + username;
                json record;
                record["uploader"] = username;
                record["time"] = time;
                record["longtitude"] = longtitude;
                record["latitude"] = latitude;
                record["mothCount"] = mothCount;
                record["imageFilename"] = newFilename + ".jpg";
                ofstream recordStream(RecordDir + "\\" + newFilename + ".json");
                recordStream << record;
                system(("move " + (uuidDir + "\\" + imageFilename) + ' ' + (RecordDir + "\\" + newFilename + ".jpg")).c_str());
                response["mothCount"] = mothCount;
            }
            if (requestType == "refresh")
            {
                cout << username << " requested records. \n";
                
                list<string> filenames = listFiles(RecordDir);
                response["records"] = json::array();
                for (string & filename : filenames)
                {
                    string extName = getExtName(filename);
                    if (extName == "json")
                    {
                        json record;
                        ifstream recordStream(RecordDir + "\\" + filename);
                        recordStream >> record;

                        string uploader;
                        long long int time = 0;
                        double longtitude = 0, latitude = 0;
                        int mothCount = 0;

                        record["uploader"].get_to(uploader);
                        record["time"].get_to(time);
                        record["longtitude"].get_to(longtitude);
                        record["latitude"].get_to(latitude);
                        record["mothCount"].get_to(mothCount);

                        response["records"].push_back(json::object({
                            {"uploader", uploader},
                            {"time", time},
                            {"longtitude", longtitude},
                            {"latitude", latitude},
                            {"mothCount", mothCount}
                        }));
                    }
                }
            }
        }
        else
        {
            response["authority"] = "unauthorised";
            cout << username << " unauthorised. \n";
        }
    }
    else
    {
        response["authority"] = "unauthorised";
        cout << username << " not found. \n";
    }

    responseStream << response.dump(4);
    responseStream.close();

    return 0;
}
<file_sep>/Server/README.md
## 服务端部署

### 概述

智慧生态项目的服务端（下称“服务端”）基于Windows Server，使用FTP协议实现json文件的传输，配合以相关程序对json文件进行的分析，实现对客户端请求的识别、处理和回应。

服务端包括以下模块：

1. FTP模块

2. 请求识别与处理模块

3. 记录模块

### 部署

#### 文件结构

本项目的文件结构如下：

- 根目录（手动创建，路径不允许包括空格）
  
  - 请求识别与处理模块（手动部署）
  
  - users.json（保存个人用户信息）
  
  - buffer文件夹（自动生成）
    
    - 多个以请求的uuid命名的文件夹，文件夹下临时存放具体的请求文件（自动生成）
  
  - record文件夹（自动生成）
    
    - 多对记录文件，每一对包括两个文件名相同的jpg和json文件（自动生成）

每一部分的部署将在下文中具体介绍。

#### FTP模块

本项目的FTP模块可以通过Windows Server原生的FTP功能实现，也可以使用其他FTP工具实现。无论使用哪一种方式，都需要将FTP设为被动模式，并开放一定数量的端口供文件传输使用。FTP的用户只需设定一个，不限制登陆数量，并允许其拥有buffer文件夹的读写权限。该用户应与Android客户端内硬编码的FTP用户一致。注意！该FTP用户可以看作任意一个客户端与服务端联系的通用通行证，与users.json内记录的个人用户并非同一个概念，且不存在联系。

#### 请求识别与处理模块

该模块包括以下程序/动态链接库文件，需要在编译后复制到根目录下：

- RequestScan.py

- RequestProcess.exe

- MothCount.exe

- concrt140.dll

- libgcc_s_seh-1.dll

- libstdc++-6.dll

- libwinpthread-1.dll

- msvcp140.dll

- opencv_world410.dll

- vcruntime140.dll

##### 请求识别模块（RequestScan.py）

该脚本是服务端的入口程序，会不间断地扫描buffer文件夹内出现的新请求，将请求传递给RequestProcess.exe进行进一步处理。处理完成后，该脚本会自动清理buffer内残留的临时文件。

另外，该脚本以Python3编写，如果服务器不存在Python3运行环境，则需要提前安装。具体参见Python官网。

##### 请求处理模块（RequestProcess.exe）

收到请求后，该程序会对请求进行分类处理。所有请求可分为下列三类：

- login（登陆请求）

- upload（上传请求）

- refresh（查看记录请求）

对于每一类请求，该程序都会读取请求信息并生成回应文件，等待客户端下载。其中upload请求涉及图像分析，该程序会进一步调用MothCount.exe获取结果，将结果保存进回应文件返回给客户端并在record内保留记录。

#### 记录模块

记录模块包括个人用户记录users.json和数据记录record文件夹。

users.json内记录了所有个人用户的用户名、密码和权限。目前，各种权限之间没有差异。可以通过后台登陆服务器并参照已有的记录修改users.json中的内容来完成用户的注册和注销等操作。

record文件夹记录所有用户上传的监测数据，不需要手动创建，服务端开始运行后会自动生成。每一个upload请求都会在record文件夹中留下两个文件名相同（文件名包括上传用户和上传时间）的jpg和json文件。其中jpg文件是用户上传的原始监测数据，json文件中则记录了该数据的上传用户、时间、经纬度和MothCount.exe对该数据的分析结果。Android客户端的refresh请求只能获取json文件中的信息，若需要查看原始监测数据图片，则需要通过后台登陆服务器。

### 运行与停止

部署完成后，执行RequestScan.py即可让服务端开始运行。运行中的任何时候，使用Ctrl + C终止RequestScan.py的运行窗口即可停止服务端。
<file_sep>/MothCount/MothCount_Thermal/MothCount-Thermal-CutImage/main.cpp
//
//  main.cpp
//  MothCount-Thermal
//
//  Created by william on 2019/7/30.
//  Copyright © 2019 william. All rights reserved.
//

#include <iostream>
#include <opencv2/opencv.hpp>

//#include "CutPicture.hpp"
#include "CutPicture_Gray.hpp"

using namespace std;
using namespace cv;

int main(int argc, const char * argv[]) {
    //Read an image
    //freopen("/Users/william/out.txt", "w", stdout);
    string s = "/Users/william/Documents/20190703-“智慧生态”小课题/software/GitRepo/IntelliEco/MothCount/moth/ThermalSrc/Gray/IR_019";
    string append;
    
    cin >> append;
    s+=append;
    append = ".jpg";
    s+=append;
    
    Mat image = imread(s);
    //BGR at default...
    if (image.empty()) {
        cerr << "No image input!" << endl;
        return -2;
    }
    
    testImage("shabby src", image);
    //Portait to horizontal
    if (image.cols < image.rows)
    {
        transpose(image, image);
        flip(image, image, 1);
    }
    
    //image lossy compression
    while (image.cols >= 2000)
    {
        pyrDown(image, image, Size(image.cols / 2, image.rows / 2));
    }
    
//    FindBiggest(image, image);
    
    //line(image, <#Point pt1#>, <#Point pt2#>, <#const Scalar &color#>)
//    Mat line(image);
//
    long beginTime = clock();
    CutImage(image, image);
    long endTime = clock();
    cerr << (endTime - beginTime)/1000  << "ms" << endl;
//    int areaSum = col * row;
//    auto get = [&image](int x, int y) { return image.at<uchar>(y, x); };
//    auto get_TripleChannel = [&image](int x,int y) { return image.at<Vec3b>(y, x); };
    
    
//    cvtColor(image, image, CV_BGR2GRAY);
    
//    Canny(image, line, 65, 95);
    
//    cout << int(get_TripleChannel(21,21)[0]) << " " << int(get_TripleChannel(21,21)[1]) << " " << int(get_TripleChannel(21,21)[2]) << endl;
    
//    testImage("shabby", image, true);
//    testImage("shibby", line);
    testImage("shabby res", image);
    return 0;
}
<file_sep>/MothCount/MatToJson/DrawContour.hpp
//
//  main.cpp
//  DrawContour
//
//  Created by william on 2019/7/6.
//  Copyright © 2019 W-Hsu. All rights reserved.
//

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream> //our best loved header
#include <vector> //No matter
#include <cmath> //abs()

using namespace cv;
using namespace std;

const int LinePerEdge = 2;

//Not defining global variables

void testImage(const string WindowName, Mat TestingImage, bool DestroyOrNot = true)
{
    //Show an image on window "winname", then destroy the window when pressing a key.
    namedWindow(WindowName); //Create an window
    imshow(WindowName, TestingImage); //Show the image on the window
    waitKey(); //Waiting for pressing a key
    
    if (DestroyOrNot)
        destroyWindow(WindowName); //Destroy the window
}

void FindBiggest(Mat& src, Mat& dst) //dst stores the countour lines
{
    Mat dst_middle;
    
    Canny( src, dst_middle, 100,150,5); //Edge detection using Canny:
    
    //Dilate : The "Canny" function draws a rather thin line.
    //So dilate the lines to allow the "findContours" function to find it.
    //Much time to DE this BUG
    Mat element = getStructuringElement(MORPH_RECT, Size(3, 3), Point(1, 1));
    dilate(dst_middle, dst_middle, element);
    
    //Get and store the edge of the "white" board:
    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy; //The containers
    
    findContours(dst_middle, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE); //Get the edges
    
    //Find the biggest area then output it into dst:
    vector<vector<Point> > polyContours(contours.size());
    int maxArea = 0;
    for (int index = 0; index < contours.size(); index++)
    {
        if (contourArea(contours[index]) > contourArea(contours[maxArea]))
            maxArea = index;
        approxPolyDP(contours[index], polyContours[index], 10, true);
        //cout << contourArea(contours[index]) << endl;
    }
    
    //cout << endl << contourArea(contours[maxArea]) << endl; //Debugging command : print the size of the max area
    
    dst = Mat::zeros(src.size(), CV_8UC3);
    drawContours(dst, polyContours, maxArea, Scalar(0,0,255/*rand() & 255, rand() & 255, rand() & 255*/), 2);
}

void DetectAndDrawLines(Mat& LineImage, Mat& DstImage)
{
    //To Detect lines:
    vector<Vec2f> lines; //Container which will hold the results of the 1st-time detection
    HoughLines(LineImage, lines, 1, CV_PI/180, 150,0 ,0); //Runs the actual detection
    //USAGE : dst1: Source image; lines: container of line's parameter(rho,theta); 1: precision of rho; CV/PI/180: precision of theta(rad).
    
    //To draw lines:
    
    int upCount=0, downCount=0, leftCount=0, rightCount=0;
    ////cout << LineImage.cols << " " << LineImage.rows << "\n" << endl;
    
    //Go through all the lines
    for( size_t i = 0; i < lines.size(); i++ )
    {
        float rho = lines[i][0], theta = lines[i][1];
        
        //"const int LinePerEdge" lines each edge:
        //Controls the maximum number of lines on each edge.
        if (rho < 0)
        {
            if (rightCount >= LinePerEdge)
                continue;
            else
            {
                rightCount++;
                //cout << "right" << endl;
            }
            
        }
        else if (rho >= 500)
        {
            if (downCount >= LinePerEdge)
                continue;
            else
            {
                downCount++;
                //cout << "down" << endl;
            }
        }
        else
        {
            if (theta >= 0.70)
            {
                if (upCount >= LinePerEdge)
                    continue;
                else
                {
                    upCount++;
                    //cout << "up" << endl;
                }
            }
            else
            {
                if (leftCount >= LinePerEdge)
                    continue;
                else
                {
                    leftCount++;
                    //cout << "left" << endl;
                }
            }
            
        }

        
        Point pt1, pt2; //Using pt1 and pt2 as terminals to draw a segment.
        
        if (theta < 0.001 && theta > -0.001) //If the line is close to vertical
        {
            //double k = -1 * cos(theta)/sin(theta), b = rho/sin(theta);
            double n = -1 * rho/sin(theta) / tan(theta);
            pt1.x = n;
            pt2.x = n;
            pt1.y = 20;
            pt2.y = LineImage.rows - 20;
        }
        else
        {
            double k = -1 * cos(theta)/sin(theta), b = rho/sin(theta);
            //double m = -1 * tan(theta), n = -1 * rho/sin(theta) / tan(theta);
            
            for (int x0=0 ; x0<LineImage.cols ; x0++)
            {
                double y0 = k * x0 + b;
                if (y0 < LineImage.rows && y0 > 0)
                {
                    pt1.x = x0 + 40 * sin(theta);
                    if (k>0)
                        pt1.y = y0 + 40 * abs(cos(theta));
                    else
                        pt1.y = y0 - 40 * abs(cos(theta));
                    break;
                }
            }

            for (int x0 = pt1.x + 1 ; x0<LineImage.cols ; x0++)
            {
                double y0 = k * x0 + b;
                pt2.x = x0 - 40 * sin(theta);
                if (k>0)
                    pt2.y = y0 - 40 * abs(cos(theta));
                else
                    pt2.y = y0 + 40 * abs(cos(theta));
                if (y0 > LineImage.rows || y0 < 0)
                    break;
            }
        }
        
        line(DstImage, pt1, pt2, 255, 5, LINE_8, 0);
    }
}

void GetCountour(Mat& InputImage)
{
    //Define variables
    Mat SourceImg, LineImg;
    Mat& _SourceImg = SourceImg;
    Mat& _LineImg = LineImg;
    
    //Load an image:
    SourceImg = InputImage;
    
    FindBiggest(_SourceImg, _LineImg);
    //IMPORTANT!! Must convert Polypic to GRAYSCALE to (draw lines by using Houghlines)
    //Takes me much time to DE this BUG
    cvtColor(LineImg, LineImg, COLOR_BGR2GRAY);
    DetectAndDrawLines(_LineImg, InputImage);
}

<file_sep>/Android/FTPTest/app/src/main/java/cc/hurrypeng/ftptest/MainActivity.java
package cc.hurrypeng.ftptest;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.google.gson.Gson;

public class MainActivity extends AppCompatActivity {

    SharedPreferences sp;
    SharedPreferences.Editor spEdit;

    LocationManager locationManager;

    EditText editTextServer;
    EditText editTextUsername;
    EditText editTextPassword;
    Button buttonSelectFile;

    ProgressDialog progressDialog;

    final int REQUEST_TAKE_PHOTO = 1;
    Uri imageUri;

    String server;
    String username;
    String password;
    String imageFilePath;
    String imageFilename;

    Gson gson;

    private class FtpTask extends AsyncTask<String, String, String> {
        private String jsonPath;
        private boolean success = false;
        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            progressDialog.setTitle("Packing Data Sample");
            progressDialog.show();

            try {
                long time = System.currentTimeMillis();
                Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                double longtitude = location.getLongitude();
                double latitude = location.getLatitude();
                DataSample dataSample = new DataSample(username, time, longtitude, latitude, imageFilename);
                String json = gson.toJson(dataSample);
                jsonPath = getExternalCacheDir() + "/DataSample.json";
                File jsonFile = new File(jsonPath);
                if (jsonFile.exists()) jsonFile.delete();
                jsonFile.createNewFile();
                FileOutputStream fileOutputStream = new FileOutputStream(jsonFile);
                fileOutputStream.write(json.getBytes());
            }
            catch (SecurityException e)
            {
                e.printStackTrace();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            progressDialog.setTitle("Uploading Data Sample");
        }

        @Override
        protected String doInBackground(String... strings) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    FTPClient ftp = new FTPClient();
                    try {
                        int reply;
                        ftp.connect(server);

                        // After connection attempt, you should check the reply code to verify
                        // success.
                        reply = ftp.getReplyCode();

                        if (!FTPReply.isPositiveCompletion(reply)) {
                            ftp.disconnect();
                            Log.e("TAG", "FTP server refused connection.");
                        }

                        success = ftp.login(username, password);

                        ftp.enterLocalPassiveMode();
                        ftp.setRemoteVerificationEnabled(false);

                        ftp.makeDirectory("/users/" + username);
                        ftp.changeWorkingDirectory("/users/" + username);
                        ftp.setFileType(FTP.BINARY_FILE_TYPE);

                        ftp.storeFile("DataSample.json", new FileInputStream(jsonPath));
                        ftp.storeFile(imageFilename, new FileInputStream(imageFilePath));

                        ftp.makeDirectory("/users/" + username + "/uploadfin");
                        //ftp.retrieveFile("/6.jpeg",new FileOutputStream("/storage/emulated/0/Android/data/cc.hurrypeng.ftptest/files/6.jpg"));
                        // ... // transfer files
                        //ftp.logout();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            }).run();
            return null;
        }

        @Override
        protected void onPostExecute(String s) {
            progressDialog.setTitle("Server Processing");
            progressDialog.dismiss();
            if (!success) Toast.makeText(MainActivity.this, "FTP operation failed! ", Toast.LENGTH_LONG).show();
            else Toast.makeText(MainActivity.this, "FTP upload successful", Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sp = getSharedPreferences("ftp", MODE_PRIVATE);
        spEdit = sp.edit();

        server = sp.getString("server", "");
        username = sp.getString("username", "");
        password = sp.getString("password", "");

        editTextServer = findViewById(R.id.EditTextServer);
        editTextUsername = findViewById(R.id.EditTextUsername);
        editTextPassword = findViewById(R.id.EditTextPassword);
        buttonSelectFile = findViewById(R.id.ButtonSelectFile);

        editTextServer.setText(server);
        editTextUsername.setText(username);
        editTextPassword.setText(password);

        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setMessage("Loading...");
        progressDialog.setCancelable(false);

        gson = new Gson();

        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        if (savedInstanceState != null)
        {
            editTextServer.setText(savedInstanceState.getString("server"));
            editTextUsername.setText(savedInstanceState.getString("username"));
            editTextPassword.setText(savedInstanceState.getString("password"));
        }

        buttonSelectFile.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                server = editTextServer.getText().toString();
                username = editTextUsername.getText().toString();
                password = editTextPassword.getText().toString();

                spEdit.putString("server", server);
                spEdit.putString("username", username);
                spEdit.putString("password", <PASSWORD>);
                spEdit.apply();

                /*
                Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                intent.addCategory(Intent.CATEGORY_OPENABLE);
                startActivityForResult(intent, REQUEST_CHOOSEFILE);
                */

                imageFilePath = getExternalCacheDir() + "/cache.jpg";
                imageFilename = imageFilePath.substring(imageFilePath.lastIndexOf('/') + 1, imageFilePath.length());
                File photo = new File(imageFilePath);
                try {
                    if (photo.exists()) photo.delete();
                    photo.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (Build.VERSION.SDK_INT >= 24) {
                    imageUri = FileProvider.getUriForFile(MainActivity.this,
                            "cc.hurrypeng.ftptest.fileprovider", photo);
                } else {
                    imageUri = Uri.fromFile(photo);
                }

                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                startActivityForResult(intent, REQUEST_TAKE_PHOTO);
            }
        });

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case REQUEST_TAKE_PHOTO: {
                    new FtpTask().execute();
                }
            }
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("server", editTextServer.getText().toString());
        outState.putString("username", editTextUsername.getText().toString());
        outState.putString("password", editTextPassword.getText().toString());
    }
}
<file_sep>/MothCount/BranchCutter/BranchCutter.cpp
// [OBSOLETED]
// It's successful, but just TOO SLOW. 

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
#include <deque>
#include <list>
#include <queue>
#include <math.h>

using namespace std;
using namespace cv;

const string window_capture_name = "Video Capture";

bool Counted[2001][2001] = { false };
bool Checked[2001][2001] = { false };

long long int Repetition = 0;

int dist2(Point & p, Point & q)
{
	return (p.x - q.x)*(p.x - q.x) + (p.y - q.y)*(p.y - q.y);
}

int taxicab(Point & p, Point & q)
{
	return abs(p.x - q.x) + abs(p.y - q.y);
}

class Block
{
public:
	Block();
	void add(Point p);
	int area() const;
	Point getCog();
	int getR2();
	int areaPerR();
	void fill(Mat & mat);

	deque<Point> pixels;
	bool white;
	Point cog;
	int r2;
};

Block::Block()
{
	r2 = 0;
	cog = Point(-1, -1);
}

void Block::add(Point p)
{
	pixels.push_back(p);
}

int Block::area() const
{
	return pixels.size();
}

Point Block::getCog()
{
	if (cog != Point(-1, -1)) return cog;
	int xSum = 0, ySum = 0;
	for (Point & p : pixels) xSum += p.x, ySum += p.y;
	cog.x = xSum / max(1, area()), cog.y = ySum / max(1, area());
	return cog;
}

int Block::getR2()
{
	if (r2) return r2;
	getCog();
	for (Point & p : pixels) r2 = max(r2, dist2(p, cog));
	return r2;
}

int Block::areaPerR()
{
	return 100 * area() / max(1, getR2());
	// Single moth APR falls between 100 and 200
}

void Block::fill(Mat & mat)
{
	for (Point & p : pixels) mat.at<uchar>(p.y, p.x) = white ? 0 : 255;
	return;
}

int main(int argc, char* argv[])
{
	namedWindow(window_capture_name, WINDOW_AUTOSIZE);
	// Trackbars to set thresholds for HSV values

	Mat image, imageHSV, imageThreshold, imageErode;
	image = imread(R"(C:\Users\haora\Documents\visual studio 2015\Projects\IntelliEco\moth\moth1.jpg)", IMREAD_COLOR); // Read the file
	if (image.empty()) // Check for invalid input
	{
		cout << "Could not open or find the image" << std::endl;
		cin.get();
		cin.get();
		return -1;
	}

	// Compress
	while (image.cols >= 2000)
	{
		pyrDown(image, image, Size(image.cols / 2, image.rows / 2));
	}

	// Calculate basic params
	int col = image.cols, row = image.rows;
	int areaSum = col * row;
	auto get = [&imageErode](int x, int y) {return imageErode.at<uchar>(y, x); };

	// Convert to HSV colourspace
	cvtColor(image, imageHSV, COLOR_BGR2HSV);

	// Threshold
	int hSum1 = 0, sSum1 = 0, vSum1 = 0;
	int area1 = 0;
	auto get1 = [&imageHSV](int x, int y) {return imageHSV.at<uchar>(y, x); };
	for (int i = 0; i < col * 3; i += 3) for (int j = 0; j < row; j++)
	{
		if (get1(i, j) >= 20 && get1(i, j) <= 50)
		{
			area1++;
			hSum1 += get1(i, j);
			sSum1 += get1(i + 1, j);
			vSum1 += get1(i + 2, j);
		}
	}
	int hMean = hSum1 / area1;
	inRange(imageHSV, Scalar(hMean - 5, 19, 70), Scalar(hMean + 10, 255, 255), imageThreshold);

	auto get2 = [&imageThreshold](int x, int y) -> uchar & {return imageThreshold.at<uchar>(y, x); };
	auto bfs = [get2, col, row](int x, int y, int tax = 2000)
	{
		if (Checked[x][y]) return Block();
		Block block;
		block.white = (get2(x, y) == 255 ? true : false);
		queue<Point> q;
		q.emplace(x, y);
		while (!q.empty())
		{
			Point p = q.front();

			Repetition++;

			q.pop();
			if (Counted[p.x][p.y]) continue;
			Counted[p.x][p.y] = Checked[p.x][p.y] = true;
			block.add(p);
			if (p.x - 1 >= 0 && taxicab(Point(x, y), Point(p.x - 1, p.y)) <= tax) if (get2(p.x - 1, p.y) == get2(x, y)) q.emplace(p.x - 1, p.y);
			if (p.x + 1 < col && taxicab(Point(x, y), Point(p.x + 1, p.y)) <= tax) if (get2(p.x + 1, p.y) == get2(x, y)) q.emplace(p.x + 1, p.y);
			if (p.y - 1 >= 0 && taxicab(Point(x, y), Point(p.x, p.y - 1)) <= tax) if (get2(p.x, p.y - 1) == get2(x, y)) q.emplace(p.x, p.y - 1);
			if (p.y + 1 < row && taxicab(Point(x, y), Point(p.x, p.y + 1)) <= tax) if (get2(p.x, p.y + 1) == get2(x, y)) q.emplace(p.x, p.y + 1);
		}
		return block;
	};

	list<Block> blocks;
	for (int i = 0; i < col; i++) for (int j = 0; j < row; j++)
	{
		memset(Counted, 0, sizeof(Counted));
		Block block = bfs(i, j, 20);
		//block.areaPerR();
		if (block.areaPerR() <= 64)
		{
			block.fill(imageThreshold);
		}
		//if (block.area()) blocks.emplace_back(block);
	}

	//for (Block & block : blocks) cout << (block.white ? "white" : "black") << "\tarea: " << block.area() << "\tCoG: " << block.getCog().x << ' ' << block.getCog().y << "\tradius: " << block.getR() << "\tAPR: " << block.areaPerR() << '\n';

	
	for (Block & block : blocks)
	{
		if (block.areaPerR() <= 64)
		{
			block.fill(imageThreshold);
		}
	}
	
	imshow(window_capture_name, imageThreshold);

	waitKey();

	return 0;
}
<file_sep>/MothCount/MothCount_Thermal/MothCount_Thermal.cpp
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <list>
#include <queue>
#include <deque>

#include "MothCount-Thermal-CutImage/CutPicture_Gray.hpp"

using namespace cv;
using namespace std;

bool Counted[2001][2001] = { false };

int dist(Point & p, Point & q)
{
	return sqrt((p.x - q.x)*(p.x - q.x) + (p.y - q.y)*(p.y - q.y));
}

class Block
{
public:
	Block();
	void add(Point p);
	long area() const;
	Point getCog();
	int getR();
	int getAPR2();
	void erase(Mat & mat);

	deque<Point> pixels;
	bool white;
	Point cog;
	int r;
};

Block::Block()
{
	r = 0;
	cog = Point(-1, -1);
}

void Block::add(Point p)
{
	pixels.push_back(p);
}

long Block::area() const 
{
	return pixels.size();
}

Point Block::getCog()
{
	if (cog != Point(-1, -1)) return cog;
	int xSum = 0, ySum = 0;
	for (Point & p : pixels)
		xSum += p.x, ySum += p.y;
	cog.x = xSum / area(), cog.y = ySum / area();
	return cog;
}

int Block::getR()
{
	if (r) return r;
	getCog();
	for (Point & p : pixels)
		r = max(r, dist(p, cog));
	return r;
}

int Block::getAPR2()
{
	return 100 * area() / max(1, getR()) / max(1, getR());
	// Single moth APR2 falls between 100 and 200
}

void Block::erase(Mat & mat)
{
	for (Point & p : pixels)
		mat.at<uchar>(p.y, p.x) = white ? 0 : 255;
	return;
}

bool operator<(const Block & a, const Block & b)
{
	return a.area() < b.area();
}

//string Filenames[8] = { "IR_01920.jpg", "IR_01921.jpg", "IR_01922.jpg", "IR_01926.jpg", "IR_01931.jpg", "IR_01938.jpg", "IR_01941.jpg", "IR_01944.jpg" };

int main(int argc, char ** argv)
{
	string filename;
	if (argc > 1) filename = argv[1];
	else
	{
		cout << "Enter the path of the image to be tested: ";
		getline(cin, filename);
	}
	Mat image;
	image = imread(filename, IMREAD_GRAYSCALE); // Read the file
	if (image.empty()) // Check for invalid input
	{
		//cout << "Could not open or find the image" << std::endl;
		//cin.get();
		//cin.get();
		return -2;
	}

	//testImage("Source", image);

	// Compress
	while (image.cols >= 2000)
	{
		pyrDown(image, image, Size(image.cols / 2, image.rows / 2));
	}

	CutImage(image, image);
	//testImage("cut", image);

	// If oriented potrait, then transpose it landscape
	if (image.cols < image.rows)
	{
		transpose(image, image);
		flip(image, image, 1);
	}

	// Calculate basic params
	int col = image.cols, row = image.rows;
	int areaSum = col * row;
	auto get = [&image](int x, int y) {return image.at<uchar>(y, x); };

	// Threshold
	//int greySum = 0;
	//for (int i = 0; i < col; i++) for (int j = 0; j < row; j++) greySum += get(i, j);
	//int greyMean = greySum / areaSum;
	//threshold(image, image, greyMean * 0.75, 255, 0);
	/*
	cvtColor(image, image, COLOR_BGR2HSV);
	{
	Mat temp(image.rows, image.cols, CV_8UC1);
	for (int i = 0; i < image.cols; i++) for (int j = 0; j < image.rows; j++)
	{
	temp.at<uchar>(j, i) = get(3 * i, j);
	}
	image = temp;
	}
	testImage("de-colour", image);
	*/

	// Adaptive Threshold
	adaptiveThreshold(image, image, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 55, 16);

	//testImage("Thresholded", image);

	// BFS algorithm
	auto bfs = [get, col, row](int x, int y)
	{
		if (Counted[x][y])
			return Block();
		Block block;
		block.white = (get(x, y) == 255 ? true : false);
		queue<Point> q;
		q.emplace(x, y);
		while (!q.empty())
		{
			Point p = q.front();
			q.pop();
			if (Counted[p.x][p.y]) continue;
			Counted[p.x][p.y] = true;
			block.add(p);
			if (p.x - 1 >= 0) if (get(p.x - 1, p.y) == get(x, y)) q.emplace(p.x - 1, p.y);
			if (p.x + 1 < col) if (get(p.x + 1, p.y) == get(x, y)) q.emplace(p.x + 1, p.y);
			if (p.y - 1 >= 0) if (get(p.x, p.y - 1) == get(x, y)) q.emplace(p.x, p.y - 1);
			if (p.y + 1 < row) if (get(p.x, p.y + 1) == get(x, y)) q.emplace(p.x, p.y + 1);
		}
		return block;
	};

	// Block search lambda
	list<Block> block_list;
	auto blockSearch = [&block_list, bfs, &col, &row]()
	{
		block_list.clear();
		memset(Counted, 0, sizeof(Counted));
		for (int i = 0; i < col; i++) for (int j = 0; j < row; j++)
		{
			Block block = bfs(i, j);
			if (block.area() != 0)
				block_list.emplace_back(block);
		}
	};

	// Print initial block info 
	//blockSearch();
	//block_list.sort();
	//for (Block & block : block_list)
	//	cout << (block.white ? "white" : "black") << "\tarea: " << block.area() << "\tCoG: " << block.getCog().x << ' ' << block.getCog().y << "\tradius: " << block.getR() << "\tAPR2: " << block.getAPR2() << '\n';

	// Dilate
	Mat element = getStructuringElement(MORPH_RECT, Size(3, 3), Point(1, 1));
	dilate(image, image, element);

	//testImage("Dilated", image);

	//GetCountour(_image);

	// Block search 1
	blockSearch();

	// Erase all tiny blocks and small long black blocks
	for (Block & block : block_list)
	{
		if ((block.area() <= 50)
			|| (block.area() <= 7000 && block.getAPR2() <= 50 && !block.white))
		{
			block.erase(image);
		}
	}

	//testImage("Erased", image);

	// Erode
	//element = getStructuringElement(MORPH_RECT, Size(5, 5), Point(3, 3));
	//erode(image, image, element);

	//testImage("Eroded", image);

	// Block search 2
	blockSearch();

	// Erase all tiny blocks and small white blocks
	for (Block & block : block_list)
	{
		if ((block.area() <= 50)
			|| (block.area() <= 7000 && block.white))
		{
			block.erase(image);
		}
	}

	//testImage("Erased", image);

	// Block search 3
	blockSearch();

	// Print block info
	//block_list.sort();
	//for (Block & block : block_list)
	//	cout << (block.white ? "white" : "black") << "\tarea: " << block.area() << "\tCoG: " << block.getCog().x << ' ' << block.getCog().y << "\tradius: " << block.getR() << "\tAPR2: " << block.getAPR2() << '\n';

	//See if the board exists
	long maximumWhite = 0;
	for (Block & block : block_list)
	{
		if (maximumWhite < block.area() && block.white)
			maximumWhite = block.area();
	}
	if (maximumWhite < 50000) return -1;

	/*
	// Calculate boardArea
	int boardArea = 0;
	for (Block & block : block_list)
	{
	if ((block.area() < 7000 && !block.white) || (block.area() >= 7000 && block.white))
	boardArea += block.area();
	} // TODO: replace 7000 with another variable depending on areaSum


	// Calculate singleMothArea
	int singleMothArea = boardArea * 3.00e-3;
	int minMothArea = singleMothArea / 2;
	*/


	// 632.61 per moth with board area being 427280
	// moth/board = 1.60e-3
	// //cout << mothArea / 65.0 << ' ' << boardArea << '\n';
	int mothArea = 0, approxMothCount = 0;
	for (Block & block : block_list)
		if (!block.white && block.area() < 7000) mothArea += block.area(), approxMothCount++;
	int singleMothArea = mothArea / approxMothCount;

	//cout << "board area: " << boardArea << '\n';
	//cout << "single moth area: " << singleMothArea << '\n';
	//cout << "total moth area: " << mothArea << '\n';

	int mothCount = 0;
	for (Block & block : block_list)
		if (!block.white && block.area() >= singleMothArea / 2) mothCount++;

	cout << mothCount << " moth(s) counted. ";

	cin.get();
	cin.get();

	//testImage("Result", image); // Show our image inside it.

	return mothCount;
	
}
<file_sep>/Android/IntelliEco/app/src/main/java/cc/hurrypeng/intellieco/RecordAdapter.java
package cc.hurrypeng.intellieco;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

class RecordAdapter extends RecyclerView.Adapter {

    private List<DataPack.Record> records;

    static class ViewHolder extends RecyclerView.ViewHolder {

        TextView textViewMothCount;
        TextView textViewRecordInfo;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            textViewMothCount = itemView.findViewById(R.id.TextViewMothCount);
            textViewRecordInfo = itemView.findViewById(R.id.TextViewRecordInfo);
        }
    }

    public RecordAdapter(List<DataPack.Record> records) {
        super();
        this.records = records;
        Collections.reverse(this.records);
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.record_item, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        ViewHolder holder1 = (ViewHolder) holder;
        DataPack.Record record = records.get(position);
        SimpleDateFormat format=new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
        Date date = new Date(record.time);
        String timeString = format.format(date);

        DecimalFormat df = new DecimalFormat("#.0000");

        if (record.mothCount >= 0) {
            holder1.textViewMothCount.setTextColor(0x8A000000);
            holder1.textViewMothCount.setText(String.valueOf(record.mothCount));
        }
        else {
            holder1.textViewMothCount.setTextColor(0xFFFF0000);
            holder1.textViewMothCount.setText("!");
        }
        holder1.textViewRecordInfo.setText("Uploader: " + record.uploader + "\n" +
                                           "Time: " + timeString + '\n' +
                                           "Location: " + df.format(record.longtitude) + ", " + df.format(record.latitude));
    }

    @Override
    public int getItemCount() {
        return records.size();
    }
}
<file_sep>/Android/IntelliEco/app/src/main/java/cc/hurrypeng/intellieco/FTPTask.java
package cc.hurrypeng.intellieco;

import android.os.Handler;
import android.os.Message;

import androidx.annotation.NonNull;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.UUID;

class FTPTask extends Thread {
    final static int RESPONSE_RECV = 1;

    protected File response;
    protected ArrayList<File> sends;

    protected Handler handler;

    protected String server;
    protected String ftpUsername;
    protected String ftpPassword;
    protected String uuid;

    FTPTask(File response, File... send) {
        super();
        this.response = response;
        sends = new ArrayList<File>();
        for (File file : send) sends.add(file);

        handler = new FTPTaskHandler();

        server = "ie.hurrypeng.cc";
        ftpUsername = "IE_User";
        ftpPassword = "<PASSWORD>";
        uuid = UUID.randomUUID().toString().replaceAll("-", "");
    }

    void execute() {
        onPreExecute();
        start();
    }

    void onPreExecute() {
    }

    @Override
    public void run() {
        FTPClient ftp = new FTPClient();
        try {
            int reply;
            ftp.connect(server);
            // After connection attempt, you should check the reply code to verify
            // success.
            reply = ftp.getReplyCode();
            if (!FTPReply.isPositiveCompletion(reply)) {
                ftp.disconnect();
                return;
            }

            if(!ftp.login(ftpUsername, ftpPassword)) {
                return;
            }

            ftp.enterLocalPassiveMode();
            ftp.setRemoteVerificationEnabled(false);

            ftp.makeDirectory(uuid);
            ftp.changeWorkingDirectory(uuid);
            ftp.setFileType(FTP.BINARY_FILE_TYPE);

            for (File file : sends) {
                String filename = file.getName();
                ftp.storeFile(filename, new FileInputStream(file));
            }

            ftp.makeDirectory("request");

            // Wait for response
            while (true)
            {
                if (ftp.removeDirectory("response")) break;

                try {
                    Thread.sleep(1000);
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (response.exists()) response.delete();
            response.createNewFile();

            FileOutputStream responseStream = new FileOutputStream(response);
            ftp.retrieveFile("response.json", responseStream);
            responseStream.close();

            ftp.makeDirectory("receive");

            ftp.disconnect();

            Message message = new Message();
            message.what = RESPONSE_RECV;
            handler.sendMessage(message);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    class FTPTaskHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);

            switch (msg.what) {
                case FTPTask.RESPONSE_RECV: {
                    onPostExecute();
                    break;
                }
            }
        }
    }

    void onPostExecute() {
    }
}
<file_sep>/MothCount/ThresholdTest/ThresholdTest.cpp
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>

using namespace std;
using namespace cv;

const int max_value_H = 360 / 2;
const int max_value = 255;
const String window_capture_name = "Video Capture";
const String window_detection_name = "Object Detection";
int low_H = 0, low_S = 0, low_V = 0;
int high_H = max_value_H, high_S = max_value, high_V = max_value;

Mat frame, frame_HSV, frame_threshold;

static void on_low_H_thresh_trackbar(int, void *)
{
	low_H = min(high_H - 1, low_H);
	setTrackbarPos("Low H", window_detection_name, low_H);
	inRange(frame_HSV, Scalar(low_H, low_S, low_V), Scalar(high_H, high_S, high_V), frame_threshold);
	imshow(window_capture_name, frame_threshold);
	imshow(window_detection_name, frame);
}
static void on_high_H_thresh_trackbar(int, void *)
{
	high_H = max(high_H, low_H + 1);
	setTrackbarPos("High H", window_detection_name, high_H);
	inRange(frame_HSV, Scalar(low_H, low_S, low_V), Scalar(high_H, high_S, high_V), frame_threshold);
	imshow(window_capture_name, frame_threshold);
	imshow(window_detection_name, frame);
}
static void on_low_S_thresh_trackbar(int, void *)
{
	low_S = min(high_S - 1, low_S);
	setTrackbarPos("Low S", window_detection_name, low_S);
	inRange(frame_HSV, Scalar(low_H, low_S, low_V), Scalar(high_H, high_S, high_V), frame_threshold);
	imshow(window_capture_name, frame_threshold);
	imshow(window_detection_name, frame);
}
static void on_high_S_thresh_trackbar(int, void *)
{
	high_S = max(high_S, low_S + 1);
	setTrackbarPos("High S", window_detection_name, high_S);
	inRange(frame_HSV, Scalar(low_H, low_S, low_V), Scalar(high_H, high_S, high_V), frame_threshold);
	imshow(window_capture_name, frame_threshold);
	imshow(window_detection_name, frame);
}
static void on_low_V_thresh_trackbar(int, void *)
{
	low_V = min(high_V - 1, low_V);
	setTrackbarPos("Low V", window_detection_name, low_V);
	inRange(frame_HSV, Scalar(low_H, low_S, low_V), Scalar(high_H, high_S, high_V), frame_threshold);
	imshow(window_capture_name, frame_threshold);
	imshow(window_detection_name, frame);
}
static void on_high_V_thresh_trackbar(int, void *)
{
	high_V = max(high_V, low_V + 1);
	setTrackbarPos("High V", window_detection_name, high_V);
	inRange(frame_HSV, Scalar(low_H, low_S, low_V), Scalar(high_H, high_S, high_V), frame_threshold);
	imshow(window_capture_name, frame_threshold);
	imshow(window_detection_name, frame);
}
int main(int argc, char* argv[])
{
	namedWindow(window_capture_name);
	namedWindow(window_detection_name, WINDOW_AUTOSIZE);
	// Trackbars to set thresholds for HSV values
	createTrackbar("Low H", window_detection_name, &low_H, max_value_H, on_low_H_thresh_trackbar);
	createTrackbar("High H", window_detection_name, &high_H, max_value_H, on_high_H_thresh_trackbar);
	createTrackbar("Low S", window_detection_name, &low_S, max_value, on_low_S_thresh_trackbar);
	createTrackbar("High S", window_detection_name, &high_S, max_value, on_high_S_thresh_trackbar);
	createTrackbar("Low V", window_detection_name, &low_V, max_value, on_low_V_thresh_trackbar);
	createTrackbar("High V", window_detection_name, &high_V, max_value, on_high_V_thresh_trackbar);
	
	frame = imread(R"(C:\Users\haora\Documents\visual studio 2015\Projects\IntelliEco\moth\moth5.jpg)", IMREAD_COLOR); // Read the file

	pyrDown(frame, frame, Size(frame.cols / 2, frame.rows / 2));
	pyrDown(frame, frame, Size(frame.cols / 2, frame.rows / 2));

	// Convert from BGR to HSV colorspace
	cvtColor(frame, frame_HSV, COLOR_BGR2HSV);
	// Detect the object based on HSV Range Values
	// Show the frames

	int hSum = 0, hSum1 = 0, sSum = 0, sSum1 = 0, vSum = 0, vSum1 = 0;
	int col = frame_HSV.cols, row = frame_HSV.rows;
	int areaSum = col * row, area1 = 0;
	auto get = [](int x, int y) {return frame_HSV.at<uchar>(y, x); };
	for (int i = 0; i < col * 3; i += 3) for (int j = 0; j < row; j++)
	{
		// if (j == 0) cout << (int)get(i, j) << ' ' << (int)get(i + 1, j) << ' ' << (int)get(i + 2, j) << '\n';
		
		if (get(i, j) >= 20 && get(i, j) <= 50)
		{
			area1++;
			hSum1 += get(i, j);
			sSum1 += get(i + 1, j);
			vSum1 += get(i + 2, j);
		}

		hSum += get(i, j);
		sSum += get(i + 1, j);
		vSum += get(i + 2, j);
	}
	cout << hSum / areaSum << ' ' << sSum / areaSum << ' ' << vSum / areaSum << '\n';
	cout << hSum1 / area1 << ' ' << sSum1 / area1 << ' ' << vSum1 / area1 << '\n';
	
	waitKey();

	return 0;
}
<file_sep>/Android/FTPTest/app/src/main/java/cc/hurrypeng/ftptest/DataSample.java
package cc.hurrypeng.ftptest;

class DataSample {
    public String username;
    public long time;
    double longtitude;
    double latitude;
    public String imageFilename;

    public DataSample()
    {
        username = null;
        time = 0;
        longtitude = latitude = 0.0;
        imageFilename = null;
    }

    public DataSample(String username, long time, double longtitude, double latitude, String imageFilename)
    {
        this.username = username;
        this.time = time;
        this.longtitude = longtitude;
        this.latitude = latitude;
        this.imageFilename = imageFilename;
    }
}
<file_sep>/Android/IntelliEco/app/src/main/java/cc/hurrypeng/intellieco/RecordActivity.java
package cc.hurrypeng.intellieco;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.gson.Gson;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class RecordActivity extends AppCompatActivity {

    ProgressDialog progressDialog;

    Toolbar toolbar;

    RecyclerView recyclerView;

    FloatingActionButton fab;

    String username;
    String password;

    File request;
    File response;
    File sampleImage;

    SharedPreferences sp;
    SharedPreferences.Editor spEdit;

    long lastWarning;

    LocationManager locationManager;

    final int REQUEST_TAKE_PHOTO = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_record);

        fab = findViewById(R.id.fab);

        progressDialog = new ProgressDialog(this);
        progressDialog.setTitle("Loading");
        progressDialog.setCancelable(false);

        toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        recyclerView = findViewById(R.id.RecyclerViewRecords);

        username = getIntent().getStringExtra("username");
        password = getIntent().getStringExtra("<PASSWORD>");

        request = new File(getExternalCacheDir() + "/request.json");
        response = new File(getExternalCacheDir() + "/response.json");
        sampleImage = new File(getExternalCacheDir() + "/cache.jpg");

        sp = getSharedPreferences("warning", MODE_PRIVATE);
        spEdit = sp.edit();

        lastWarning = sp.getLong("lastWarning", 0);

        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    if (sampleImage.exists()) sampleImage.delete();
                    sampleImage.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                Uri imageUri;
                if (Build.VERSION.SDK_INT >= 24) {
                    imageUri = FileProvider.getUriForFile(RecordActivity.this,
                            "cc.hurrypeng.intellieco.fileprovider", sampleImage);
                } else {
                    imageUri = Uri.fromFile(sampleImage);
                }

                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                startActivityForResult(intent, REQUEST_TAKE_PHOTO);
            }
        });

        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.ActionRefresh: {
                        refresh();
                        break;
                    }
                }
                return false;
            }
        });

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);

        refresh();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_record, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case REQUEST_TAKE_PHOTO: {
                    progressDialog.setMessage("Uploading");
                    progressDialog.show();

                   try {
                       if (request.exists()) request.delete();
                       request.createNewFile();

                       DataPack.UploadRequest uploadRequest = new DataPack.UploadRequest();
                       uploadRequest.username = username;
                       uploadRequest.password = <PASSWORD>;
                       uploadRequest.request = "upload";
                       uploadRequest.time = System.currentTimeMillis();
                       Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                       uploadRequest.longtitude = location.getLongitude();
                       uploadRequest.latitude = location.getLatitude();
                       uploadRequest.imageFilename = "cache.jpg";

                       FileOutputStream requestStream = new FileOutputStream(request);
                       requestStream.write(new Gson().toJson(uploadRequest).getBytes());
                       requestStream.close();

                       new FTPTask(response, request, sampleImage) {
                           @Override
                           void onPostExecute() {
                               super.onPostExecute();
                               progressDialog.dismiss();
                               try {
                                   String responseContent = Util.getContent(response);
                                   if (responseContent.equals("")) {
                                       Snackbar.make(fab, "FTP connection failed! ", Snackbar.LENGTH_LONG).show();
                                   }
                                   DataPack.Response generalResponse = new Gson().fromJson(responseContent, DataPack.Response.class);
                                   if (generalResponse.authority.equals("unauthorised")) Snackbar.make(fab, "Unauthorised! ", Snackbar.LENGTH_LONG).show();
                                   else {
                                       DataPack.UploadResponse uploadResponse = new Gson().fromJson(responseContent, DataPack.UploadResponse.class);
                                       refresh();
                                       if (uploadResponse.mothCount >= 0) {
                                           AlertDialog.Builder dialog = new AlertDialog.Builder(RecordActivity.this);
                                           dialog.setTitle("Analyse Result");
                                           dialog.setMessage(uploadResponse.mothCount + " moth(s) have been detected. ");
                                           dialog.setCancelable(true);
                                           dialog.show();
                                       }
                                   }
                               }
                               catch (IOException e) {
                                   e.printStackTrace();
                               }
                           }
                       }.execute();
                   }
                   catch (SecurityException e) {
                       e.printStackTrace();
                   }
                   catch (Exception e) {
                       e.printStackTrace();
                   }

                    break;
                }
            }
        }
    }

    void refresh() {
        progressDialog.setMessage("Refreshing Record");
        progressDialog.show();

        try {
            if (request.exists()) request.delete();
            request.createNewFile();

            final DataPack.RefreshRequest refreshRequest = new DataPack.RefreshRequest();
            refreshRequest.username = username;
            refreshRequest.password = <PASSWORD>;
            refreshRequest.request = "refresh";

            FileOutputStream requestStream = new FileOutputStream(request);
            requestStream.write(new Gson().toJson(refreshRequest).getBytes());
            requestStream.close();

            new FTPTask(response, request) {
                @Override
                void onPostExecute() {
                    super.onPostExecute();
                    progressDialog.dismiss();
                    try {
                        String responseContent = Util.getContent(response);
                        if (responseContent.equals("")) {
                            Snackbar.make(fab, "FTP connection failed! ", Snackbar.LENGTH_LONG).show();
                        }
                        DataPack.Response generalResponse = new Gson().fromJson(responseContent, DataPack.Response.class);
                        if (generalResponse.authority.equals("unauthorised")) Snackbar.make(fab, "Unauthorised! ", Snackbar.LENGTH_LONG).show();
                        else
                        {
                            DataPack.RefreshResponse refreshResponse = new Gson().fromJson(responseContent, DataPack.RefreshResponse.class);
                            RecordAdapter adapter = new RecordAdapter(refreshResponse.records);
                            recyclerView.setAdapter(adapter);
                            for (DataPack.Record record : refreshResponse.records)
                            {
                                if (record.mothCount < 0 && record.time > lastWarning)
                                {
                                    AlertDialog.Builder dialog = new AlertDialog.Builder(RecordActivity.this);
                                    dialog.setTitle("Warning");
                                    dialog.setMessage("An exceptional record has been detected. It could be a bad sample, or a moth outburst. Please check it out on the server. ");
                                    dialog.setCancelable(true);
                                    dialog.show();
                                    break;
                                }
                            }
                            lastWarning = System.currentTimeMillis();
                            spEdit.putLong("lastWarning", lastWarning);
                            spEdit.apply();
                        }
                    }
                    catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }.execute();
        }
        catch (SecurityException e) {
            e.printStackTrace();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
<file_sep>/Server/RequestScan.py
import os
import time
import subprocess

rootDir = "C:\\IntelliEco"
bufferDir = "C:\\IntelliEco\\buffer"
while True:
    list = os.listdir(bufferDir)
    for i in range(0, len(list)):
        uuid = list[i]
        uuidDir = bufferDir + "\\" + uuid;
        requestDir = uuidDir + "\\request"
        responseDir = uuidDir + "\\response"
        receiveDir = uuidDir + "\\receive"
        if os.path.exists(requestDir):
            print(f"A request has been posted by {uuid}.")
            os.rmdir(requestDir)
            if os.path.exists(responseDir):
                os.rmdir(responseDir)
            subprocess.call(rootDir + "\\RequestProcess.exe " + uuidDir)
            os.remove(uuidDir + "\\request.json")
            os.mkdir(responseDir)
        if os.path.exists(receiveDir):
            print(f"A response has been fetched by {uuid}.")
            os.rmdir(receiveDir)
            os.remove(uuidDir + "\\response.json")
            os.rmdir(uuidDir)
            
    time.sleep(0.1)
<file_sep>/MothCount/MatToJson/MatToJson.cpp
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <fstream>
#include <list>
#include <queue>
#include <deque>

#include "DrawContour.hpp"
#include "json.hpp"

using namespace cv;
using namespace nlohmann;
using namespace std;

bool Counted[2001][2001] = { false };

int dist(Point & p, Point & q)
{
	return sqrt((p.x - q.x)*(p.x - q.x) + (p.y - q.y)*(p.y - q.y));
}

class Block
{
public:
	Block();
	void add(Point p);
	long area() const;
	Point getCog();
	int getR();
	int getAPR2();
	void erase(Mat & mat);

	deque<Point> pixels;
	bool white;
	Point cog;
	int r;
};

Block::Block()
{
	r = 0;
	cog = Point(-1, -1);
}

void Block::add(Point p)
{
	pixels.push_back(p);
}

long Block::area() const //
{
	return pixels.size();
}

Point Block::getCog()
{
	if (cog != Point(-1, -1)) return cog;
	int xSum = 0, ySum = 0;
	for (Point & p : pixels)
		xSum += p.x, ySum += p.y;
	cog.x = xSum / area(), cog.y = ySum / area();
	return cog;
}

int Block::getR()
{
	if (r) return r;
	getCog();
	for (Point & p : pixels)
		r = max(r, dist(p, cog));
	return r;
}

int Block::getAPR2()
{
	return 100 * area() / max(1, getR()) / max(1, getR());
	// Single moth APR2 falls between 100 and 200
}

void Block::erase(Mat & mat)
{
	for (Point & p : pixels)
		mat.at<uchar>(p.y, p.x) = white ? 0 : 255;
	return;
}

bool operator<(const Block & a, const Block & b)
{
	return a.area() < b.area();
}

int main(int argc, char ** argv)
{
	Mat image;
	Mat &_image = image;
	image = imread(R"(C:\Users\haora\Documents\Visual Studio 2015\Projects\IntelliEco\moth\moth1.2.jpg)", IMREAD_GRAYSCALE); // Read the file

	// Calculate basic params
	int col = image.cols, row = image.rows;
	int areaSum = col * row;
	auto get = [&image](int x, int y) -> uchar & {return image.at<uchar>(y, x); };

	for (int i = 0; i < col; i++) for (int j = 0; j < row; j++) get(i, j) = get(i, j) >= 128 ? 255 : 0;

	// BFS algorithm
	auto bfs = [get, col, row](int x, int y)
	{
		if (Counted[x][y])
			return Block();
		Block block;
		block.white = (get(x, y) == 255 ? true : false);
		queue<Point> q;
		q.emplace(x, y);
		while (!q.empty())
		{
			Point p = q.front();
			q.pop();
			if (Counted[p.x][p.y]) continue;
			Counted[p.x][p.y] = true;
			block.add(p);
			if (p.x - 1 >= 0) if (get(p.x - 1, p.y) == get(x, y)) q.emplace(p.x - 1, p.y);
			if (p.x + 1 < col) if (get(p.x + 1, p.y) == get(x, y)) q.emplace(p.x + 1, p.y);
			if (p.y - 1 >= 0) if (get(p.x, p.y - 1) == get(x, y)) q.emplace(p.x, p.y - 1);
			if (p.y + 1 < row) if (get(p.x, p.y + 1) == get(x, y)) q.emplace(p.x, p.y + 1);
		}
		return block;
	};

	// Block search lambda
	list<Block> block_list;
	auto blockSearch = [&block_list, bfs, &col, &row]()
	{
		block_list.clear();
		memset(Counted, 0, sizeof(Counted));
		for (int i = 0; i < col; i++) for (int j = 0; j < row; j++)
		{
			Block block = bfs(i, j);
			if (block.area() != 0)
				block_list.emplace_back(block);
		}
	};

	ofstream ofs("bitmap.json");
	json js;

	js["col"] = col;
	js["row"] = row;
	for (int i = 0; i < col; i++) for (int j = 0; j < row; j++) js["pixels"].push_back(get(i, j));

	ofs << js;
	ofs.close();

	// Block search 
	blockSearch();

	testImage("Window", image);

	// Print block info
	block_list.sort();
	for (Block & block : block_list) if (block.area() >= 10)
		cout << (block.white ? "white" : "black") << "\tarea: " << block.area() << "\tCoG: " << block.getCog().x << ' ' << block.getCog().y << "\tradius: " << block.getR() << "\tAPR2: " << block.getAPR2() << '\n';

	system("pause");

	return 0;
}
<file_sep>/MothCount/MothCount_Thermal/MothCount-Thermal-CutImage/CutPicture_Gray.hpp
#include <opencv2/highgui.hpp>
#include <list> //No matter
#include <cmath> //abs()

// A milestone! No Mount. Bullshxt added!

void testImage(const std::string WindowName,\
               cv::Mat TestingImage,\
               bool DestroyOrNot = true)
{
    //Show an image on window "winname", then destroy the window when pressing a key.
    cv::namedWindow(WindowName); //Create an window
    cv::imshow(WindowName, TestingImage); //Show the image on the window
    cv::waitKey(); //Waiting for pressing a key
    
    if (DestroyOrNot)
        cv::destroyWindow(WindowName); //Destroy the window
}

void SingleCut_col(cv::Mat & ToBeCut,\
            cv::Mat & Destination,\
            int thresholdCol_mono=50)
{
    //testImage("shabby 0", ToBeCut);
    std::list<int>memcol;
    
    int col=ToBeCut.cols, row=ToBeCut.rows;
    
    for (int i_cols = 0 ; i_cols < col-5 ; i_cols+=4)
    {
        double avg_b=0, avg_g=0, avg_r=0;
        //double avg_b_next=0, avg_g_next=0, avg_r_next=0;
        for (int i_rows = 0 ; i_rows < row ; i_rows++)
        {
            avg_b += ToBeCut.at<uchar>(i_rows, i_cols);
            avg_b -= ToBeCut.at<uchar>(i_rows, i_cols+4);
        }
        
        avg_b = std::abs(avg_b / row);
        
        //std::cerr << avg_b << " " << avg_g << " " << avg_r << std::endl;
        if (avg_b >= thresholdCol_mono)
        {
            memcol.push_back(i_cols);
            //std::cerr << avg_b << "\t" << avg_g << "\t" << avg_r << "\tcolumn:" << i_cols << std::endl;
            //cv::line(ToBeCut, cv::Point(i_cols,0), cv::Point(i_cols,row), cvScalar(255,255,255), 5, cv::LINE_8, 0);
            //i_cols += 4;
        }
        else;
    }
    
//    testImage("shabby", ToBeCut);
    
    //See if 0 or "col"/"row" is included
    if (memcol.size()>=2)
    {
        if (memcol.front() == 0)
            memcol.pop_front();
        if (memcol.back() == col)
            memcol.pop_back();
    }
    
    //See if found two egdes

    cv::Rect m_select = cv::Rect(memcol.front(), 0, memcol.back()-memcol.front(), row);

    //testImage("shabby 0", ToBeCut);

    Destination = ToBeCut(m_select);

//    testImage("shabby", Destination);
//    std::cerr << std::endl;
}

void SingleCut_row(cv::Mat & ToBeCut,\
                   cv::Mat & Destination,\
                   int thresholdRow_mono=50)
{
    std::list<int>memrow;
    
    int col=ToBeCut.cols, row=ToBeCut.rows;
    
    for (int i_rows = 0 ; i_rows < row-5 ; i_rows+=4)
    {
        double avg_b=0;
        //double avg_b_next=0, avg_g_next=0, avg_r_next=0;
        for (int i_cols = 0 ; i_cols < col ; i_cols++)
        {
            avg_b += ToBeCut.at<uchar>(i_rows, i_cols);
            avg_b -= ToBeCut.at<uchar>(i_rows, i_cols+4);
        }
        
        avg_b = std::abs(avg_b / col);
        
        //std::cerr << avg_b << " " << avg_g << " " << avg_r << std::endl;
        if (avg_b >= thresholdRow_mono)
        {
            memrow.push_back(i_rows);
            //std::cerr << avg_b << "\t" << avg_g << "\t" << avg_r << "\trow:" << i_rows << std::endl;
            //cv::line(ToBeCut, cv::Point(i_rows,0), cv::Point(i_rows,row), cvScalar(255,255,255), 5, cv::LINE_8, 0);
        }
        else;
    }
    
    if (memrow.size()>=2)
    {
        if (memrow.front() == 0)
            memrow.pop_front();
        if (memrow.back() == row)
            memrow.pop_back();
    }
    
    if (memrow.empty())
    {
        memrow.push_back(0);
        memrow.push_back(row);
    }
    else if (memrow.size() == 1)
    {
        memrow.pop_back();
        memrow.push_back(0);
        memrow.push_back(row);
    }
    
    cv::Rect m_select = cv::Rect(0, memrow.front(), col, memrow.back()-memrow.front());
    Destination = ToBeCut(m_select);
}

void CutImage(const cv::Mat & Source,\
              cv::Mat & Destination)
{
    Destination = Source;
    //testImage("Shabby src", Source);
    // Define controlling variables
    bool colContinue = true, rowContinue = true;
    int CutCount_Row=0, CutCount_Col=0;
    // Basic metadata of the image
    int col = Destination.cols, row = Destination.rows;
    // Base Threshold definition
    int monRow=20, monCol=10;
    // Hold the result of last cutting
    // To recover when a ~~failed~~ cut occurs
    cv::Mat Lastime(Destination);
    // Do the cutting job
    // Stop when cut to 3/4 size
    
    // Cut ROWs
    while ((Destination.rows >= row * 3.0 / 4)\
           &&\
           (rowContinue)\
           &&\
           (CutCount_Row <= 200))
    {
        // Do a single cutting in rows
        // When no cutting is done, reduce the threshold
        
        SingleCut_row(Destination, Destination, monRow);
        if (Lastime.rows == Destination.rows)
        {
            monRow--;
        }
        else;
        //        testImage("rows", Destination);
        // To detect a bad cut
        
        
        if (Destination.rows <= row * 0.83)
        {
            Destination = Lastime;
            rowContinue = false;
            monRow = 255;
        }
        
        // Update Last time
        Lastime = Destination;
        CutCount_Row++;
    }
    
    // Column pre-conditioning
    cv::Rect m_select = cv::Rect(Destination.cols*0.20, 0, Destination.cols*0.60, Destination.rows);
    Destination = Destination(m_select);
    Lastime = Destination;
//    testImage("Shabby Middle", Destination);
    
    while (Destination.cols >= col * 0.5\
           &&\
           colContinue\
           &&\
           (CutCount_Col <= 70))
    {
        // Do a single cutting in rows
        // When no cutting is done, reduce the threshold
        SingleCut_col(Destination, Destination, monCol);
        if (Lastime.cols == Destination.cols)
        {
            monCol++;
        }
        else;
        
        // To Detect a bad cut
        if (Destination.cols <= col * 0.30)
        {
            Destination = Lastime;
            colContinue = false;
            monCol = 255;
        }
        
        // Update Last time
        Lastime = Destination;
        CutCount_Col++;
    }

    
    //std::cerr << "CutTimes:" << CutCount_Row << " " << CutCount_Col << " ";
}
