Merge pull request 'FolderMangement' (#2) from FolderMangement into main
Reviewed-on: http://192.168.178.135:3000/Fabelous/MPENN/pulls/2
This commit is contained in:
commit
7a549e0063
|
@ -0,0 +1,157 @@
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class SaveData:
|
||||||
|
def search_in_folder(self, folder, file_name = "MPENN_image_data_information.json"):
|
||||||
|
"""
|
||||||
|
Searches for a specific file named "MPENN_image_data_information.json" within a given folder.
|
||||||
|
|
||||||
|
:param folder: The folder path to search in.
|
||||||
|
:return: A list containing the path to the "MPENN_image_data_information.json" file if found, empty otherwise.
|
||||||
|
"""
|
||||||
|
# Validate input parameters
|
||||||
|
if not os.path.isdir(folder):
|
||||||
|
raise ValueError("The provided folder path does not exist or is not a directory.")
|
||||||
|
|
||||||
|
# Construct the full search path
|
||||||
|
search_path = os.path.join(folder, file_name)
|
||||||
|
|
||||||
|
# Use glob.glob to find the specified file
|
||||||
|
matching_file = glob.glob(search_path)
|
||||||
|
if len(matching_file) == 0:
|
||||||
|
matching_file = self.create(folder)
|
||||||
|
return matching_file
|
||||||
|
|
||||||
|
def create(self, folder):
|
||||||
|
"""
|
||||||
|
Create a .json file in the given folder path.
|
||||||
|
|
||||||
|
:param folder: Path of the folder where the .json file will be created.
|
||||||
|
"""
|
||||||
|
# Validating folder path
|
||||||
|
if not isinstance(folder, str) or not folder:
|
||||||
|
raise ValueError("Folder path must be a non-empty string.")
|
||||||
|
|
||||||
|
# Ensuring the folder exists
|
||||||
|
os.makedirs(folder, exist_ok=True)
|
||||||
|
|
||||||
|
# Generating a JSON file name with a placeholder approach
|
||||||
|
file_name = "MPENN_image_data_information.json"
|
||||||
|
file_path = os.path.join(folder, file_name)
|
||||||
|
|
||||||
|
# Creating and writing to the JSON file
|
||||||
|
try:
|
||||||
|
with open(file_path, 'w') as json_file:
|
||||||
|
json.dump({}, json_file) # Creating an empty JSON object
|
||||||
|
print(f"File '{file_name}' successfully created at '{folder}'.")
|
||||||
|
return file_path
|
||||||
|
except OSError as e:
|
||||||
|
raise OSError(f"Failed to create file in {folder}. Error: {e}")
|
||||||
|
|
||||||
|
def append_to_json_file(self, image_path, resolution, labeled, rectangles, filename):
|
||||||
|
"""
|
||||||
|
Appends a new record to an existing .json file or creates a file if it doesn't exist.
|
||||||
|
|
||||||
|
:param image_path: A string representing the path to the image.
|
||||||
|
:param resolution: A list or tuple with two integers representing the image's resolution.
|
||||||
|
:param labeled: A boolean indicating if the image is labeled.
|
||||||
|
:param rectangles: A list of lists, where each inner list represents rectangle information.
|
||||||
|
:param filename: A string representing the name of the output .json file.
|
||||||
|
"""
|
||||||
|
rectangles = self.calculate_missing_coordinates_for_each(rectangles)
|
||||||
|
# New entry to add
|
||||||
|
|
||||||
|
|
||||||
|
new_entry = {
|
||||||
|
"Image_path": image_path,
|
||||||
|
"resolution": resolution,
|
||||||
|
"labeled": str(labeled),
|
||||||
|
"rectangle": rectangles
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try to read the existing file
|
||||||
|
with open(filename, 'r') as file:
|
||||||
|
# Load the JSON data from the file
|
||||||
|
data = json.load(file)
|
||||||
|
# Ensure the data is a list to hold multiple entries
|
||||||
|
if not isinstance(data, list):
|
||||||
|
data = [data] # Convert to list if it's a single dictionary
|
||||||
|
except FileNotFoundError:
|
||||||
|
# If the file doesn't exist, start a new list
|
||||||
|
data = []
|
||||||
|
|
||||||
|
# Append the new entry
|
||||||
|
data.append(new_entry)
|
||||||
|
|
||||||
|
# Write the updated list back to the file
|
||||||
|
with open(filename, 'w') as file:
|
||||||
|
json.dump(data, file, indent=4)
|
||||||
|
|
||||||
|
print(f"Entry added to {filename}.")
|
||||||
|
|
||||||
|
def calculate_missing_coordinates_for_each(self, rectangles):
|
||||||
|
"""
|
||||||
|
Calculate missing coordinates for each rectangle set in
|
||||||
|
the provided list of rectangles, considering the new format wherein
|
||||||
|
coordinates are provided as a single tuple.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- rectangles (List[List]): Input list containing rectangle identifiers, their coordinates in a single tuple, and other information.
|
||||||
|
Example: [[id, (top_left_x, top_left_y, bottom_right_x, bottom_right_y), ...]]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- List[List]: Updated list with added missing coordinate lists for each rectangle.
|
||||||
|
"""
|
||||||
|
updated_rectangles = []
|
||||||
|
|
||||||
|
for rectangle in rectangles:
|
||||||
|
# Assuming the structure is [rectangle_id, (top_left_x, top_left_y, bottom_right_x, bottom_right_y), other_info...]
|
||||||
|
rectangle_id, coords, *others = rectangle
|
||||||
|
|
||||||
|
# Unpack coords
|
||||||
|
top_left_x, top_left_y, bottom_right_x, bottom_right_y = coords
|
||||||
|
|
||||||
|
# Calculate missing coordinates: top right (x2, y1) and bottom left (x1, y2)
|
||||||
|
top_right = (bottom_right_x, top_left_y)
|
||||||
|
bottom_left = (top_left_x, bottom_right_y)
|
||||||
|
|
||||||
|
# Ensure the order is [rectangle_id, [top_left], [top_right], [bottom_right], [bottom_left], other_info...]
|
||||||
|
top_left = (top_left_x, top_left_y)
|
||||||
|
bottom_right = (bottom_right_x, bottom_right_y)
|
||||||
|
|
||||||
|
updated_rectangle = [rectangle_id, top_left, top_right, bottom_right, bottom_left] + others
|
||||||
|
updated_rectangles.append(updated_rectangle)
|
||||||
|
|
||||||
|
return updated_rectangles
|
||||||
|
|
||||||
|
def find_highest_image_number(self, json_data):
|
||||||
|
"""
|
||||||
|
Search through the provided JSON data for the highest number in the "Image_path".
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- j_data (List[Dict]): The input JSON data loaded into a Python data structure.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- int: The highest number found in the "Image_path"; returns -1 if no numbers are found.
|
||||||
|
"""
|
||||||
|
highest_number = -1
|
||||||
|
with open(json_data, 'r') as file:
|
||||||
|
# Load the JSON data from the file
|
||||||
|
j_data = json.load(file)
|
||||||
|
pattern = re.compile(r'\d+')
|
||||||
|
|
||||||
|
for item in j_data:
|
||||||
|
if "Image_path" in item:
|
||||||
|
# Extract numbers from "Image_path"
|
||||||
|
numbers = pattern.findall(item["Image_path"])
|
||||||
|
for number in numbers:
|
||||||
|
# Convert found numbers to integers and track the highest
|
||||||
|
current_number = int(number)
|
||||||
|
if current_number > highest_number:
|
||||||
|
highest_number = current_number
|
||||||
|
highest_number+= 1
|
||||||
|
return highest_number
|
|
@ -0,0 +1,93 @@
|
||||||
|
import customtkinter as Ctk
|
||||||
|
import os
|
||||||
|
import scripts.get_sys_info as system_code
|
||||||
|
from ..SaveData import SaveData
|
||||||
|
|
||||||
|
FONT = "Berlin Sans FB"
|
||||||
|
|
||||||
|
class CreateFolder(Ctk.CTkFrame):
|
||||||
|
def __init__(self,master, output_path,callback, **kwargs):
|
||||||
|
super().__init__(master, **kwargs)
|
||||||
|
system_code.load_json_file()
|
||||||
|
self.data_saver = SaveData()
|
||||||
|
self.my_font = Ctk.CTkFont(family=FONT, size=22)
|
||||||
|
self.output_path = output_path
|
||||||
|
self.callback = callback
|
||||||
|
self.error_txt = ""
|
||||||
|
|
||||||
|
self.create_folder()
|
||||||
|
|
||||||
|
def enable_keybinding(self):
|
||||||
|
self.master.bind("<Return>", self.on_a_press, add="+")
|
||||||
|
|
||||||
|
def disable_keybinding(self):
|
||||||
|
self.master.unbind("<Return>")
|
||||||
|
|
||||||
|
def on_a_press(self, event):
|
||||||
|
# Check if the frame is visible by querying its manager info
|
||||||
|
if self.winfo_manager():
|
||||||
|
self.create_new_data_folder()
|
||||||
|
|
||||||
|
def create_folder(self):
|
||||||
|
self.enable_keybinding()
|
||||||
|
self.create_folder_label = Ctk.CTkLabel(self, text="Type name for new Object:", width=100, font=self.my_font)
|
||||||
|
self.create_folder_entry = Ctk.CTkEntry(self, placeholder_text="New Data Object", width=100, font=self.my_font)
|
||||||
|
self.create_folder_name_btn = Ctk.CTkButton(self, text="Create", width=100, command=self.create_new_data_folder, font=self.my_font)
|
||||||
|
self.create_folder_error = Ctk.CTkLabel(self, text=self.error_txt, width=100, font=self.my_font)
|
||||||
|
self.place_create_folder()
|
||||||
|
|
||||||
|
def create_new_data_folder(self):
|
||||||
|
"""
|
||||||
|
Create a new folder in the directory specified by self.output_path.
|
||||||
|
|
||||||
|
The folder name will be 'data_folder'. If the folder already exists, a number will be appended
|
||||||
|
to create a unique folder name ('data_folder_1', 'data_folder_2', etc.).
|
||||||
|
"""
|
||||||
|
folder_name = self.create_folder_entry.get()
|
||||||
|
|
||||||
|
# Construct the full path for the new data folder
|
||||||
|
full_path = os.path.join(self.output_path, folder_name)
|
||||||
|
if os.path.exists(full_path):
|
||||||
|
self.error_txt = "The folder exists already"
|
||||||
|
self.create_folder_error.configure(text=self.error_txt)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
os.makedirs(full_path)
|
||||||
|
print(f"Folder created at: {full_path}")
|
||||||
|
self.data_saver.create(full_path)
|
||||||
|
except OSError:
|
||||||
|
self.error_txt = "There occured an error creating the Folder"
|
||||||
|
self.create_folder_error.configure(text=self.error_txt)
|
||||||
|
self.callback(full_path)
|
||||||
|
|
||||||
|
|
||||||
|
def place_create_folder(self):
|
||||||
|
self.create_folder_label.place(
|
||||||
|
relx=0.5,
|
||||||
|
rely=0.425,
|
||||||
|
relwidth=0.3,
|
||||||
|
relheight=0.06,
|
||||||
|
anchor="center",
|
||||||
|
) # Position the converter button to the left
|
||||||
|
self.create_folder_entry.place(
|
||||||
|
relx=0.3875,
|
||||||
|
rely=0.5,
|
||||||
|
relwidth=0.125,
|
||||||
|
relheight=0.06,
|
||||||
|
anchor="w",
|
||||||
|
) # Position the converter button to the left
|
||||||
|
self.create_folder_name_btn.place(
|
||||||
|
relx=0.6125,
|
||||||
|
rely=0.5,
|
||||||
|
relwidth=0.08,
|
||||||
|
relheight=0.06,
|
||||||
|
anchor="e",
|
||||||
|
) # Adjusted for .place
|
||||||
|
self.create_folder_error.place(
|
||||||
|
relx=0.5,
|
||||||
|
rely=0.575,
|
||||||
|
relwidth=0.3,
|
||||||
|
relheight=0.06,
|
||||||
|
anchor="center",
|
||||||
|
) # Position the converter button to the left
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
import customtkinter as Ctk
|
||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
from ..SaveData import SaveData
|
||||||
|
import scripts.get_sys_info as system_code
|
||||||
|
|
||||||
|
|
||||||
|
FONT = "Berlin Sans FB"
|
||||||
|
|
||||||
|
class OpenFolder(Ctk.CTkScrollableFrame):
|
||||||
|
def __init__(self,master,output_path, callback, **kwargs, ):
|
||||||
|
super().__init__(master, **kwargs)
|
||||||
|
self.my_font = Ctk.CTkFont(family=FONT, size=16)
|
||||||
|
self.warning_font = Ctk.CTkFont(family=FONT, size=20)
|
||||||
|
# the variables needed to get from the upper class
|
||||||
|
self.button_image = Ctk.CTkImage(Image.open(r"./icons/folder.png"), size=(63, 63))
|
||||||
|
self.output_path = output_path
|
||||||
|
self.callback = callback # Store the callback function
|
||||||
|
system_code.load_json_file()
|
||||||
|
self.data_saver = SaveData()
|
||||||
|
self.folders = self.get_all_folders()
|
||||||
|
self.button_size = (50, 50) # Size width and height
|
||||||
|
self.padding = 10 # Padding around each button
|
||||||
|
if len(self.folders) == 0:
|
||||||
|
self.folder_has_no_subs()
|
||||||
|
else:
|
||||||
|
self.make_buttons()
|
||||||
|
self.bind("<Configure>", self.on_configure, '+')
|
||||||
|
|
||||||
|
def on_configure(self, event=None):
|
||||||
|
self.rearrange_buttons()
|
||||||
|
|
||||||
|
def get_button_widths(self):
|
||||||
|
"""
|
||||||
|
Estimates button widths based on button text size.
|
||||||
|
"""
|
||||||
|
average_char_width = 8
|
||||||
|
width = max(len(button._text) * average_char_width for button in self.buttons)
|
||||||
|
return max(width, self.button_size[0])
|
||||||
|
|
||||||
|
def get_max_folders_per_row(self):
|
||||||
|
"""
|
||||||
|
Calculate the maximum number of folders that can fit in a row based on the container's width.
|
||||||
|
"""
|
||||||
|
container_width = self.winfo_width()
|
||||||
|
button_width = self.get_button_widths()
|
||||||
|
# Calculate the space required for one button including padding
|
||||||
|
total_button_width = button_width + 2 * self.padding
|
||||||
|
# Divide the available space in the container by the space required for one button
|
||||||
|
max_folders_per_row = max(1, container_width // total_button_width)
|
||||||
|
|
||||||
|
return max_folders_per_row
|
||||||
|
|
||||||
|
def get_all_folders(self):
|
||||||
|
folders = {f.name: f.path for f in os.scandir(self.output_path) if f.is_dir()}
|
||||||
|
return dict(sorted(folders.items()))
|
||||||
|
|
||||||
|
def rearrange_buttons(self):
|
||||||
|
"""
|
||||||
|
Rearrange buttons based on the current window size and calculated folders per row.
|
||||||
|
If the window size changes, it recalculates the arrangement.
|
||||||
|
"""
|
||||||
|
max_folders_per_row = self.get_max_folders_per_row()
|
||||||
|
|
||||||
|
for index, button in enumerate(self.buttons):
|
||||||
|
row = index // max_folders_per_row
|
||||||
|
col = index % max_folders_per_row
|
||||||
|
button.grid(row=row, column=col, padx=self.padding, pady=self.padding)
|
||||||
|
|
||||||
|
def make_buttons(self):
|
||||||
|
"""
|
||||||
|
Generates buttons for each folder provided.
|
||||||
|
"""
|
||||||
|
self.buttons = []
|
||||||
|
for key, value in self.folders.items():
|
||||||
|
folder_btn = Ctk.CTkButton(
|
||||||
|
self, text=key,
|
||||||
|
width=self.button_size[0], height=self.button_size[1],
|
||||||
|
image=self.button_image, # Corrected: Use self.button_image for the image parameter
|
||||||
|
compound="top", # Position text below the image
|
||||||
|
command=lambda val=value: self.new_folder_chosen(val),
|
||||||
|
font=self.my_font
|
||||||
|
)
|
||||||
|
folder_btn.grid_remove()
|
||||||
|
self.buttons.append(folder_btn)
|
||||||
|
self.rearrange_buttons() # Rearrange buttons after creation
|
||||||
|
|
||||||
|
def new_folder_chosen(self, folder_path):
|
||||||
|
"""
|
||||||
|
Callback for when a new folder is chosen.
|
||||||
|
"""
|
||||||
|
self.data_saver.search_in_folder(folder_path)
|
||||||
|
|
||||||
|
self.callback(folder_path) # Call the callback passing the current value
|
||||||
|
|
||||||
|
def folder_has_no_subs(self):
|
||||||
|
self.place_forget()
|
||||||
|
self.label = Ctk.CTkLabel(self, text="No Folders were found", width=100, font=self.warning_font)
|
||||||
|
self.label.grid(row = 1, column = 1, padx= 50, pady = 50)
|
|
@ -0,0 +1,117 @@
|
||||||
|
import customtkinter as Ctk
|
||||||
|
import os
|
||||||
|
import scripts.get_sys_info as system_code
|
||||||
|
from ..SaveData import SaveData
|
||||||
|
from tkinter import filedialog
|
||||||
|
|
||||||
|
|
||||||
|
FONT = "Berlin Sans FB"
|
||||||
|
|
||||||
|
class SwitchFolder(Ctk.CTkFrame):
|
||||||
|
def __init__(self,master, callback, **kwargs):
|
||||||
|
super().__init__(master, **kwargs)
|
||||||
|
system_code.load_json_file()
|
||||||
|
self.data_saver = SaveData()
|
||||||
|
self.my_font = Ctk.CTkFont(family=FONT, size=22)
|
||||||
|
self.callback = callback
|
||||||
|
self.error_txt = ""
|
||||||
|
self.input_path = None
|
||||||
|
self.output_path = None
|
||||||
|
self.img_paths = None
|
||||||
|
|
||||||
|
self.create_selection()
|
||||||
|
# the selection part
|
||||||
|
def create_selection(self):
|
||||||
|
# input
|
||||||
|
self.input_entry = Ctk.CTkEntry(self, placeholder_text="Input folder path", width=100, font=self.my_font)
|
||||||
|
self.input_btn = Ctk.CTkButton(self, text="Browse", width=100, command=self.get_folder_path, font=self.my_font)
|
||||||
|
# output
|
||||||
|
self.output_entry = Ctk.CTkEntry(self, placeholder_text="Output folder path", width=100, font=self.my_font)
|
||||||
|
self.output_btn = Ctk.CTkButton(self, text="Browse", width=100, command=self.get_project_path, font=self.my_font)
|
||||||
|
# start
|
||||||
|
self.start_btn = Ctk.CTkButton(self, text="Start", width=100, command=self.open_labeling, font=self.my_font)
|
||||||
|
self.error_label = Ctk.CTkLabel(self, text=self.error_txt, width=100, font=self.my_font)
|
||||||
|
self.place_label_selection()
|
||||||
|
|
||||||
|
def open_labeling(self):
|
||||||
|
source_path = self.input_entry.get()
|
||||||
|
self.output_path = self.output_entry.get()
|
||||||
|
if not os.path.exists(source_path):
|
||||||
|
self.error_txt = "Source Folder could not be found"
|
||||||
|
self.error_label.configure(text=self.error_txt)
|
||||||
|
elif not os.path.exists(self.output_path):
|
||||||
|
self.error_txt = "Output Folder could not be found"
|
||||||
|
self.error_label.configure(text=self.error_txt)
|
||||||
|
else:
|
||||||
|
self.data_saver.search_in_folder(self.output_path)
|
||||||
|
self.img_paths = self.load_images_into_array(source_path)
|
||||||
|
self.give_back()
|
||||||
|
|
||||||
|
def load_images_into_array(self, folder_path):
|
||||||
|
image_paths = []
|
||||||
|
for filename in os.listdir(folder_path):
|
||||||
|
if filename.endswith(system_code.img_format_options):
|
||||||
|
img_path = os.path.join(folder_path, filename)
|
||||||
|
image_paths.append(img_path)
|
||||||
|
return image_paths
|
||||||
|
|
||||||
|
def get_folder_path(self):
|
||||||
|
file_path = filedialog.askdirectory()
|
||||||
|
self.input_entry.delete(0, Ctk.END) # Delete any existing text in the Entry widget
|
||||||
|
self.input_entry.insert(0, file_path) # Insert the new text
|
||||||
|
|
||||||
|
def get_project_path(self):
|
||||||
|
video_path = filedialog.askdirectory()
|
||||||
|
self.output_entry.delete(0, Ctk.END) # Delete any existing text in the Entry widget
|
||||||
|
self.output_entry.insert(0, video_path) # Insert the new text
|
||||||
|
|
||||||
|
def give_back(self):
|
||||||
|
self.callback(self.img_paths, self.output_path)
|
||||||
|
|
||||||
|
def place_label_selection(self):
|
||||||
|
# output placing
|
||||||
|
self.input_entry.place(
|
||||||
|
relx=0.10,
|
||||||
|
rely=0.4,
|
||||||
|
anchor="w",
|
||||||
|
relwidth=0.5,
|
||||||
|
relheight=0.06
|
||||||
|
) # Position the converter button to the left
|
||||||
|
self.input_btn.place(
|
||||||
|
relx=0.90,
|
||||||
|
rely=0.4,
|
||||||
|
anchor="e",
|
||||||
|
relwidth=0.2,
|
||||||
|
relheight=0.06
|
||||||
|
) # Position the converter button to the left
|
||||||
|
|
||||||
|
# output placing
|
||||||
|
self.output_entry.place(
|
||||||
|
relx=0.10,
|
||||||
|
rely=0.5,
|
||||||
|
anchor="w",
|
||||||
|
relwidth=0.5,
|
||||||
|
relheight=0.06
|
||||||
|
) # Position the converter button to the left
|
||||||
|
self.output_btn.place(
|
||||||
|
relx=0.90,
|
||||||
|
rely=0.5,
|
||||||
|
anchor="e",
|
||||||
|
relwidth=0.2,
|
||||||
|
relheight=0.06
|
||||||
|
) # Position the converter button to the left
|
||||||
|
# button row
|
||||||
|
self.start_btn.place(
|
||||||
|
relx=0.5,
|
||||||
|
rely=0.6,
|
||||||
|
anchor="center",
|
||||||
|
relwidth=0.8,
|
||||||
|
relheight=0.06
|
||||||
|
) # Position the converter button to the left
|
||||||
|
self.error_label.place(
|
||||||
|
relx=0.5,
|
||||||
|
rely=0.7,
|
||||||
|
anchor="center",
|
||||||
|
relwidth=0.8,
|
||||||
|
relheight=0.06
|
||||||
|
) # Position the converter button to the left
|
Loading…
Reference in New Issue