Dharini Baskaran commited on
Commit
1d64201
·
1 Parent(s): 1666228

initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Caches
7
+ .cache/
8
+ .ipynb_checkpoints/
9
+
10
+ # Virtual environment
11
+ venv/
12
+ .env/
13
+ *.env
14
+
15
+ # MacOS system files
16
+ .DS_Store
17
+
18
+ # Model files
19
+ *.pth
20
+ *.ckpt
21
+
22
+ # Streamlit temp files
23
+ .streamlit/
24
+
25
+ # Uploads and results
26
+ rcnn_model/uploads/
27
+ rcnn_model/results/
Dockerfile ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python base image
2
+ FROM python:3.10-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ git \
10
+ build-essential \
11
+ libgl1-mesa-glx \
12
+ libglib2.0-0 \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Install Python dependencies
16
+ COPY requirements.txt .
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Clone and install Detectron2
20
+ RUN pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu
21
+ RUN git clone https://github.com/facebookresearch/detectron2.git && \
22
+ pip install -e detectron2
23
+
24
+ # Copy your app code
25
+ COPY . .
26
+
27
+ # Expose Streamlit default port
28
+ EXPOSE 7860
29
+
30
+ # Streamlit Environment Variables
31
+ ENV STREAMLIT_SERVER_PORT=7860
32
+ ENV STREAMLIT_SERVER_HEADLESS=true
33
+ ENV STREAMLIT_SERVER_ENABLECORS=false
34
+
35
+ # Command to run the app
36
+ CMD ["streamlit", "run", "app.py"]
README.md CHANGED
@@ -1,10 +1,75 @@
 
 
 
 
1
  ---
2
- title: Floorplan Vectorizer
3
- emoji: 📉
4
- colorFrom: pink
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 2D Floorplan Vectorizer
2
+
3
+ A Streamlit web app that allows you to upload 2D floorplan images and automatically vectorize them into COCO-style annotations using a trained Mask R-CNN model.
4
+
5
  ---
