Dharini Baskaran
commited on
Commit
·
1d64201
1
Parent(s):
1666228
initial commit
Browse files- .gitignore +27 -0
- Dockerfile +36 -0
- README.md +72 -7
- app.py +177 -0
- packages.txt +2 -0
- public/logo.png +0 -0
- rcnn_model/__init__.py +0 -0
- rcnn_model/extraction/annotation_builder.py +69 -0
- rcnn_model/extraction/floorplan_sampler.py +257 -0
- rcnn_model/extraction/from_labelme_runner.py +61 -0
- rcnn_model/extraction/svg_to_json.py +194 -0
- rcnn_model/preprocessing/cleaning_images.py +67 -0
- rcnn_model/preprocessing/cleaning_single_image.py +48 -0
- rcnn_model/preprocessing/splitting_dataset.py +45 -0
- rcnn_model/preprocessing/svg_to_yolo.py +120 -0
- rcnn_model/scripts/rcnn_config.py +38 -0
- rcnn_model/scripts/rcnn_eval.py +108 -0
- rcnn_model/scripts/rcnn_full_tuner.py +19 -0
- rcnn_model/scripts/rcnn_run.py +173 -0
- rcnn_model/scripts/rcnn_train.py +21 -0
- rcnn_model/utils/coco_to_inovonics_json.py +34 -0
- rcnn_model/utils/floorplan_vectorizer_utils.py +104 -0
- rcnn_model/utils/inovonics_ann_builder.py +52 -0
- requirements.txt +5 -0
.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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|