Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
stages:
- test-csv
- test-image_extensions

variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

cache:
key: "$CI_JOB_NAME"
paths:
- .cache/pip

before_script:
- apt-get update
- apt-get install -y python3-pyqt5 xvfb
- python3 -m venv venv
- source venv/bin/activate
- python3 -m pip install --upgrade pip
- pip3 install -r requirements.txt
- pip install pytest pytest-qt
- export PYTHONPATH=$PYTHONPATH:$CI_PROJECT_DIR/app

test-csv:
stage: test-csv
image: python:3.8
script:
- xvfb-run -a --server-args="-screen 0 1024x768x24" pytest test/test_generate_csv.py --tb=short -v

test-image_extensions:
stage: test-image_extensions
image: python:3.8
script:
- xvfb-run -a --server-args="-screen 0 1024x768x24" pytest test/test_img_paths.py --tb=short -v

Empty file added app/.gitkeep
Empty file.
25 changes: 18 additions & 7 deletions main.py → app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
import numpy as np
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QIntValidator, QKeySequence
from PyQt5.QtGui import QPixmap, QIntValidator, QKeySequence, QImageReader
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QCheckBox, QFileDialog, QDesktopWidget, QLineEdit, \
QRadioButton, QShortcut, QScrollArea, QVBoxLayout, QGroupBox, QFormLayout
from xlsxwriter.workbook import Workbook


