Skip to content

Commit

Permalink
Modified server.js with new API call, logics to run python script; mo…
Browse files Browse the repository at this point in the history
…dified toolbar fetch button to call the new API call; and added python script to server folder
  • Loading branch information
huytruonngg committed Mar 23, 2024
1 parent 7586fe3 commit d94f525
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 67 deletions.
211 changes: 211 additions & 0 deletions server/floor_plan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import cv2
import os
import numpy as np
import pyautogui
import time
from selenium import webdriver
from selenium.webdriver import Firefox, FirefoxOptions
from selenium.webdriver.common.keys import Keys
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By

# Get the current directory
current_dir = os.path.dirname(os.path.abspath(__file__))

# Image file name
image_file = "uploads/floor_plan.png" # Include the 'uploads' directory in the path
output_file = "uploads/output.png" # Include the 'uploads' directory in the path

# Image file path
image_path = os.path.join(current_dir, image_file)
output_path = os.path.join(current_dir, output_file)

# Check if the image file exists
if not os.path.isfile(image_path):
raise FileNotFoundError(f"Image file '{image_file}' not found.")

def getWalls(image_path, threshold_area, canny_threshold1, canny_threshold2):
# Load the image
image = cv2.imread(image_path)

# Convert the image to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Apply thresholding to segment the walls
_, thresholded = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# Apply Canny edge detection to detect lines
edges = cv2.Canny(thresholded, canny_threshold1, canny_threshold2)

# Remove small components or noise from the detected lines
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(edges, connectivity=8)

# Create a mask to filter out small components
mask = np.zeros_like(labels, dtype=np.uint8)
for label in range(1, num_labels):
area = stats[label, cv2.CC_STAT_AREA]
if area > threshold_area:
mask[labels == label] = 255

# Apply the mask to retain only the lines along the walls
result = cv2.bitwise_and(edges, edges, mask=mask)

# Perform morphological dilation to connect adjacent line segments
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
dilated = cv2.dilate(result, kernel, iterations=1)

# Display the resulting image [UNDO TO SEE MID RESULT]
## cv2.imshow('Original Image', image)
## cv2.imshow('Lines along Walls', dilated)
cv2.imwrite(output_path, dilated)

cv2.waitKey(0)
cv2.destroyAllWindows()

def reduction(pathToOutput):
img = cv2.imread(pathToOutput, cv2.IMREAD_GRAYSCALE)

# Set the desired thickness reduction factor
thickness_reduction_factor = 9

# Define the structuring element for erosion
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

# Perform erosion
eroded_image = cv2.erode(img, kernel, iterations=thickness_reduction_factor)

# Display the eroded image [UNDO TO SEE MID RESULT]
## cv2.imshow("Eroded Image", eroded_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite(output_path, eroded_image)

def detect_line_segments(image_path):
# Load the image in grayscale
image = cv2.imread(image_path, 0)

# Apply Canny edge detection
edges = cv2.Canny(image, 50, 150, apertureSize=3)

# Perform Hough Line Transform
lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi / 180, threshold=100, minLineLength=20, maxLineGap=10)

# Process the detected lines
dimensions = []
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
length = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
angle = np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi
dimensions.append((x1, y1, x2, y2, length, angle))

return dimensions

# Call the function to reduce the image to lines and label them
threshold_area = 90
canny_threshold1 = 10
canny_threshold2 = 10
thickness_reduction_factor = 1

# Call the function to remove background elements and retain lines along walls
getWalls(image_path, threshold_area, canny_threshold1, canny_threshold2)

reduction(output_path)

line_segments = detect_line_segments(output_path)

list_of_xcor = []
list_of_ycor = []

for segment in line_segments:
x1, y1, x2, y2, length, angle = segment
print(f"Start Point: ({x1}, {y1}), End Point: ({x2}, {y2}), Length: {length}, Angle: {angle}")
list_of_xcor.append(x1)
list_of_ycor.append(y1)
list_of_xcor.append(x2)
list_of_ycor.append(y2)

list_of_xcor = [int(x) for x in list_of_xcor]
list_of_ycor = [int(y) for y in list_of_ycor]

# Set the downloads directory path
downloads_dir = os.path.join(current_dir, "jsons")
if not os.path.exists(downloads_dir):
os.makedirs(downloads_dir)

