Merge pull request #4 from UiA-IKT213-Gruppe4/Refactor_and_add_documentation

[WIP] Refactor and add documentation
This commit is contained in:
viljarb0
2025-10-20 13:05:50 +02:00
committed by GitHub
15 changed files with 512 additions and 7 deletions

177
.gitignore vendored
View File

@@ -1,2 +1,179 @@
cache/*
cache/**
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

26
.idea/runConfigurations/Run_project.xml generated Normal file
View File

@@ -0,0 +1,26 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run project" type="PythonConfigurationType" factoryName="Python">
<module name="assignment_2_improved" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="SDK_NAME" value="ikt213" />
<option name="WORKING_DIRECTORY" value="." />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/Main.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

40
.idea/workspace.xml generated
View File

@@ -1,7 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="5e47ddf2-717f-442d-8925-70fd842ea97b" name="Changes" comment="" />
<list default="true" id="5e47ddf2-717f-442d-8925-70fd842ea97b" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docs/UML/Class Diagram.puml" beforeDir="false" afterPath="$PROJECT_DIR$/docs/UML/Class Diagram.puml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -14,9 +20,12 @@
</list>
</option>
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 7
}]]></component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 7
}</component>
<component name="ProjectId" id="32YAxAi3VvMsvhBoa7ysNMLpCbv" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
@@ -25,8 +34,20 @@
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ModuleVcsDetector.initialDetectionPerformed": "true",
"Python.Main.executor": "Coverage",
"Python.Run project.executor": "Run",
"Python.demo.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "Refactor__and__add__documentation",
"junie.onboarding.icon.badge.shown": "true",
"last_opened_file_path": "/home/kim/Dokumenter/Universitet/Machine Vision/ikt213_project/src/Main.py",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.pluginManager",
"to.speed.mode.migration.done": "true",
"vue.rearranger.settings.migration": "true"
}
@@ -34,8 +55,8 @@
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-b598e85cdad2-JavaScript-PY-252.25557.178" />
<option value="bundled-python-sdk-ce6832f46686-7b97d883f26b-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-252.25557.178" />
<option value="bundled-js-predefined-d6986cc7102b-3aa1da707db6-JavaScript-PY-252.26830.99" />
<option value="bundled-python-sdk-164cda30dcd9-0af03a5fa574-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-252.26830.99" />
</set>
</attachedChunks>
</component>
@@ -47,10 +68,17 @@
<option name="presentableId" value="Default" />
<updated>1757587375498</updated>
<workItem from="1757587376542" duration="189000" />
<workItem from="1760178939983" duration="46000" />
<workItem from="1760179006753" duration="6999000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/ikt213_project$demo.coverage" NAME="demo Coverage Results" MODIFIED="1760179584389" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/ikt213_project$Main.coverage" NAME="Main Coverage Results" MODIFIED="1760194900099" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src" />
<SUITE FILE_PATH="coverage/ikt213_project$Run_project.coverage" NAME="Run project Coverage Results" MODIFIED="1760197454610" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
</component>
</project>

View File

@@ -6,7 +6,7 @@ pillow
numpy
## run
python main.py
python src/Main.py
## menu items
* file

View File

@@ -0,0 +1,60 @@
@startuml
class GUI{
This is a singleton
---
- ImageContainer _currentImage
- ImageManipulation[] _imageManipulations
+ none initialise()
}
class ImageContainer{
- imagedata _imageData
+ none loadImage(path: str)
+ imagedata getImage()
+ pair<int,int> getDimensions()
}
abstract class ImageManipulation{
+none manipulateImage(ImageContainer, Parameters)
+str[] getParameters()
+str getManipulationName()
}
class CropImage{
- str _name
}
class GreyScaleImage{
- str _name
}
class SmoothImage{
- str _name
}
class RotateImage{
- str _name
}
class HSVImage{
- str _name
}
class HueShiftImage{
- str _name
}
GUI ..> ImageContainer
GUI ..> ImageManipulation
CropImage ..|> ImageManipulation
GreyScaleImage ..|> ImageManipulation
SmoothImage ..|> ImageManipulation
RotateImage ..|> ImageManipulation
HSVImage ..|> ImageManipulation
HueShiftImage ..|> ImageManipulation
@enduml

View File

@@ -0,0 +1,7 @@
@startuml
[GUI]
[Image Processing]
[GUI] ..> [Image Processing]
@enduml

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
pillow~=11.3.0
numpy~=2.3.1

75
src/GUI.py Normal file
View File

@@ -0,0 +1,75 @@
import tkinter as tk
from tkinter import filedialog
from PIL import ImageTk
from ImageContainer import ImageContainer
from ImageManipulation.ManipulationList import *
class GUI:
"""The GUI class responsible for the main application GUI.
@warning This class is a singleton.
"""
_instance = None
# If the GUI has been initialised.
_isInitialised = False
_currentImage = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(GUI, cls).__new__(cls)
return cls._instance
def initialise(self):
"""Initialise the GUI."""
if self._isInitialised:
return
else:
self._isInitialised = True
# Main window
root = tk.Tk()
root.title("Image Viewer")
root.geometry("800x600")
root.config(bg="white")
icon = tk.PhotoImage(file='icon.png')
root.tk.call('wm', 'iconphoto', root._w, icon)
root.minsize(800, 600)
# Menus
menu = tk.Menu(root)
root.config(menu=menu)
file_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Open Image", command=lambda: self._openImage())
file_menu.add_command(label="Save Image", command=lambda: _save_image(OPENED_IMAGE))
file_menu.add_command(label="Exit", command=root.quit)
test_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label="Filters", menu=test_menu)
for manipulation in GetImageManipulationList():
test_menu.add_command(label=manipulation.getManipulationName(), command=lambda: manipulation.manipulateImage())
# Frame to hold image
imgframe = tk.Frame(root, width=500, height=500, bg="lightgray", relief="sunken", bd=2)
imgframe.pack(side="top", pady=10)
# Label to display image
image_label = tk.Label(imgframe, width=500, height=500, bg="white")
image_label.pack(expand=True)
root.mainloop()
def _openImage(self) -> None:
file_path = filedialog.askopenfilename(
filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif *.bmp")]
)
if not file_path:
return
self._currentImage = ImageContainer()
self._currentImage.loadImage(file_path)

25
src/ImageContainer.py Normal file
View File

@@ -0,0 +1,25 @@
import cv2
from PIL import Image, ImageTk
class ImageContainer:
_imageData = None
_path = None
def loadImage(self, path: str) -> None:
""" Load image file from the path.
:param path: Path to the image file.
"""
imgcv2 = cv2.imread(path)
height, width, channels = imgcv2.shape
# Open and resize image (optional)
_imageData = Image.open(path)
_imageData = _imageData.resize((height, width), Image.LANCZOS) # Resize to fit frame
_path = path
print("Opened image:", path)
def getDimensions(self) -> tuple[int,int]:
return self._imageData.shape

View File

@@ -0,0 +1,39 @@
from .ImageManipulation import ImageManipulation
from ..ImageContainer import ImageContainer
from typing import Any, List
class CropImage(ImageManipulation):
"""Concrete implementation for cropping images."""
def __init__(self):
# You can initialize any instance variables here
self.manipulation_name = "Crop"
def manipulateImage(self, image: ImageContainer, parameters: Any) -> None:
"""
Manipulate the given image using the provided parameters.
Args:
image: The image to manipulate
parameters: Parameters for the manipulation (e.g., crop coordinates)
"""
pass
def getParameters(self) -> List[str]:
"""
Get the list of parameters required for cropping.
Returns:
List of parameter names
"""
return ["width", "height"]
def getManipulationName(self) -> str:
"""
Get the name of this manipulation operation.
Returns:
The name of the manipulation
"""
return "Crop Image"

View File

@@ -0,0 +1,42 @@
from abc import ABC, abstractmethod
from typing import Any
import sys
sys.path.append('..')
from ..ImageContainer import ImageContainer
class ImageManipulation(ABC):
"""Abstract base class for image manipulation operations."""
@abstractmethod
def manipulateImage(self, image: ImageContainer, parameters: Any) -> None:
"""
Manipulate the given image using the provided parameters.
Args:
image: The image to manipulate
parameters: Parameters for the manipulation
"""
pass
@abstractmethod
def getParameters(self) -> list[str]:
"""
Get the list of parameters required for this manipulation.
Returns:
List of parameter names
"""
pass
@abstractmethod
def getManipulationName(self) -> str:
"""
Get the name of this manipulation operation.
Returns:
The name of the manipulation
"""
pass

View File

@@ -0,0 +1,4 @@
from src.ImageManipulation.CropImage import *
def GetImageManipulationList() -> list:
return [CropImage()]

6
src/Main.py Normal file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/python
from GUI import GUI
if __name__ == "__main__":
gui = GUI()
gui.initialise()