def get_img_paths(dir, extensions=('.jpg', '.png', '.jpeg')):
def get_img_paths(dir, extensions=tuple(['.' + extension.data().decode('utf-8') for
extension in QImageReader.supportedImageFormats()])):
'''
:param dir: folder with files
:param extensions: tuple with file endings. e.g. ('.jpg', '.png'). Files with these endings will be added to img_paths
Expand Down Expand Up @@ -129,7 +130,7 @@ def init_ui(self):
self.next_button.clicked.connect(self.continue_app)
self.next_button.setObjectName("blueButton")

# Erro message
# Error message
self.error_message.setGeometry(20, 810, self.width - 20, 20)
self.error_message.setAlignment(Qt.AlignCenter)
self.error_message.setStyleSheet('color: red; font-weight: bold')
Expand Down Expand Up @@ -211,7 +212,7 @@ def pick_labels_file(self):
self.numLabelsInput.setText(str(len(labels)))
self.generate_label_inputs()

# fill the input fileds with loaded labels
# fill the input fields with loaded labels
for input, label in zip(self.label_inputs, labels):
input.setText(label)

Expand Down Expand Up @@ -250,6 +251,7 @@ def generate_label_inputs(self):
self.groupBox.setLayout(self.formLayout)
self.scroll.setWidget(self.groupBox)
self.scroll.setWidgetResizable(True)

def centerOnScreen(self):
"""
Centers the window on the screen.
Expand Down Expand Up @@ -330,6 +332,7 @@ def __init__(self, labels, input_folder, mode):
self.curr_image_headline = QLabel('Current image', self)
self.csv_note = QLabel('(csv will be also generated automatically after closing the app)', self)
self.csv_generated_message = QLabel(self)
self.german_format_check_box = QCheckBox("Save as German format", self)
self.show_next_checkbox = QCheckBox("Automatically show next image when labeled", self)
self.generate_xlsx_checkbox = QCheckBox("Also generate .xlsx file", self)

Expand All @@ -355,6 +358,7 @@ def init_ui(self):

# "create xlsx" checkbox
self.generate_xlsx_checkbox.setChecked(False)
self.german_format_check_box.setGeometry(self.img_panel_width + 140, 560, 300, 20)
self.generate_xlsx_checkbox.setGeometry(self.img_panel_width + 140, 606, 300, 20)

# image headline
Expand Down Expand Up @@ -390,6 +394,8 @@ def init_ui(self):
ui_line.setGeometry(20, 98, 1012, 1)
ui_line.setStyleSheet('background-color: black')

# init checkbox to generate the csv in german format add sep with replacing the delimiter

# apply custom styles
try:
styles_path = "./styles.qss"
Expand Down Expand Up @@ -474,7 +480,7 @@ def set_label(self, label):
elif self.mode == 'move':
# label was in assigned labels, so I want to remove it from label folder,
# but this was the last label, so move the image to input folder.
# Don't remove it, because it it not save anywehre else
# Don't remove it, because it not save anywhere else
if img_name not in self.assigned_labels.keys():
shutil.move(os.path.join(self.input_folder, label, img_name), self.input_folder)
else:
Expand Down Expand Up @@ -601,7 +607,12 @@ def generate_csv(self, out_filename):
csv_file_path = os.path.join(path_to_save, out_filename) + '.csv'

with open(csv_file_path, "w", newline='') as csv_file:
writer = csv.writer(csv_file, delimiter=',')
if(self.german_format_check_box.isChecked()):
delimiter = ';'
else:
delimiter = ','

writer = csv.writer(csv_file, delimiter=delimiter)

# write header
writer.writerow(['img'] + self.labels)
Expand Down Expand Up @@ -640,7 +651,7 @@ def csv_to_xlsx(self, csv_file_path):
def set_button_color(self, filename):
"""
changes color of button which corresponds to selected label
:filename filename of loaded image:
:filename of loaded image:
"""

if filename in self.assigned_labels.keys():
Expand Down
Empty file added test/.gitkeep
Empty file.
51 changes: 51 additions & 0 deletions test/test_generate_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import pytest
from PyQt5.QtWidgets import QApplication
from app.main import LabelerWindow

@pytest.fixture
def app(qtbot):
test_app = LabelerWindow(labels=['landschaft', 'menschen'], input_folder='test_folder', mode='csv')
qtbot.addWidget(test_app)
return test_app

def test_generate_csv_german(app, qtbot, tmpdir):
# Set up the environment for the test
app.german_format_check_box.setChecked(True)
app.assigned_labels = {
'test1.jpg': ['landschaft'],
'test2.BMP': ['landschaft'],
'test3.jpg': ['menschen'],
'test4.gif': ['menschen']
}
output_csv = "test_output"
app.generate_csv(output_csv)

with open(r"test_folder/output/" + output_csv + ".csv") as f:
lines = f.readlines()

assert lines[0].strip() == 'img;landschaft;menschen'
assert lines[1].strip() == 'test1.jpg;1;0'
assert lines[2].strip() == 'test2.BMP;1;0'
assert lines[3].strip() == 'test3.jpg;0;1'
assert lines[4].strip() == 'test4.gif;0;1'

def test_generate_csv_no_german_format(app, qtbot, tmpdir):
# Set up the environment for the test
app.german_format_check_box.setChecked(False)
app.assigned_labels = {
'test1.jpg': ['landschaft'],
'test2.BMP': ['landschaft'],
'test3.jpg': ['menschen'],
'test4.gif': ['menschen']
}
output_csv = "test_output"
app.generate_csv(output_csv)

with open(r"test_folder/output/" + output_csv + ".csv") as f:
lines = f.readlines()

assert lines[0].strip() == 'img,landschaft,menschen'
assert lines[1].strip() == 'test1.jpg,1,0'
assert lines[2].strip() == 'test2.BMP,1,0'
assert lines[3].strip() == 'test3.jpg,0,1'
assert lines[4].strip() == 'test4.gif,0,1'
38 changes: 38 additions & 0 deletions test/test_img_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import os
import pytest
from app.main import get_img_paths
from PyQt5.QtGui import QImageReader

def test_supported_image_formats(tmpdir):
# Get supported image formats from QImageReader
# This will return a tuple of which represent the supported image file extensions (e.g., '.jpg', '.png')
expected_formats = tuple('.' + extension.data().decode('utf-8') for extension in QImageReader.supportedImageFormats())

# Create a temporary directory with dummy image files of each supported format
# Using the tmpdir fixture from pytest to create a temporary directory that will be automatically cleaned up
for fmt in expected_formats:
# Construct the file path for a dummy image file with the given format
file_path = os.path.join(tmpdir, f'test_image{fmt}')
# Create an empty file with the specified format
open(file_path, 'a').close()

# Get image paths using the updated get_img_paths function
# This function should return a list of paths to the image files in the directory that match the supported formats
img_paths = get_img_paths(tmpdir)

# Ensure all supported image formats are detected
# The number of detected image paths should match the number of supported formats
assert len(img_paths) == len(expected_formats)
# Verify that each detected image path has an extension that is in the expected formats
for path in img_paths:
assert os.path.splitext(path)[1] in expected_formats

def test_no_images_found(tmpdir):
# Create a temporary directory with no image files
# Using the tmpdir fixture from pytest to create a temporary directory
# This time, we do not create any image files in the directory
img_paths = get_img_paths(tmpdir)

# Ensure no images are found
# Since no image files were created in the directory, get_img_paths should return an empty list
assert img_paths == []
Empty file added test_folder/.gitkeep
Empty file.
Binary file added test_folder/test1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test_folder/test2.BMP
Binary file not shown.
Binary file added test_folder/test3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test_folder/test4.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.