# Set up Firefox options
opts = FirefoxOptions()
opts.add_argument("--width=4000")
opts.add_argument("--height=4000")
opts.add_argument('--headless')

# Set Firefox Preferences to specify the custom download directory
opts.set_preference("browser.download.folderList", 2) # Use custom download path
opts.set_preference("browser.download.manager.showWhenStarting", False)
opts.set_preference("browser.download.dir", downloads_dir)
opts.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream") # MIME type

# Create the driver with the custom options
driver = Firefox(options=opts)
driver.get('https://cvdlab.github.io/react-planner/')
actionChains = ActionChains(driver)

def selectWall(i):
cssSelector_OpenMenu = ".toolbar > div:nth-child(4) > div:nth-child(1) > svg:nth-child(1)"
## cssSelector = "#app > div:nth-child(1) > div:nth-child(2) > div:nth-child(4) > svg:nth-child(1) > g:nth-child(2) > g:nth-child(2) > g:nth-child(2) > g:nth-child(1) > rect:nth-child(1)"

button = driver.find_element(By.CSS_SELECTOR, cssSelector_OpenMenu)
actionChains.move_to_element(button).click().perform()
time.sleep(0.20)
if i != 0:
cssSelector_Wall = "#app > div:nth-child(1) > div:nth-child(2) > div:nth-child(4) > div:nth-child(3)"
else:
cssSelector_Wall = "#app > div:nth-child(1) > div:nth-child(2) > div:nth-child(3) > div:nth-child(3)"
button = driver.find_element(By.CSS_SELECTOR, cssSelector_Wall)
actionChains.move_to_element(button).click().perform()


def drawWall(x1, y1, x2, y2):
## CSS Properties of the Grid
cssSelector_Grid = "#app > div:nth-child(1) > div:nth-child(2) > div:nth-child(4) > svg:nth-child(1) > g:nth-child(2) > g:nth-child(2) > g:nth-child(2) > g:nth-child(1) > rect:nth-child(1)"

## Grid Size = (3000, 2000)
## Center = (1500,1000)
Offset_X1 = x1 - 1500
Offset_Y1 = y1 - 1000

Offset_X2 = x2 - 1500
Offset_Y2 = y2 - 1000

button = driver.find_element(By.CSS_SELECTOR, cssSelector_Grid)
actionChains.move_to_element_with_offset(button, Offset_X1, Offset_Y1)
actionChains.click()
actionChains.move_to_element_with_offset(button, Offset_X2, Offset_Y2)
actionChains.click()
actionChains.send_keys(Keys.ESCAPE).perform()

coords = list(map(list, zip(list_of_xcor, list_of_ycor)))

for i in range(0,len(coords),2):
selectWall(i)
time.sleep(0.20)
drawWall(coords[i][0], coords[i][1], coords[i+1][0], coords[i+1][1])

def saveProject():
time.sleep(0.20)
## Properties of the Save Button
cssSelector_Save = ".toolbar > div:nth-child(2) > div:nth-child(1) > svg:nth-child(1) > path:nth-child(1)"
## Find the Save Button
button = driver.find_element(By.CSS_SELECTOR, cssSelector_Save)
## Move to and click the save Button
actionChains.move_to_element(button).click().perform()

## Wait for it to load properly in case
time.sleep(0.20)
## Accept the alert pop-up
driver.switch_to.alert.accept()

saveProject()
time.sleep(1)
print("clicked")
driver.quit()
132 changes: 70 additions & 62 deletions server/server.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,88 @@
const express = require('express');
const cors = require('cors');
const multer = require('multer');
const fs = require('fs');
const express = require("express");
const cors = require("cors");
const multer = require("multer");
const fs = require("fs");
const path = require("path");
const port = 4000;
const { exec } = require("child_process");

// Ensure the uploads directory exists
const uploadsDir = './uploads';
fs.existsSync(uploadsDir) || fs.mkdirSync(uploadsDir);
// Initialize express app
const app = express();

// Enable CORS with predefined options
app.use(
cors({
origin: ["http://localhost:9000", "http://localhost:44463"], // Frontend domains
optionsSuccessStatus: 200,
})
);