6
+
7
+ ## How to Run the App
8
+
9
+ 1. **Clone the repository:**
10
+
11
+ ```bash
12
+ git clone <this-repo-link>
13
+ cd inovonics-ui-vectorizer
14
+ ```
15
+
16
+ 2. **Install the required Python packages:**
17
+
18
+ ```bash
19
+ pip install -r requirements.txt
20
+ ```
21
+
22
+ 3. **Download the pretrained model:**
23
+
24
+ - Download `model_final.pth` from [Google Drive here](https://drive.google.com/file/d/1yr64AOgaYZPTcQzG6cxG6lWBENHR9qjW/view?usp=sharing).
25
+ - Place it inside:
26
+
27
+ ```plaintext
28
+ inovonics-ui-vectorizer/rcnn_model/output/model_final.pth
29
+ ```
30
+
31
+ 4. **Run the app:**
32
+
33
+ ```bash
34
+ streamlit run app.py
35
+ ```
36
+
37
+ 5. Open your browser at [http://localhost:8501](http://localhost:8501) to start using the app!
38
+
39
  ---
40
 
41
+ ## Project Structure
42
+
43
+ ```plaintext
44
+ inovonics-ui-vectorizer/
45
+ ├── app.py # Streamlit frontend app
46
+ ├── public/
47
+ │ └── logo.png # App logo
48
+ ├── rcnn_model/
49
+ │ ├── extraction/ # Extract information from uploaded png image
50
+ │ │ └── annotation_builder.py
51
+ │ │ └── floorplan_sampler.py
52
+ │ │ └── from_labelme_runner.py
53
+ │ │ └── svg_to_json.py
54
+ │ ├── output/ # Empty folder while cloning. Place the pth file here
55
+ │ ├── preprocessing/ # Preprocess the image before sending to model
56
+ │ │ └── cleaning_images.py
57
+ │ │ └── cleaning_single_image.py
58
+ │ │ └── splitting_dataset.py
59
+ │ │ └── svg_to_yolo.py
60
+ │ ├── results/ # Empty folder while cloning. The resulting image and JSON will be stored here
61
+ │ ├── sample/ # Sample images for the model
62
+ │ ├── scripts/ # Model training, evaluation and inference. Streamlit runs the rcnn_run.py file from the frontend
63
+ │ │ └── rcnn_config.py
64
+ │ │ └── rcnn_eval.py
65
+ │ │ └── rcnn_full_tuner.py
66
+ │ │ └── rcnn_run.py
67
+ │ │ └── rcnn_train.py
68
+ │ ├── uploads/ # Temporary folder for streamlit to store the user uploaded image
69
+ │ ├── utils/ # Utility functions during model train and preprocessing
70
+ │ │ └── coco_to_inovonics_json.py
71
+ │ │ └── floorplan_vectorizer_utils.py
72
+ │ │ └── inovonics_ann_builder.py
73
+ ├── README.md # (this file)
74
+ ├── requirements.txt # Python dependencies
75
+ └── .gitignore # Files to ignore during Git commits
app.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import json
3
+ import time
4
+ from PIL import Image
5
+ import os
6
+ import sys
7
+ import gdown
8
+
9
+ st.set_page_config(
10
+ page_title="2D Floorplan Vectorizer",
11
+ layout="wide",
12
+ initial_sidebar_state="collapsed"
13
+ )
14
+
15
+ print("Streamlit App Starting...")
16
+
17
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
18
+
19
+ # Setup Paths
20
+ UPLOAD_DIR = os.path.join(BASE_DIR, "rcnn_model", "uploads")
21
+ MODEL_DIR = os.path.join(BASE_DIR, "rcnn_model", "scripts")
22
+ JSON_DIR = os.path.join(BASE_DIR, "rcnn_model", "results")
23
+ OUTPUT_DIR = os.path.join(BASE_DIR, "rcnn_model", "output")
24
+ SAMPLE_DIR = os.path.join(BASE_DIR, "rcnn_model", "sample")
25
+ logo_path = os.path.join(BASE_DIR, "public", "logo.png")
26
+ model_path = os.path.join(OUTPUT_DIR, "model_final.pth")
27
+
28
+ # Google Drive file download link
29
+ GOOGLE_DRIVE_FILE_ID = "1yr64AOgaYZPTcQzG6cxG6lWBENHR9qjW"
30
+ GDRIVE_URL = f"https://drive.google.com/uc?id={GOOGLE_DRIVE_FILE_ID}"
31
+
32
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
33
+ os.makedirs(JSON_DIR, exist_ok=True)
34
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
35
+
36
+ # DOWNLOAD MODEL IF MISSING
37
+
38
+ if not os.path.exists(model_path):
39
+ print("Model file not found! Downloading from Google Drive...")
40
+ try:
41
+ gdown.download(GDRIVE_URL, model_path, quiet=False)
42
+ print("Model downloaded successfully.")
43
+ except Exception as e:
44
+ print(f"Failed to download model: {e}")
45
+
46
+ sys.path.append(MODEL_DIR)
47
+ from rcnn_model.scripts.rcnn_run import main, write_config
48
+
49
+
50
+ st.markdown(
51
+ """
52
+ <style>
53
+ .stApp { background-color: #FAFAFA; }
54
+ .header-title { font-size: 2.5rem; font-weight: bold; text-align: center;
55
+ background: linear-gradient(to right, #D4ECDD, #EAF4F4);
56
+ color: #2C3E50; padding: 20px; border-radius: 12px; }
57
+ .upload-container { display: flex; flex-direction: column; align-items: center;
58
+ justify-content: center; background: white; padding: 20px;
59
+ border-radius: 10px; box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); }
60
+ .json-container { background: #F5F5F5; padding: 15px; border-radius: 10px;
61
+ font-family: monospace; overflow-y: auto; max-height: 400px;
62
+ white-space: pre-wrap; }
63
+ </style>
64
+ """,
65
+ unsafe_allow_html=True
66
+ )
67
+
68
+ st.image(logo_path, width=250)
69
+ st.markdown("<div class='header-title'>2D Floorplan Vectorizer</div>", unsafe_allow_html=True)
70
+ st.subheader("Upload your Floorplan Image")
71
+ uploaded_file = st.file_uploader("Choose an image", type=["png", "jpg", "jpeg"])
72
+
73
+ if "processing_complete" not in st.session_state:
74
+ st.session_state.processing_complete = False
75
+ if "json_output" not in st.session_state:
76
+ st.session_state.json_output = None
77
+
78
+ col1, col2 = st.columns([1, 2])
79
+
80
+ if uploaded_file is not None:
81
+ print("File Uploaded:", uploaded_file.name)
82
+
83
+ # Save uploaded file
84
+ uploaded_path = os.path.join(UPLOAD_DIR, uploaded_file.name)
85
+ with open(uploaded_path, "wb") as f:
86
+ f.write(uploaded_file.getbuffer())
87
+ print("Uploaded file saved at:", uploaded_path)
88
+
89
+ # Display uploaded image
90
+ with col1:
91
+ st.markdown("<div class='upload-container'>", unsafe_allow_html=True)
92
+ st.image(Image.open(uploaded_path), caption="Uploaded Image", use_container_width=True)
93
+ st.markdown("</div>", unsafe_allow_html=True)
94
+
95
+ with col2:
96
+ if not st.session_state.processing_complete:
97
+ status_placeholder = st.empty()
98
+ status_placeholder.info("⏳ Model is processing the uploaded image...")
99
+ progress_bar = st.progress(0)
100
+ status_text = st.empty()
101
+
102
+ # Run Model
103
+ input_image = uploaded_path
104
+ output_json_name = uploaded_file.name.replace(".png", "_result.json").replace(".jpg", "_result.json").replace(".jpeg", "_result.json")
105
+ output_image_name = uploaded_file.name.replace(".png", "_result.png").replace(".jpg", "_result.png").replace(".jpeg", "_result.png")
106
+
107
+ cfg = write_config()
108
+ print("Model config created. Running model...")
109
+
110
+ # Simulate progress bar
111
+ for i in range(1, 30):
112
+ time.sleep(0.01)
113
+ progress_bar.progress(i)
114
+ status_text.text(f"Preprocessing: {i}%")
115
+
116
+ main(cfg, input_image, output_json_name, output_image_name)
117
+ print("Model run complete.")
118
+
119
+ output_json_path = os.path.join(JSON_DIR, output_json_name)
120
+ output_image_path = os.path.join(JSON_DIR, output_image_name)
121
+
122
+ while not os.path.exists(output_json_path):
123
+ print("Waiting for JSON output...")
124
+ time.sleep(0.5)
125
+
126
+ for i in range(30, 100):
127
+ time.sleep(0.01)
128
+ progress_bar.progress(i)
129
+ status_text.text(f"Postprocessing: {i}%")
130
+
131
+ progress_bar.empty()
132
+ status_text.text("✅ Processing Complete!")
133
+ status_placeholder.success("✅ Model finished and JSON is ready!")
134
+
135
+ # Read generated JSON
136
+ if os.path.exists(output_json_path):
137
+ with open(output_json_path, "r") as jf:
138
+ st.session_state.json_output = json.load(jf)
139
+ print("JSON Output Loaded Successfully.")
140
+ else:
141
+ st.session_state.json_output = {"error": "JSON output not generated."}
142
+ print("JSON output missing.")
143
+
144
+ st.session_state.processing_complete = True
145
+
146
+ out_col1, out_col2 = st.columns(2)
147
+
148
+ with out_col1:
149
+ if os.path.exists(output_image_path):
150
+ st.image(output_image_path, caption="🖼 Output Vectorized Image", use_container_width=True)
151
+
152
+ with open(output_image_path, "rb") as img_file:
153
+ st.download_button(
154
+ label="Download Output Image",
155
+ data=img_file,
156
+ file_name="floorplan_output.png",
157
+ mime="image/png"
158
+ )
159
+
160
+ json_str = json.dumps(st.session_state.json_output, indent=4)
161
+ st.download_button(
162
+ label="Download JSON",
163
+ data=json_str,
164
+ file_name="floorplan_output.json",
165
+ mime="application/json"
166
+ )
167
+ else:
168
+ st.warning("⚠️ Output image not found.")
169
+
170
+ with out_col2:
171
+ st.markdown("<div class='json-container'>", unsafe_allow_html=True)
172
+ st.json(st.session_state.json_output)
173
+ st.markdown("</div>", unsafe_allow_html=True)
174
+
175
+ else:
176
+ st.warning("⚠️ No image uploaded yet.")
177
+ st.session_state.processing_complete = False
packages.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ libgl1-mesa-glx
2
+ libglib2.0-0
public/logo.png ADDED
rcnn_model/__init__.py ADDED
File without changes
rcnn_model/extraction/annotation_builder.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import numpy as np
3
+ from datetime import datetime
4
+
5
+ version_number = "0.0.1"
6
+
7
+ class AnnotationBuilder:
8
+ #creates the base structure of the coco format
9
+ def __init__(self):
10
+ self.licenses = []
11
+ self.categories = [{"id": 0,
12
+ "name": "Wall",
13
+ "supercategory": "none"},
14
+ { "id": 1,
15
+ "name": "Door",
16
+ "supercategory": "none"},
17
+ {"id": 2,
18
+ "name": "Room",
19
+ "supercategory": "none"},
20
+ {"id": 3,
21
+ "name": "Window",
22
+ "supercategory": "none"}
23
+ ]
24
+ self.images = []
25
+ self.annotations = []
26
+
27
+ def set_info(self, description, data_source_name, data_source_url, data_source_creation_date):
28
+ self.info = [{"year":2025,
29
+ "version":version_number,
30
+ "description":description,
31
+ "contributor":data_source_name,
32
+ "url":data_source_url,
33
+ "date_created":data_source_creation_date.strftime("%Y-%m-%dT%H:%M:%S")}]
34
+
35
+ def add_license(self, license_name, license_url):
36
+ self.licenses.append({"id":len(self.licenses),
37
+ "url":license_url,
38
+ "name":license_name})
39
+
40
+ def add_image(self, filename, width, height):
41
+ id = len(self.images)
42
+ self.images.append({"id":id,
43
+ "width":width,
44
+ "height":height,
45
+ "file_name":filename, #filename should be the image path relative to the cocofile's path
46
+ "license":0,
47
+ "date_captured":datetime.now().strftime("%Y-%m-%dT%H:%M:%S")})
48
+ return id
49
+
50
+ def add_annotation(self, image_id, category_id, poly):
51
+ id = len(self.annotations)
52
+ segmentation = np.array(poly.exterior.coords).astype(int).ravel().tolist()[:-2]
53
+ x,y,x2,y2 = tuple(map(int, poly.bounds))
54
+ self.annotations.append({"id":id,
55
+ "image_id":image_id,
56
+ "category_id":category_id,
57
+ "segmentation":[segmentation],
58
+ "area":poly.area,
59
+ "bbox":[x,y,x2-x,y2-y],
60
+ "iscrowd":0})
61
+ return id, poly
62
+
63
+ def final_output(self):
64
+ return {"info":self.info, "licenses":self.licenses, "categories":self.categories, "images":self.images, "annotations":self.annotations}
65
+
66
+ def save_file(self, filepath):
67
+ coco_file = open(filepath,'w')
68
+ json.dump(self.final_output(),coco_file,indent=4)
69
+ coco_file.close()
rcnn_model/extraction/floorplan_sampler.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import copy
4
+ from pycocotools.coco import COCO
5
+ import shapely
6
+ from shapely import geometry
7
+ import sys
8
+ import random
9
+ from datetime import datetime
10
+ from from_root import from_root
11
+ from annotation_builder import AnnotationBuilder as AnnBuild
12
+ import pylab
13
+ pylab.rcParams['figure.figsize'] = (128.0, 160.0)
14
+ from rcnn_model.utils.floorplan_vectorizer_utils import get_image_size, draw_from_coco
15
+
16
+
17
+ # sys.path.append(str(from_root("utils")))
18
+ # from floorplan_vectorizer_utils import get_image_size, draw_from_coco
19
+
20
+
21
+ ### Main functionality ###
22
+
23
+ data_directory_root = str(from_root("dataset/"))+"/"
24
+ category_filter = [2]
25
+ image_sample_room_count_threshold = 4
26
+ min_sample_size = 400
27
+ max_sample_size = 800
28
+ samples_per_image = 30
29
+
30
+ def main():
31
+ sample_from_labelme2coco_dataset("train",data_directory_root+"annotations/","sample_data/","validation_images/")
32
+ sample_from_labelme2coco_dataset("val",data_directory_root+"annotations/","sample_data/","validation_images/")
33
+
34
+
35
+ ### Core Sampling Logic ###
36
+
37
+ #sample from dataset cocofile created by labelme2coco
38
+ #dataset_name should only be "train", "val", or "dataset" based on labelme2coco's output naming conventions
39
+ def sample_from_labelme2coco_dataset(dataset_name,annotation_source_dir,sample_img_dest_dir,validation_img_dest_dir=""):
40
+ #initialize annbuilder
41
+ ann_builder = AnnBuild()
42
+ ann_builder.set_info("manual annotations of Inovonics and university provided data","inovonData","NA",datetime(2019,5,24))
43
+ ann_builder.add_license("TODO", "TODO")
44
+ coco = COCO(annotation_source_dir+dataset_name+".json")
45
+ print("Coco Loaded")
46
+
47
+ #reading
48
+ for img_id in coco.getImgIds():
49
+ take_samples_from_image(ann_builder, img_id, coco, sample_img_dest_dir)
50
+
51
+ #save
52
+ ann_builder.save_file(annotation_source_dir+dataset_name+"_sampled_data.json")
53
+
54
+ #validation images
55
+ if(validation_img_dest_dir != ""):
56
+ validation_coco = COCO(annotation_source_dir+dataset_name+"_sampled_data.json")
57
+ validation_images(dataset_name,validation_coco,validation_img_dest_dir)
58
+
59
+
60
+ def take_samples_from_image(ann_builder, img_id, coco, img_dest):
61
+ #set up image name
62
+ source_img_filename = coco.imgs[img_id]['file_name']
63
+ source_img = cv2.imread(data_directory_root+source_img_filename,cv2.IMREAD_COLOR)
64
+ img_name = source_img_filename[1:-4]
65
+ img_name = img_name[img_name.index("/"):]
66
+ img_name = img_name[1:]
67
+
68
+ #set up mirroring
69
+ mirrored_imgs = [source_img, np.fliplr(source_img), np.flipud(source_img), np.flipud(np.fliplr(source_img))]
70
+ mirror_tags = ["","_h","_v","_hv"]
71
+ print("Processing image "+str(img_id)+": "+img_name)
72
+
73
+ #run sampler
74
+ for m in range(0,len(mirrored_imgs)):
75
+ #load mirror of image
76
+ img = mirrored_imgs[m]
77
+ tag = mirror_tags[m]
78
+ mirrored_anns = mirror_coco_coordinates(coco,img_id,m)
79
+
80
+ if len(mirrored_anns) > 10:
81
+ #collect samples
82
+ for i in range(0,samples_per_image):
83
+ img_dest_path = data_directory_root+img_dest+img_name+tag+"_"+str(i)+".png"
84
+ take_sample(ann_builder, mirrored_anns, img, img_dest_path)
85
+ else:
86
+ img_dest_path = data_directory_root+img_dest+img_name+tag+".png"
87
+ take_full_image(ann_builder, mirrored_anns, img, img_dest_path)
88
+
89
+
90
+ def take_sample(ann_builder, annotations, img, img_dest_path):
91
+ #Take a random sample with at least a certain number of room bounding boxes overlapping
92
+ sample_annotations, cropped, room_count = random_sample_selection(annotations,img)
93
+ while(room_count < image_sample_room_count_threshold):
94
+ sample_annotations, cropped, room_count = random_sample_selection(annotations,img)
95
+
96
+ #sav the cropped image portion of the final sample
97
+ cv2.imwrite(img_dest_path, cropped)
98
+ print(" sample saved to "+img_dest_path)
99
+ sample_width, sample_height = get_image_size(img_dest_path)
100
+ sampled_img_id = ann_builder.add_image(img_dest_path, sample_width, sample_height)
101
+
102
+ #crop annotations
103
+ crop_area = define_crop_area(0,0,sample_width, sample_height)
104
+ add_cropped_annotations(ann_builder, sampled_img_id, sample_annotations, crop_area)
105
+
106
+
107
+ def take_full_image(ann_builder, annotations, img, img_dest_path):
108
+ #sav the cropped image portion of the final sample
109
+ cv2.imwrite(img_dest_path, img)
110
+ print(" whole image saved to "+img_dest_path)
111
+ width, height = get_image_size(img_dest_path)
112
+ img_id = ann_builder.add_image(img_dest_path, width, height)
113
+ for ann in annotations:
114
+ ann_builder.add_annotation(img_id, ann["category_id"], segmentation_to_polygon(ann["segmentation"]))
115
+
116
+
117
+ def random_sample_selection(annotations, img):
118
+ #get bounds of original image
119
+ init_width = len(img)
120
+ init_height = len(img[0])
121
+
122
+ #randomly select a rectangle
123
+ sample_x = random.randrange(0,init_width-min_sample_size-1)
124
+ sample_y = random.randrange(0,init_height-min_sample_size-1)
125
+ sample_width = random.randrange(min_sample_size,min(max_sample_size, init_width-sample_x))
126
+ sample_height = random.randrange(min_sample_size,min(max_sample_size, init_height-sample_y))
127
+
128
+ #create cropped image and offset annotation coordinates immediately for easier data transfer
129
+ cropped = img[sample_y:sample_y+sample_height,sample_x:sample_x+sample_width]
130
+ sampled_annotations, room_count = offset_annotation_coordinates(annotations,sample_x,sample_y,sample_width,sample_height)
131
+
132
+ #return values
133
+ return sampled_annotations, cropped, room_count
134
+
135
+
136
+ ### Annotation Cropping ###
137
+
138
+ def define_crop_area(x, y, width, height):
139
+ cropped_area = geometry.Polygon([(x,y),
140
+ (x+width,y),
141
+ (x+width,y+height),
142
+ (x,y+height)])
143
+ return cropped_area
144
+
145
+
146
+ def add_cropped_annotations(ann_builder, img_id, annotations, cropped_area):
147
+ for ann in annotations:
148
+ #get intersecting area
149
+ poly = crop_polygon(ann["segmentation"],cropped_area,ann)
150
+
151
+ #handle convex rooms that weren't split
152
+ if(isinstance(poly,geometry.Polygon)):
153
+ ann_builder.add_annotation(img_id, ann["category_id"], poly)
154
+
155
+ #handle concave rooms that were split
156
+ elif(isinstance(poly,geometry.GeometryCollection) or isinstance(poly,geometry.MultiPolygon)):
157
+ for subpoly in poly.geoms:
158
+ if(isinstance(subpoly,geometry.Polygon)):
159
+ ann_builder.add_annotation(img_id, ann["category_id"], subpoly)
160
+
161
+
162
+ def crop_polygon(segmentation,crop_area,id):
163
+ #reformat into shapely geometry Polygon
164
+ poly = segmentation_to_polygon(segmentation)
165
+
166
+ #check shape validity (most common error is self overlapping)
167
+ if(not shapely.is_valid(poly)):
168
+ print(id)
169
+ print(shapely.is_valid_reason(poly))
170
+ return None
171
+
172
+ #check size and return
173
+ cropped_poly = shapely.intersection(poly,crop_area)
174
+ if(cropped_poly.area > 0):
175
+ return cropped_poly
176
+ else:
177
+ return None
178
+
179
+ def segmentation_to_polygon(segmentation):
180
+ points = np.array(segmentation[0])
181
+ points = points.reshape(int(len(segmentation[0])/2),2)
182
+ return geometry.Polygon([[p[0], p[1]] for p in points])
183
+
184
+
185
+ ### Applying Geometry Modifications
186
+
187
+ def mirror_coco_coordinates(coco, img_id, mirroring_index):
188
+ #instantiate copy
189
+ original_annotations = coco.imgToAnns[img_id]
190
+ annotations = copy.deepcopy(original_annotations)
191
+ new_annotations = []
192
+
193
+ #get width
194
+ width = coco.imgs[img_id]['width']
195
+ height = coco.imgs[img_id]['height']
196
+
197
+ #apply mirroring
198
+ for ann in annotations:
199
+ if(ann["category_id"] in category_filter):
200
+ for i in range(0,len(ann['bbox'])):
201
+ apply_mirroring_to_coord(ann['bbox'], i, mirroring_index, width, height)
202
+ for i in range(0,len(ann['segmentation'][0])):
203
+ apply_mirroring_to_coord(ann['segmentation'][0], i, mirroring_index, width, height)
204
+ new_annotations.append(ann)
205
+
206
+ return new_annotations
207
+
208
+
209
+ def apply_mirroring_to_coord(array, index, mirroring_index, width, height):
210
+ if(index%2 == 0):
211
+ if(mirroring_index%2==1):
212
+ array[index] = width-array[index]
213
+ else:
214
+ if(mirroring_index>1):
215
+ array[index] = height-array[index]
216
+
217
+
218
+ def offset_annotation_coordinates(original_annotations,x_offset,y_offset,width,height):
219
+ #instantiate copy
220
+ annotations = copy.deepcopy(original_annotations)
221
+ new_annotations = []
222
+ room_count = 0
223
+
224
+ #apply offfset
225
+ for ann in annotations:
226
+ if(check_bounding_box_overlap(ann['bbox'], x_offset, y_offset, width, height)):
227
+ room_count += 1
228
+ for i in range(0,len(ann['bbox'])):
229
+ apply_offset_to_coord(ann['bbox'], i, x_offset, y_offset)
230
+ for i in range(0,len(ann['segmentation'][0])):
231
+ apply_offset_to_coord(ann['segmentation'][0], i, x_offset, y_offset)
232
+ new_annotations.append(ann)
233
+
234
+ return new_annotations, room_count
235
+
236
+
237
+ def apply_offset_to_coord(array, index, x_offset, y_offset):
238
+ if(index%2 == 0):
239
+ array[index] -= x_offset
240
+ else:
241
+ array[index] -= y_offset
242
+
243
+
244
+ def check_bounding_box_overlap(bbox, x_offset, y_offset, width, height):
245
+ boundary_threshold = 25 #so a row of rooms with a one pixel sliver within the area don't get counted
246
+ within_horizontal_bounds = max(bbox[0],bbox[2]) >= x_offset+boundary_threshold and min(bbox[0],bbox[2]) <= x_offset+width-boundary_threshold
247
+ within_vertical_bounds = max(bbox[1],bbox[3]) >= y_offset+boundary_threshold and min(bbox[1],bbox[3]) <= y_offset+height-boundary_threshold
248
+ return within_horizontal_bounds and within_vertical_bounds
249
+
250
+
251
+ ### Validation Display ###
252
+
253
+ def validation_images(dataset_name,coco,validation_img_target_dir):
254
+ count = 1
255
+ for i in np.random.choice(coco.getImgIds(),8):
256
+ draw_from_coco(i,coco,data_directory_root+validation_img_target_dir+dataset_name+"_sampling_validation_"+str(count)+".png")
257
+ count+=1
rcnn_model/extraction/from_labelme_runner.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import labelme2coco
3
+ from PIL import Image
4
+ from from_root import from_root
5
+ import floorplan_sampler
6
+ import json
7
+
8
+
9
+ def main():
10
+ convert_from_labelme()
11
+
12
+ handle_jpeg_files("annotations/train.json")
13
+ handle_jpeg_files("annotations/val.json")
14
+
15
+ print("NOW FOR SAMPLING")
16
+ floorplan_sampler.main()
17
+
18
+ def convert_from_labelme():
19
+ os.chdir(str(from_root("dataset")))
20
+ labelme_source_dir = "labelme_data"
21
+ annotation_dest_dir = "annotations"
22
+ training_split_percentage = .8
23
+ labelme2coco.convert(labelme_source_dir, annotation_dest_dir, training_split_percentage, category_id_start=0)
24
+
25
+
26
+ def handle_jpeg_files(coco_path):
27
+ #open file
28
+ file = open(coco_path,"r+")
29
+ coco = json.load(file)
30
+
31
+ #find and edit jpeg images
32
+ for image in coco["images"]:
33
+ img_name = image["file_name"]
34
+ if(".jpg" in img_name or ".jpeg" in img_name):
35
+ new_img_name = convert_to_png(img_name)
36
+ image["file_name"]=new_img_name
37
+
38
+ #save
39
+ file.seek(0)
40
+ json.dump(coco, file, indent=" ")
41
+ file.close()
42
+
43
+
44
+ def convert_to_png(img_path):
45
+ #load image
46
+ img = Image.open(img_path)
47
+
48
+ #remove .jpg or .jpeg from path
49
+ if(".jpeg" in img_path):
50
+ img_path = img_path[0:-5]
51
+ else:
52
+ img_path = img_path[0:-4]
53
+
54
+ #add .png and save
55
+ img_path += ".png"
56
+ img.save(img_path)
57
+ return img_path
58
+
59
+
60
+
61
+ main()
rcnn_model/extraction/svg_to_json.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from xml.dom import minidom
2
+ import cv2
3
+ import numpy as np
4
+ import math
5
+ from annotation_builder import AnnotationBuilder as AnnBuild
6
+ from pycocotools.coco import COCO
7
+ from shapely import geometry
8
+ from datetime import datetime
9
+ import os
10
+ import sys
11
+ import random
12
+ from from_root import from_root
13
+ from rcnn_model.preprocessing.cleaning_single_image import preprocess_image
14
+ from rcnn_model.utils.floorplan_vectorizer_utils import get_image_size, draw_from_coco
15
+
16
+
17
+
18
+ # sys.path.append(str(from_root("preprocessing")))
19
+ # from cleaning_images import preprocessing
20
+ # sys.path.append(str(from_root("utils")))
21
+ # from floorplan_vectorizer_utils import get_image_size, draw_from_coco
22
+
23
+ ### After running, its split with https://github.com/akarazniewicz/cocosplit
24
+ ### This may or may not be temporary
25
+
26
+ ### Main functionality ###
27
+
28
+ scale_factor = .5
29
+ dataset_root = str(from_root("dataset"))+"/"
30
+
31
+ def main():
32
+ extract_all_cubicasa_anns(True)
33
+
34
+
35
+ def extract_all_cubicasa_anns(export_image=False):
36
+ #initialize annotation builder
37
+ ann_builder = AnnBuild()
38
+ ann_builder.set_info("converted from cubicasa 5k SVG file","cubicasa 5k","https://github.com/cubicasa/cubicasa5k",datetime(2019,5,24))
39
+ ann_builder.add_license("Creative Commons Attribution-NonCommercial 4.0 International License", "http://creativecommons.org/licenses/by-nc/4.0/")
40
+ #iterate through cubicasa files
41
+ for name in os.listdir(str(from_root(dataset_root+"cubicasa_data/"))):
42
+ process_cubicasa_image(ann_builder, name)
43
+ #save data
44
+ print("SAVING TO annotations/cubicasa_coco.json")
45
+ ann_builder.save_file(str(from_root(dataset_root+"annotations/cubicasa_coco.json")))
46
+ if(export_image):
47
+ save_validation_images(str(from_root(dataset_root+"annotations/cubicasa_coco.json")))
48
+
49
+
50
+ def process_cubicasa_image(ann_builder, name):
51
+ #load and preprocess image
52
+ print("\nprocessing "+name)
53
+ source_img_path = str(from_root(dataset_root+"cubicasa_data/"+name+"/F1_scaled.png"))
54
+ processed_img_path = str(from_root(dataset_root+"preprocessed/casa"+name+".png"))
55
+ apply_preprocessing(source_img_path, processed_img_path)
56
+
57
+ #load svg
58
+ source_svg_path = str(from_root(dataset_root+"cubicasa_data/"+name+"/model.svg"))
59
+ print("from "+source_svg_path)
60
+ print("image in "+processed_img_path)
61
+
62
+ #extract data from svg
63
+ try:
64
+ width, height = get_image_size(processed_img_path)
65
+ ann_builder = process_cubicasa(ann_builder, source_svg_path, processed_img_path, width, height)
66
+ except:
67
+ print("ERROR while extracting "+name)
68
+ print(sys.exc_info())
69
+
70
+
71
+ def find_svg(path, name):
72
+ for file in os.listdir(path):
73
+ found_name = file.startswith(name+"_gt_")
74
+ if(found_name):
75
+ found_svg = file.endswith(".svg")
76
+ if(found_svg):
77
+ return path+file
78
+
79
+
80
+ def process_cubicasa(ann_builder, sourve_svg_path, source_img_path, width, height):
81
+ #Get points
82
+ doc = minidom.parse(sourve_svg_path)
83
+ walls = extract_casa_elements_with_id("Wall",doc)
84
+ windows = extract_casa_elements_with_id("Window",doc)
85
+ doors = extract_casa_elements_with_id("Door",doc)
86
+ doc.unlink()
87
+ #export to JSON and potentially imges for visual confirmation that the process works
88
+ ann_builder = export_to_builder_casa(ann_builder,source_img_path,width,height,walls,doors,windows)
89
+ return ann_builder
90
+
91
+
92
+ ### Coco Formatting/Export ###
93
+
94
+ def export_to_builder_casa(ann_builder,source_img,width,height,walls,doors,windows):
95
+ #initialization
96
+ id = ann_builder.add_image(source_img, width, height)
97
+ #walls
98
+ wall_polygons = get_features_from_ann_set(walls)
99
+ door_polygons = get_features_from_ann_set(doors)
100
+ window_polygons = get_features_from_ann_set(windows)
101
+ features = wall_polygons + door_polygons + window_polygons
102
+ rooms = create_rooms_from_features(features, width, height)
103
+ for poly in rooms.geoms:
104
+ ann_builder.add_annotation(id, 2, poly)
105
+ return ann_builder
106
+
107
+
108
+ def get_features_from_ann_set(set, coco = None, image_id = 0, category_id = 0):
109
+ polygons = []
110
+ for points in set:
111
+ poly = geometry.Polygon([[p[0], p[1]] for p in points])
112
+ if(coco is not None):
113
+ coco.add_annotation(image_id, category_id, poly)
114
+ polygons.append(poly)
115
+ return polygons
116
+
117
+
118
+ def create_rooms_from_features(features, width, height):
119
+ room_polygons = geometry.Polygon([(0,0),
120
+ (width,0),
121
+ (width,height),
122
+ (0,height)
123
+ ])
124
+ for poly in features:
125
+ room_polygons = room_polygons.difference(poly,3)
126
+ return geometry.MultiPolygon(room_polygons.geoms[1:]) #this eliminates the exterior from the rooms
127
+
128
+
129
+ def apply_preprocessing(source_path, processed_path):
130
+ img = cv2.imread(source_path)
131
+ small_img = cv2.resize(img, (0,0), fx=scale_factor, fy=scale_factor)
132
+ cv2.imwrite(processed_path,small_img)
133
+ processed_img = preprocess_image(processed_path)
134
+ #small = cv2.resize(processed, (0,0), fx=scale_factor, fy=scale_factor)
135
+ cv2.imwrite(processed_path,processed_img)
136
+ print(get_image_size(source_path))
137
+ print(get_image_size(processed_path))
138
+
139
+
140
+ ### SVG element extraction ###
141
+
142
+ def get_casa_size(doc):
143
+ path = doc.getElementsByTagName('svg')[0]
144
+ return int(float(path.getAttribute('width'))), int(float(path.getAttribute('height')))
145
+
146
+
147
+ def extract_casa_elements_with_id(id, doc):
148
+ elements = []
149
+ for path in doc.getElementsByTagName('g'):
150
+ #iterates through everything and finds items labelled as walls
151
+ if(id in path.getAttribute('id')):
152
+ #luckily, the first attribute after all of these is a polygon containing a list of coordinate points
153
+ string = path.firstChild.getAttribute('points')
154
+ points = points_string_to_int_points(string)
155
+ elements.append(points)
156
+ return elements
157
+
158
+
159
+ ### Helper Functions ###
160
+
161
+ def quadrilateral_to_line(points):
162
+ base_point = [0,0]
163
+ points.sort(key=lambda p: check_distance(base_point,p))
164
+ base_point = points[0]
165
+ points.sort(key=lambda p: check_distance(base_point,p))
166
+ return np.array([get_midpoint(points[0], points[1]), get_midpoint(points[2], points[3])])
167
+
168
+
169
+ def check_distance(point_A, point_B):
170
+ return math.sqrt(((point_A[0]-point_B[0]) ** 2) + ((point_A[1]-point_B[1]) ** 2))
171
+
172
+
173
+ def get_midpoint(point_A, point_B):
174
+ return np.array([round((point_A[0]+point_B[0])/2), round((point_A[1]+point_B[1])/2)])
175
+
176
+
177
+ def points_string_to_int_points(string):
178
+ return [[int(round(float(pi)*scale_factor)) for pi in p.split(",")] for p in string.split()]
179
+
180
+
181
+ ### Validation Images ###
182
+
183
+ def save_validation_images(filepath):
184
+ count = 0
185
+ result = COCO(filepath)
186
+ for id in random.sample(result.getImgIds(), 15):
187
+ print("IMAGE "+str(result.imgs[id]))
188
+ validation_path = str(from_root(dataset_root+"validation_images/casa_"+str(count)))
189
+ draw_from_coco(id, result, validation_path)
190
+ count+=1
191
+
192
+
193
+
194
+ main()
rcnn_model/preprocessing/cleaning_images.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import os
4
+
5
+ # Function to preprocess the image
6
+ def preprocessing(image_path):
7
+ image = cv2.imread(image_path)
8
+
9
+ if image is None:
10
+ print(f"Warning: Could not read {image_path}")
11
+ return None
12
+
13
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
14
+ denoisy_img = cv2.GaussianBlur(gray, (5, 5), 0)
15
+
16
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
17
+ clahe_img = clahe.apply(denoisy_img)
18
+
19
+ _, thresholded_img = cv2.threshold(clahe_img, 150, 255, cv2.THRESH_BINARY)
20
+
21
+ edges = cv2.Canny(thresholded_img, 100, 220, apertureSize=3)
22
+ lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi / 180, threshold=50,
23
+ minLineLength=35, maxLineGap=5)
24
+
25
+ output_img = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
26
+
27
+ if lines is not None:
28
+ for line in lines:
29
+ x1, y1, x2, y2 = line[0]
30
+ cv2.line(output_img, (x1, y1), (x2, y2), (210, 210, 210), 1)
31
+
32
+ blended_image = cv2.addWeighted(image, 0.7, output_img, 0.3, 0)
33
+
34
+ return blended_image
35
+
36
+ # Define paths
37
+ source_root = "../cubicasa5k"
38
+ output_dir = "dataset/images"
39
+
40
+ script_dir = os.path.dirname(os.path.abspath(__file__))
41
+ os.chdir(script_dir)
42
+
43
+ print(f"Fixed Working Directory: {os.getcwd()}")
44
+
45
+ # Create output directories if they don't exist
46
+ os.makedirs("dataset", exist_ok=True)
47
+ os.makedirs(output_dir, exist_ok=True)
48
+
49
+ # Iterate over subfolders 1, 2, 3, ..., n
50
+ for subfolder in os.listdir(source_root):
51
+ subfolder_path = os.path.join(source_root, subfolder)
52
+
53
+ if os.path.isdir(subfolder_path): # Ensure it's a directory
54
+ image_path = os.path.join(subfolder_path, "F1_original.png")
55
+
56
+ if os.path.exists(image_path):
57
+ processed_img = preprocessing(image_path)
58
+
59
+ if processed_img is not None:
60
+ output_filename = f"{subfolder}.png" # Save with subfolder name
61
+ output_path = os.path.join(output_dir, output_filename)
62
+ cv2.imwrite(output_path, processed_img)
63
+ print(f"Processed: {image_path} -> {output_path}")
64
+ else:
65
+ print(f"Skipping {subfolder}: F1_original.png not found")
66
+
67
+ print("Processing completed.")
rcnn_model/preprocessing/cleaning_single_image.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # single_image_cleaning.py
2
+
3
+ import cv2
4
+ import numpy as np
5
+ import os
6
+
7
+ def preprocess_image(image_path):
8
+ """
9
+ Preprocess a single floorplan image: denoising, CLAHE, edge enhancement.
10
+ """
11
+ print(f"🧹 Preprocessing image: {image_path}")
12
+
13
+ image = cv2.imread(image_path)
14
+ if image is None:
15
+ print(f"❌ Error: Could not read image from {image_path}")
16
+ return None
17
+
18
+ # Convert to grayscale
19
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
20
+
21
+ # Apply Gaussian blur
22
+ denoisy_img = cv2.GaussianBlur(gray, (5, 5), 0)
23
+
24
+ # Apply CLAHE (Contrast Limited Adaptive Histogram Equalization)
25
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
26
+ enhanced = clahe.apply(denoisy_img)
27
+
28
+ # Apply threshold
29
+ _, thresholded = cv2.threshold(enhanced, 150, 255, cv2.THRESH_BINARY)
30
+
31
+ # Detect edges
32
+ edges = cv2.Canny(thresholded, 100, 220, apertureSize=3)
33
+
34
+ # Detect lines and draw them
35
+ output_img = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
36
+ lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi / 180, threshold=50,
37
+ minLineLength=35, maxLineGap=5)
38
+
39
+ if lines is not None:
40
+ for line in lines:
41
+ x1, y1, x2, y2 = line[0]
42
+ cv2.line(output_img, (x1, y1), (x2, y2), (210, 210, 210), 1)
43
+
44
+ # Blend the original image with line-enhanced version
45
+ blended_image = cv2.addWeighted(image, 0.7, output_img, 0.3, 0)
46
+
47
+ print(f"✅ Preprocessing complete for: {image_path}")
48
+ return blended_image
rcnn_model/preprocessing/splitting_dataset.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import shutil
4
+
5
+ script_dir = os.path.dirname(os.path.abspath(__file__))
6
+ os.chdir(script_dir)
7
+
8
+ print(f"Fixed Working Directory: {os.getcwd()}")
9
+
10
+ dataset_path = "dataset"
11
+ image_dir = os.path.join(dataset_path, "images")
12
+ label_dir = os.path.join(dataset_path, "yolo_annotations")
13
+
14
+ train_dir = "dataset/train"
15
+ val_dir = "dataset/val"
16
+ test_dir = "dataset/test"
17
+
18
+ for d in [train_dir, val_dir, test_dir]:
19
+ os.makedirs(os.path.join(d, "images"), exist_ok=True)
20
+ os.makedirs(os.path.join(d, "yolo_annotations"), exist_ok=True)
21
+
22
+ images = [f for f in os.listdir(image_dir) if f.endswith(".png")]
23
+ random.shuffle(images)
24
+
25
+ split_ratio = [0.8, 0.1, 0.1]
26
+ train_split = int(split_ratio[0] * len(images))
27
+ val_split = int(split_ratio[1] * len(images)) + train_split
28
+
29
+ train_images = images[:train_split]
30
+ val_images = images[train_split:val_split]
31
+ test_images = images[val_split:]
32
+
33
+ for img in train_images:
34
+ shutil.move(os.path.join(image_dir, img), os.path.join(train_dir, "images", img))
35
+ shutil.move(os.path.join(label_dir, img.replace(".png", ".txt")), os.path.join(train_dir, "yolo_annotations", img.replace(".png", ".txt")))
36
+
37
+ for img in val_images:
38
+ shutil.move(os.path.join(image_dir, img), os.path.join(val_dir, "images", img))
39
+ shutil.move(os.path.join(label_dir, img.replace(".png", ".txt")), os.path.join(val_dir, "yolo_annotations", img.replace(".png", ".txt")))
40
+
41
+ for img in test_images:
42
+ shutil.move(os.path.join(image_dir, img), os.path.join(test_dir, "images", img))
43
+ shutil.move(os.path.join(label_dir, img.replace(".png", ".txt")), os.path.join(test_dir, "yolo_annotations", img.replace(".png", ".txt")))
44
+
45
+ print("Dataset split completed!")
rcnn_model/preprocessing/svg_to_yolo.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import xml.etree.ElementTree as ET
2
+ import re
3
+ import os
4
+ import glob
5
+
6
+ namespace = {"svg": "http://www.w3.org/2000/svg"}
7
+
8
+
9
+ YOLO_CLASSES = {
10
+ "Door": 0,
11
+ "Window": 1,
12
+ "Space": 2
13
+ }
14
+
15
+ def extract_svg_elements(svg_file):
16
+ tree = ET.parse(svg_file)
17
+ root = tree.getroot()
18
+
19
+ svg_width = float(root.get("width", "1"))
20
+ svg_height = float(root.get("height", "1"))
21
+
22
+ # floorplans = {}
23
+ floorplans = {"Door": [], "Window": [], "Space": []}
24
+ for floorplan in root.findall(".//svg:g[@class]", namespaces=namespace):
25
+ class_attr = floorplan.get("class", "").strip()
26
+
27
+
28
+ if class_attr in ["Floorplan Floor-1", "Floorplan Floor-2"]:
29
+
30
+ for element in floorplan.iter():
31
+ class_attr = element.get("class")
32
+ if class_attr:
33
+ if any(cat in class_attr for cat in YOLO_CLASSES.keys()):
34
+ polygons = []
35
+ for poly in element.findall(".//svg:polygon", namespaces=namespace):
36
+ points = poly.get("points")
37
+ if points:
38
+ polygons.append(points)
39
+
40
+ if polygons:
41
+ category = next((cat for cat in YOLO_CLASSES if cat in class_attr), None)
42
+ print(type(polygons[0]))
43
+ if category:
44
+ bbox = get_bounding_box(polygons[0], svg_width, svg_height)
45
+
46
+ if "Space" in class_attr:
47
+ name_label = re.sub(r'\b[Ss]pace\b', '', class_attr).strip()
48
+ floorplans["Space"].append({
49
+ "name": name_label, "bbox": bbox
50
+ })
51
+ else:
52
+ floorplans[category].append({"bbox": bbox})
53
+
54
+ return floorplans, svg_width, svg_height
55
+
56
+ def get_bounding_box(polygons, svg_width, svg_height):
57
+ """Compute YOLO bounding box from polygon points."""
58
+ all_x, all_y = [], []
59
+ print(polygons)
60
+ # for polygon in polygons:
61
+ # print(polygon)
62
+ points = polygons.strip().split(" ")
63
+ for point in points:
64
+ x, y = map(float, point.split(","))
65
+ all_x.append(x)
66
+ all_y.append(y)
67
+ print(all_x, all_y)
68
+ # Bounding Box Calculation
69
+ x_min, x_max = min(all_x), max(all_x)
70
+ y_min, y_max = min(all_y), max(all_y)
71
+
72
+ # Convert to YOLO format (normalized)
73
+ x_center = (x_min + x_max) / 2 / svg_width
74
+ y_center = (y_min + y_max) / 2 / svg_height
75
+ width = (x_max - x_min) / svg_width
76
+ height = (y_max - y_min) / svg_height
77
+
78
+ return (x_center, y_center, width, height)
79
+
80
+ def save_yolo_annotations(floorplans, output_dir, filename):
81
+ """Save extracted bounding boxes in YOLO format."""
82
+ os.makedirs("dataset", exist_ok=True)
83
+ os.makedirs(output_dir, exist_ok=True)
84
+
85
+
86
+ output_file = f"{output_dir}/{filename}.txt"
87
+
88
+ with open(output_file, "w") as f:
89
+ for category, elements in floorplans.items():
90
+ class_id = YOLO_CLASSES[category]
91
+
92
+ for element in elements:
93
+ bbox = element["bbox"]
94
+ yolo_line = f"{class_id} {bbox[0]:.6f} {bbox[1]:.6f} {bbox[2]:.6f} {bbox[3]:.6f}\n"
95
+ f.write(yolo_line)
96
+
97
+ print(f"YOLO annotations saved in '{output_dir}'")
98
+
99
+
100
+ input_folder = "../cubicasa5k/high_quality/"
101
+ output_folder = "dataset/yolo_annotations"
102
+
103
+ script_dir = os.path.dirname(os.path.abspath(__file__))
104
+ os.chdir(script_dir)
105
+
106
+ print(f"Fixed Working Directory: {os.getcwd()}")
107
+
108
+ subfolders = glob.glob(os.path.join(input_folder, "*"))
109
+
110
+ for subfolder in subfolders:
111
+ svg_file = os.path.join(subfolder, "model.svg")
112
+
113
+ if os.path.exists(svg_file):
114
+ filename = os.path.basename(subfolder)
115
+ print(f"Processing: {svg_file} ...")
116
+
117
+ floorplans, svg_width, svg_height = extract_svg_elements(svg_file)
118
+ save_yolo_annotations(floorplans, output_folder, filename)
119
+
120
+ print(" All SVG files have been processed!")
rcnn_model/scripts/rcnn_config.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from detectron2 import model_zoo
3
+ from detectron2.config import get_cfg
4
+ from detectron2.data.datasets import register_coco_instances
5
+ from from_root import from_root
6
+
7
+
8
+ def write_config():
9
+ cfg = get_cfg()
10
+ cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml"))
11
+ cfg.DATASETS.TRAIN = ("inodata_train","cubicasa_train")
12
+ cfg.DATASETS.PROPOSAL_FILES_TRAIN = ("inodata_train")
13
+ cfg.DATASETS.TEST = ()
14
+ cfg.SOLVER.BASE_LR = .0005
15
+ cfg.SOLVER.MAX_ITER = 100
16
+ cfg.SOLVER.CHECKPOINT_PERIOD = 1000
17
+ cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 256
18
+ cfg.MODEL.ROI_HEADS.NUM_CLASSES = 4
19
+ cfg.INPUT.MASK_FORMAT = "polygon"
20
+ cfg.MODEL.RPN.NMS_THRESH = 0.8
21
+ cfg.SOLVER.NUM_DECAYS = 2
22
+ cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.yaml")
23
+ cfg.SOLVER.STEPS = (50,75)
24
+ cfg.MODEL.ROI_HEADS.POSITIVE_FRACTION = .7
25
+ cfg.SOLVER.GAMMA = 0.4
26
+ cfg.MODEL.ROI_MASK_HEAD.POOLER_RESOLUTION = 14
27
+ cfg.MODEL.ROI_MASK_HEAD.NUM_CONV = 3
28
+ cfg.TEST.DETECTIONS_PER_IMAGE = 120
29
+ # Added this extra line
30
+ cfg.OUTPUT_DIR = str(from_root("rcnn_model/output"))
31
+
32
+ return cfg
33
+
34
+ os.chdir(str(from_root()))
35
+ register_coco_instances("cubicasa_train",{},"dataset/annotations/cubicasa_train.json","dataset/")
36
+ register_coco_instances("inodata_train",{},"dataset/annotations/train_sampled_data.json","dataset/")
37
+ register_coco_instances("inodata_val",{},"dataset/annotations/val_sampled_data.json","dataset/")
38
+ register_coco_instances("cubicasa_val",{},"dataset/annotations/cubicasa_test.json","dataset/")
rcnn_model/scripts/rcnn_eval.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import sys
3
+ sys.path.append("/workspaces/tensorflow-gpu/cocoapi/PythonAPI/pycocotools")
4
+ import os
5
+ import cv2
6
+ from detectron2.data import DatasetCatalog
7
+ import detectron2.data as ddata
8
+ from detectron2.engine import DefaultPredictor
9
+ from detectron2.evaluation import COCOEvaluator
10
+ from detectron2.utils.visualizer import ColorMode
11
+ from detectron2.utils.visualizer import Visualizer
12
+ import random
13
+ import matplotlib.pyplot as plt
14
+ import time
15
+ from rcnn_config import write_config
16
+ from from_root import from_root
17
+ from rcnn_model.utils.floorplan_vectorizer_utils import check_image_size_thresh
18
+
19
+
20
+ # sys.path.append(str(from_root("utils")))
21
+ # from floorplan_vectorizer_utils import check_image_size_thresh
22
+
23
+ results_directory = str(from_root("results"))+"/"
24
+ max_image_size = 700*500
25
+
26
+ def main(cfg,results_filename = "eval_results.txt"):
27
+ #update config file
28
+ cfg.DATALOADER.NUM_WORKERS = 1
29
+ cfg.SOLVER.IMS_PER_BATCH = 1
30
+ cfg.MODEL.WEIGHTS = os.path.join(cfg.OUTPUT_DIR, "model_final.pth")
31
+
32
+ #run evaluation
33
+ results = standard_evaluation(cfg)
34
+
35
+ #save results
36
+ file = open(results_directory+results_filename,"w")
37
+ file.write(str(results))
38
+ file.close()
39
+
40
+
41
+ ### Evaluation ###
42
+
43
+ def standard_evaluation(cfg):
44
+ #load predictor
45
+ predictor = DefaultPredictor(cfg)
46
+ test_data_loader = ddata.build_detection_test_loader(cfg, "inodata_val")
47
+
48
+ #save some validation images
49
+ save_validation_images(predictor)
50
+
51
+ #create evaluator
52
+ evaluator = COCOEvaluator("inodata_val",tasks={"segm","bbox"},output_dir="./eval_output",distributed=False,max_dets_per_image=50,allow_cached_coco=False)
53
+ print("EVALUATING")
54
+ evaluator.reset()
55
+
56
+ #load results into evaluator
57
+ for inputs, outputs in block_prediction(test_data_loader, predictor):
58
+ evaluator.process(inputs,outputs)
59
+ del inputs
60
+ del outputs
61
+ print("|",end="")
62
+ time.sleep(.5)
63
+ print("")
64
+
65
+ #run evaluator
66
+ results = evaluator.evaluate()
67
+ print(results)
68
+ print("EVALUATED")
69
+ return results
70
+
71
+
72
+ def block_prediction(loader, predictor):
73
+ for data in loader:
74
+ if(check_image_size_thresh(data[0]["file_name"],max_image_size)):
75
+ image = cv2.imread(data[0]["file_name"])
76
+ result = predictor(image)
77
+ yield data, [result]
78
+ del image
79
+ del result
80
+
81
+
82
+ ### Validation Images ###
83
+
84
+ def save_validation_images(predictor):
85
+ val_img_id = 1
86
+ for d in random.sample(DatasetCatalog.get("inodata_val"), 16):
87
+ save_image(d,predictor,val_img_id)
88
+ val_img_id += 1
89
+
90
+
91
+ def save_image(d, predictor, val_img_id):
92
+ try:
93
+ if(check_image_size_thresh(d["file_name"],max_image_size)):
94
+ #set load image
95
+ val_img_dest_path = "models/rcnn/validation_images/RCNN_val_image_"+str(val_img_id)+".png"
96
+ im = cv2.imread(d["file_name"])
97
+ outputs = predictor(im)
98
+
99
+ #save image
100
+ v = Visualizer(im[:,:,::-1],scale=0.5,instance_mode=ColorMode.IMAGE_BW)
101
+ out = v.draw_instance_predictions(outputs["instances"].to("cpu"))
102
+ plt.imshow(out.get_image()[:,:,::-1])
103
+ plt.axis('off')
104
+ plt.savefig(val_img_dest_path,bbox_inches='tight',pad_inches=0)
105
+ print("Saved validation image to "+val_img_dest_path)
106
+ plt.clf()
107
+ except:
108
+ print("ERROR SAVING IMAGE")
rcnn_model/scripts/rcnn_full_tuner.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from rcnn_config import write_config
2
+ import rcnn_train
3
+ import rcnn_eval
4
+ import rcnn_run
5
+
6
+ run_iter_counts = [3000,4000,5000,6000,7000,8000,9000,10000]
7
+
8
+ def main():
9
+ cfg = write_config()
10
+ for i in range(0,len(run_iter_counts)):
11
+ iters = run_iter_counts[i]
12
+ if(iters > 0):
13
+ cfg.SOLVER.MAX_ITER = iters
14
+ cfg.SOLVER.STEPS = (int(iters*.5),int(iters*.75))
15
+ rcnn_train.main(cfg)
16
+ rcnn_run.default_sample(cfg, i)
17
+ rcnn_eval.main(cfg,"eval_results_run_"+str(i)+".txt")
18
+
19
+ main()
rcnn_model/scripts/rcnn_run.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ from pycocotools.coco import COCO
4
+ from detectron2.engine import DefaultPredictor
5
+ import matplotlib.pyplot as plt
6
+ import torch
7
+ from datetime import datetime
8
+ from rcnn_config import write_config
9
+ import sys
10
+ from from_root import from_root
11
+ from rcnn_model.preprocessing.cleaning_single_image import preprocess_image
12
+ from rcnn_model.utils.floorplan_vectorizer_utils import draw_from_coco, bitmask_to_polygon
13
+ from rcnn_model.extraction.annotation_builder import AnnotationBuilder as AnnBuild
14
+
15
+
16
+
17
+ # sys.path.append(str(from_root("preprocessing")))
18
+ # from cleaning_images import preprocessing
19
+ # sys.path.append(str(from_root("utils")))
20
+ # from floorplan_vectorizer_utils import draw_from_coco, bitmask_to_polygon
21
+ # sys.path.append(str(from_root("dataset/extraction_scripts")))
22
+ # from annotation_builder import AnnotationBuilder as AnnBuild
23
+
24
+ # results_directory = str(from_root("results"))+"/"
25
+ # sample_data_directory = str(from_root("models/rcnn/sample_data"))+"/"
26
+
27
+ results_directory = "rcnn_model/results/"
28
+ sample_data_directory = "rcnn_model/sample/"
29
+
30
+ def main(cfg,img_source_path, coco_dest_filename, val_img_dest_filename):
31
+ os.chdir(str(from_root()))
32
+
33
+ #configure model
34
+ cfg.DATALOADER.NUM_WORKERS = 1
35
+ cfg.SOLVER.IMS_PER_BATCH = 1
36
+ cfg.MODEL.WEIGHTS = os.path.join(cfg.OUTPUT_DIR, "model_final.pth")
37
+ cfg.MODEL.DEVICE = "cpu"
38
+ predictor = DefaultPredictor(cfg)
39
+
40
+ #run
41
+ prediction_runner(img_source_path, results_directory+coco_dest_filename, results_directory+val_img_dest_filename, predictor)
42
+ #prediction_runner(img_source_path, results_directory+coco_dest_filename, results_directory+val_img_dest_filename, predictor, segmented_prediction=True, scale_factor=.5)
43
+ print("SAVED to "+results_directory+coco_dest_filename+" and "+results_directory+val_img_dest_filename)
44
+
45
+
46
+ ### Main Runner ###
47
+
48
+ def prediction_runner(filename, coco_dest_path, val_img_dest_path, predictor, segmented_prediction = False, scale_factor = 0):
49
+ #set up annotation builder
50
+ ann_builder = instantiate_ann_build()
51
+
52
+ #set up image
53
+ initImg = preprocess_image(filename)
54
+ init_width = initImg.shape[1]
55
+ init_height = initImg.shape[0]
56
+ img_id = ann_builder.add_image(str(from_root(filename)), init_width, init_height)
57
+ print("cleaned")
58
+
59
+ #resize image
60
+ scaled_width = 800
61
+ scaled_height = 800
62
+ img = cv2.resize(initImg, (scaled_width,scaled_height))
63
+ if(scale_factor > 0):
64
+ img = cv2.resize(initImg, (0,0), fx=scale_factor, fy=scale_factor)
65
+ scaled_width = img.shape[1]
66
+ scaled_height = img.shape[0]
67
+ print("resized")
68
+
69
+ #run prediction
70
+ if(segmented_prediction):
71
+ run_segmented_prediction(ann_builder, predictor, img, img_id, scaled_width, scaled_height, scale_factor)
72
+ else:
73
+ run_prediction(ann_builder, predictor, img, img_id, init_width/scaled_width, init_height/scaled_height)
74
+
75
+ #save the file
76
+ ann_builder.save_file(str(from_root(coco_dest_path)))
77
+
78
+ #visualize
79
+ coco = COCO(str(from_root(coco_dest_path)))
80
+ draw_from_coco(0, coco, val_img_dest_path)
81
+
82
+
83
+ def instantiate_ann_build():
84
+ ann_builder = AnnBuild()
85
+ ann_builder.set_info("generated annotations of Inovonics and university provided data","inovonData","NA",datetime.now())
86
+ ann_builder.add_license("TODO", "TODO")
87
+ return ann_builder
88
+
89
+
90
+ def prediction_outputs_to_annotations(annotations, outputs, img_id, base_ann_id, x_offset=0, y_offset=0, h_scale_factor=1, v_scale_factor=1):
91
+ ann_id = base_ann_id
92
+ for i in range(0,len(outputs["instances"].to(torch.device("cpu")).pred_masks)):
93
+ mask = outputs["instances"].to(torch.device("cpu")).pred_masks[i]
94
+ class_id = outputs["instances"].to(torch.device("cpu")).pred_classes[i].item()
95
+ score = outputs["instances"].to(torch.device("cpu")).scores[i].item()
96
+ annotations.append(bitmask_to_polygon(ann_id, img_id, class_id, score, mask, x_offset=x_offset, y_offset=y_offset,scale_factor_width = h_scale_factor,scale_factor_height = v_scale_factor))
97
+ ann_id += 1
98
+ return annotations, ann_id
99
+
100
+
101
+ ### Standard Prediction ###
102
+
103
+ def run_prediction(ann_builder, predictor, img, img_id, h_scale_factor, v_scale_factor):
104
+ outputs = predictor(img)
105
+ print("predicted")
106
+ annotations = []
107
+ annotations, annId = prediction_outputs_to_annotations(annotations, outputs, img_id, 0, 0, 0, h_scale_factor, v_scale_factor)
108
+ ann_builder.annotations = annotations
109
+ print("annotations converted")
110
+
111
+
112
+ ### Segmented Prediction ###
113
+
114
+ def run_segmented_prediction(ann_builder, predictor, img, img_id, width, height, scale_factor, segment_size = 800):
115
+ #initialize
116
+ count = 1
117
+ subimg_dest_path = "models/rcnn/westmoor_check/subimgs/subimg_"
118
+ annotations = []
119
+ ann_id = 0
120
+
121
+ #iterate through segments
122
+ for xi in range(0, int(width/segment_size)+1):
123
+ for yi in range(0, int(height/segment_size)+1):
124
+ #calculate subimg area
125
+ h_base, v_base, h_boundary, v_boundary = get_subimg_area(xi,yi,width,height,segment_size)
126
+
127
+ #save subimgs
128
+ if(h_boundary > h_base and v_boundary > v_base):
129
+ subimg = img[v_base:v_boundary,h_base:h_boundary,:]
130
+ save_subimg(subimg, subimg_dest_path, count, h_boundary-h_base, v_boundary-v_base)
131
+
132
+ #get annotations
133
+ outputs = predictor(subimg)
134
+ annotations, ann_id = prediction_outputs_to_annotations(annotations, outputs, img_id, ann_id, h_base*1/scale_factor, h_base*1/scale_factor, 1/scale_factor, 1/scale_factor)
135
+ count += 1
136
+ ann_builder.annotations = annotations
137
+
138
+
139
+ def get_subimg_area(xi,yi,img_width,img_height,segment_size):
140
+ h_base = xi*segment_size
141
+ v_base = yi*segment_size
142
+ h_boundary = (xi+1)*segment_size
143
+ if(h_boundary >= img_width):
144
+ h_boundary = img_width-1
145
+ v_boundary = (yi+1)*segment_size
146
+ if(v_boundary >= img_height):
147
+ v_boundary = img_height-1
148
+ return h_base, v_base, h_boundary, v_boundary
149
+
150
+
151
+ def save_subimg(subimg, subimg_dest_path, count, width, height):
152
+ plt.figure(figsize=(width, height),dpi=1)
153
+ plt.imshow(subimg)
154
+ plt.axis('off')
155
+ plt.savefig(str(from_root(subimg_dest_path+str(count)+".png")),bbox_inches='tight',pad_inches=0)
156
+ plt.clf()
157
+
158
+
159
+ def default_sample(cfg, run_index = -1):
160
+ if(run_index >= 0):
161
+ main(cfg,sample_data_directory+"westmoor_floor_2_floorplan.png","westmoor_result_run_"+str(run_index)+".json","westmoor_result_run_"+str(run_index)+".png")
162
+ main(cfg,sample_data_directory+"REV2 ENSURE Layout - Springs at the Waterfront - PR-2023-3661 - CN0012630 - 11.02.2023 (1)-16.png","rev2_result_run_"+str(run_index)+".json","rev2_result_run_"+str(run_index)+".png")
163
+ main(cfg,sample_data_directory+"F1_original.png","cubicasa_result_run_"+str(run_index)+".json","cubicasa_result_run_"+str(run_index)+".png")
164
+ else:
165
+ main(cfg,sample_data_directory+"westmoor_floor_2_floorplan.png","westmoor_result.json","westmoor_result.png")
166
+ main(cfg,sample_data_directory+"REV2 ENSURE Layout - Springs at the Waterfront - PR-2023-3661 - CN0012630 - 11.02.2023 (1)-16.png","rev2_result.json","rev2_result.png")
167
+ main(cfg,sample_data_directory+"F1_original.png","cubicasa_result.json","cubicasa_result.png")
168
+
169
+ if __name__ == "__main__":
170
+ print("Inside Main Function Call")
171
+ cfg = write_config()
172
+ run_index=-1
173
+ main(cfg,sample_data_directory+"F1_original.png","cubicasa_result_run_"+str(run_index)+".json","cubicasa_result_run_"+str(run_index)+".png")
rcnn_model/scripts/rcnn_train.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from detectron2.modeling import build_model
3
+ from detectron2.data.datasets import register_coco_instances
4
+ from detectron2.engine import DefaultTrainer
5
+ from from_root import from_root
6
+
7
+ def main(cfg):
8
+ os.chdir(str(from_root()))
9
+ register_coco_instances("cubicasa_train",{},"dataset/annotations/cubicasa_train.json","dataset/")
10
+ register_coco_instances("cubicasa_val",{},"dataset/annotations/cubicasa_test.json","dataset/")
11
+
12
+
13
+ cfg.DATALOADER.NUM_WORKERS = 4
14
+ cfg.SOLVER.IMS_PER_BATCH = 2
15
+
16
+ model = build_model(cfg)
17
+
18
+ model.train()
19
+ trainer = DefaultTrainer(cfg=cfg)
20
+ trainer.resume_or_load(resume=False)
21
+ trainer.train()
rcnn_model/utils/coco_to_inovonics_json.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from pycocotools.coco import COCO
3
+ import json
4
+ from inovonics_ann_builder import InovonicsAnnotationBuilder as InovAnnBuild
5
+ from from_root import from_root
6
+
7
+ def main(coco_source_path, inovonics_anns_dest_path, img_ids=[1]):
8
+ coco = COCO(coco_source_path)
9
+ if(len(img_ids) == 1):
10
+ for img_id in img_ids:
11
+ coco_img_to_inovonics_json(coco, inovonics_anns_dest_path, img_id)
12
+ else:
13
+ for img_id in img_ids:
14
+ coco_img_to_inovonics_json(coco, inovonics_anns_dest_path[0:-5]+"_"+str(img_id)+".json", img_id)
15
+
16
+
17
+ def coco_img_to_inovonics_json(coco, inovonics_anns_dest_path, img_id=0):
18
+ #iterate
19
+ annotation_full_file = []
20
+ count = 0
21
+ for ann in coco.imgToAnns[img_id]:
22
+ print(ann)
23
+ inov_ann_build = InovAnnBuild()
24
+ inov_ann_build.set_id(str(count))
25
+ inov_ann_build.set_body("Room "+str(count))
26
+ inov_ann_build.set_type("Selection")
27
+ inov_ann_build.set_target("FragmentSelector","http://www.w3.org/TR/media-frags/",ann["bbox"])
28
+ annotation_full_file.append(inov_ann_build.final_output())
29
+ count+=1
30
+
31
+ #save file
32
+ coco_file = open(inovonics_anns_dest_path,'w')
33
+ json.dump(annotation_full_file,coco_file,indent=4)
34
+ coco_file.close()
rcnn_model/utils/floorplan_vectorizer_utils.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image
2
+ import matplotlib.pyplot as plt
3
+ import skimage.io as io
4
+ import numpy as np
5
+ from pycocotools import mask
6
+ from skimage import measure
7
+ from shapely import geometry
8
+ from from_root import from_root
9
+
10
+ ### Image Size Checking ###
11
+
12
+ def check_image_size_thresh(png_path, areathreshold):
13
+ width, height = get_image_size(png_path)
14
+ return width*height <= areathreshold
15
+
16
+ def get_image_size(png_path):
17
+ img = Image.open(png_path)
18
+ return img.width, img.height
19
+
20
+
21
+ ### Visualization ###
22
+
23
+ def draw_from_coco(id,coco,annotated_img_dest_path,category_filter = [0,1,2,3], blank_bg = False):
24
+ filename = coco.imgs[id]["file_name"]
25
+ image = io.imread(str(from_root(filename)))
26
+ if(image is not None):
27
+ plt.figure(figsize=(image.shape[1],image.shape[0]),dpi=1)
28
+ if(blank_bg):
29
+ image = get_blank_image(image.shape[0],image.shape[1])
30
+ plt.imshow(image)
31
+ plt.axis('off')
32
+ annotation_ids = coco.getAnnIds(imgIds=[id], catIds=category_filter, iscrowd=None)
33
+ annotations = coco.loadAnns(annotation_ids)
34
+ coco.showAnns(annotations)
35
+ plt.savefig(annotated_img_dest_path,bbox_inches='tight',pad_inches=0)
36
+ plt.clf()
37
+ print("---")
38
+ print("Saved Validation Image "+annotated_img_dest_path)
39
+
40
+ def get_blank_image(width,height):
41
+ blank = io.imread(str(from_root("models/rcnn/westmoor_check/white_bg.png")))
42
+ #return cv2.resize(blank, (0,0), fx=width, fy=height)
43
+ return blank[:width,:height,:]
44
+
45
+
46
+ ### Converting Bitmask to Polygon ###
47
+
48
+ #modifeid version of code from Waspinator on https://github.com/cocodataset/cocoapi/issues/131
49
+ def bitmask_to_polygon(id, im_id, cat_id, score, ground_truth_binary_mask, x_offset = 0, y_offset = 0, scale_factor_width = 1, scale_factor_height = 1):
50
+ fortran_ground_truth_binary_mask = np.asfortranarray(ground_truth_binary_mask)
51
+ encoded_ground_truth = mask.encode(fortran_ground_truth_binary_mask)
52
+ ground_truth_area = mask.area(encoded_ground_truth)
53
+ ground_truth_bounding_box = mask.toBbox(encoded_ground_truth)
54
+ contours = measure.find_contours(ground_truth_binary_mask.numpy(), 0.5)
55
+
56
+ bbox = []
57
+ for i in range(0,len(ground_truth_bounding_box.tolist())):
58
+ if(i%2 == 0):
59
+ bbox.append(scale_factor_width*ground_truth_bounding_box.tolist()[i]+x_offset)
60
+ else:
61
+ bbox.append(scale_factor_height*ground_truth_bounding_box.tolist()[i]+y_offset)
62
+
63
+ polygon = []
64
+ for contour in contours:
65
+ contour = np.flip(contour, axis=1)
66
+ segmentation = contour.ravel().tolist()
67
+ for i in range(0,len(segmentation)):
68
+ if(i%2 == 0):
69
+ segmentation[i] = int(segmentation[i]*scale_factor_width)
70
+ else:
71
+ segmentation[i] = int(segmentation[i]*scale_factor_height)
72
+ polygon.append(segmentation)
73
+
74
+ segmentations = []
75
+ if(len(polygon) > 0):
76
+ smoothed_polygons = polygon_smoothing_and_offset(polygon[0], x_offset, y_offset)#toPolygon[0]#
77
+ for segmentation in smoothed_polygons:
78
+ segmentations.append(segmentation)
79
+
80
+ annotation = {
81
+ "segmentation": segmentations,
82
+ "area": ground_truth_area.tolist(),
83
+ "iscrowd": 0,
84
+ "image_id": im_id,
85
+ "bbox": bbox,
86
+ "category_id": cat_id,
87
+ "id": id,
88
+ "score": score
89
+ }
90
+
91
+ return annotation
92
+
93
+ def polygon_smoothing_and_offset(polygon, x_offset, y_offset):
94
+ points = []
95
+ for i in range(0,int(len(polygon)/2)):
96
+ points.append([polygon[(2*i)]+x_offset,polygon[(2*i)+1]+y_offset])
97
+ if(len(points) < 4):
98
+ return []
99
+ poly = geometry.Polygon(points)
100
+ for i in [1,2,3,5,8,12,15,18]:
101
+ poly = poly.simplify(i)
102
+ return [np.array(poly.exterior.coords).astype(int).ravel().tolist()[:-2]]
103
+
104
+
rcnn_model/utils/inovonics_ann_builder.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import numpy as np
3
+ from datetime import datetime
4
+ import random
5
+
6
+ version_number = "0.0.1"
7
+
8
+ class InovonicsAnnotationBuilder:
9
+ #creates the base structure of the coco format
10
+ def __init__(self):
11
+ self.annotation_id = ""
12
+ self.body = []
13
+ self.type = ""
14
+ self.target = {}
15
+
16
+ def set_id(self, id):
17
+ self.annotation_id = id
18
+
19
+ def set_body(self, room_name, locator_value=None):
20
+ color = self.generate_color()
21
+ self.body = [{"type":"TextualBody",
22
+ "value":room_name},
23
+ {"type":"highlighting",
24
+ "value":color},
25
+ {"type":"locators",
26
+ "value":locator_value}]
27
+
28
+ def generate_color(self):
29
+ red = str(random.randint(0,255))
30
+ green = str(random.randint(0,255))
31
+ blue = str(random.randint(0,255))
32
+ return "rgb("+red+","+green+","+blue+")"
33
+
34
+ def set_type(self, type):
35
+ self.type = type
36
+
37
+ def set_target(self, type, url, bbox):
38
+ rect = self.bbox_to_rect(bbox)
39
+ self.target = {"selector":{"type":type,
40
+ "conformsTo":url,
41
+ "value":rect}}
42
+
43
+ def bbox_to_rect(self,bbox):
44
+ return "xywh=pixel"+str(bbox[0])+","+str(bbox[1])+","+str(bbox[2])+","+str(bbox[3])
45
+
46
+ def final_output(self):
47
+ return {"annotation_id":self.annotation_id, "body":self.body, "type":self.type, "target":self.target}
48
+
49
+ def save_file(self, filepath):
50
+ coco_file = open(filepath,'w')
51
+ json.dump(self.final_output(),coco_file,indent=4)
52
+ coco_file.close()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ streamlit>=1.30
2
+ opencv-python
3
+ pycocotools
4
+ Pillow
5
+ gdown