// Set storage engine for multer
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadsDir) // Save to the 'uploads' directory
},
destination: "./uploads/",
filename: function (req, file, cb) {
cb(null, file.originalname)
}
// Set a fixed file name
const newFilename = "floor_plan.png";
cb(null, newFilename);
},
});

const fileFilter = (req, file, cb) => {
// Accept JSON files only
if (file.mimetype === 'application/json' || file.originalname.endsWith('.json')) {
// Initialize upload variable with multer configuration
const upload = multer({
storage: storage,
// File filter for PNG files
fileFilter: function (req, file, cb) {
checkFileType(file, cb);
},
}).single("file"); // 'file' is the name attribute in your form

// Check file type to ensure it's a PNG
function checkFileType(file, cb) {
if (file.mimetype === "image/png") {
cb(null, true);
} else {
cb(new Error('Not a JSON file'), false);
cb("Error: Only PNG files are allowed!");
}
};

const upload = multer({ storage: storage, fileFilter: fileFilter });

const app = express();
app.use(cors({
origin: ['http://localhost:9000', 'http://localhost:44463'], // Frontend domains
optionsSuccessStatus: 200
}));

app.post('/upload-json', upload.single('file'), (req, res) => {
if (req.file) {
fs.readFile(req.file.path, 'utf8', (err, data) => {
if (err) {
console.error('Error reading the file:', err);
return res.status(500).json({ message: 'Error reading the file' });
}

// Assuming the file content is a JSON object
const jsonData = JSON.parse(data);
console.log(jsonData); // Print the JSON content to the server console

// Save the JSON data to a single project data file
fs.writeFile('./uploads/project-data.json', data, (writeErr) => {
if (writeErr) {
console.error('Error writing project data:', writeErr);
return res.status(500).json({ message: 'Error writing project data' });
}
}

// Delete the uploaded file after saving project data
fs.unlinkSync(req.file.path);
// Serve static files from the 'uploads' directory
app.use("/uploads", express.static("uploads"));

res.json({ message: 'JSON data received', data: jsonData });
// Route for uploading PNG files
app.post("/upload-png", (req, res) => {
upload(req, res, (err) => {
if (err) {
res.send({
message: err,
});
});
} else {
res.status(400).json({ message: 'No file received or the file is not a JSON file' });
}
} else {
if (req.file == undefined) {
res.send({
message: "Error: No File Selected!",
});
} else {
res.send({
message: "File Uploaded!",
fileInfo: {
filename: req.file.filename,
path: req.file.path,
},
});
}
}
});
});

app.get('/get-project-data', (req, res) => {
// Read the project data from the project data file
fs.readFile('./uploads/project-data.json', 'utf8', (err, data) => {
if (err) {
console.error('Error reading project data:', err);
return res.status(500).json({ message: 'Error reading project data' });
// Route to execute a Python script and return its output
app.get("/process-projects", (req, res) => {
exec("python floor_plan.py", (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return res.status(500).send("Failed to execute Python script.");
}

const jsonData = JSON.parse(data);
res.json(jsonData);
res.send(stdout);
});
});

const PORT = 4000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
// Start the server on the specified port
app.listen(port, () => console.log(`Server started on port ${port}`));
Binary file added server/uploads/floor_plan.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added server/uploads/output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion server/uploads/project-data.json

This file was deleted.

16 changes: 12 additions & 4 deletions src/components/toolbar/toolbar-fetch-button.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ import ToolbarButton from './toolbar-button';

export default function ToolbarFetchButton({ state }, { translator, projectActions }) {


let fetchAndLoadProject = () => {
fetch('http://localhost:4000/get-project-data')
.then(response => response.json())
// Use fetch to get project data from the server
fetch('http://localhost:4000/process-projects')
.then(response => {
// Check if the fetch request was successful
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json(); // Parse JSON response body
})
.then(data => {
// Assuming 'data' contains the generated project data
projectActions.loadProject(data);
// and matches the structure expected by loadProject
// projectActions.loadProject(data);
})
.catch(error => {
// Log or handle errors in fetching or processing the data
console.error('Error fetching and loading project:', error);
});
};
Expand Down

0 comments on commit d94f525

Please sign in to comment.