Compare commits

..

7 Commits

Author SHA1 Message Date
f662e4fd1b fix misake in README.md 2025-11-15 17:09:59 +09:00
c62cc8dcd8 test 2025-11-15 17:07:45 +09:00
88323dc82d Update README.md
줄 넘기기를 수정
2025-11-15 17:01:24 +09:00
a6c1d1f111 설치가 안된 라이브러리 ( OCR 관련 ) 설치
requirements.txt 업데이트.
2025-11-15 16:48:31 +09:00
00f9bf4ee7 Readme 를 조금 수정 2025-11-15 16:11:38 +09:00
787b0aaf4a .gitignore 적용 2025-11-15 16:00:39 +09:00
d79c10b975 일단 커밋. 오랫동안 커밋을 안해서 꼬였다.
리팩토리 중.
2025-11-15 15:59:49 +09:00
8 changed files with 558 additions and 297 deletions

192
.gitignore vendored Normal file
View File

@@ -0,0 +1,192 @@
# Created by https://www.toptal.com/developers/gitignore/api/python,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=python,linux
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### 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,linux

BIN
EPoleDB.xlsx Normal file

Binary file not shown.

View File

@@ -12,7 +12,6 @@ def main(argc, argv):
app.setQuitOnLastWindowClosed(True)
main_window = MgrUtilityPoleUI.MyApp()
main_window.show()
sys.exit(app.exec_())

View File

@@ -8,59 +8,35 @@ import VWorldAPIs as vwapi
import JusoMngr
mCurPath = "" # 스크립트가 실행되는 경로
mArcPath = "Zips" # 압축파일이 있는 폴더, 하위를 훑는다.
mXlsPath = "" # 엑셀 파일이 있는 경로, 틀리면 안됨.
mImgPath = "/Volumes/ExSSD/Working/Images" # 이미지가 들어있는 폴더, 이 아래에 A,B,Pole 등이 있어야 한다.
# 분류작업을 위해 임시로 생성
mTempPath = "/Volumes/ExSSD/Working/TmpImgs"
# "/Volumes/ExSSD/Working/Images" # 이미지가 들어있는 폴더, 이 아래에 A,B,Pole 등이 있어야 한다.
mImgPath = ""
#def main(argc, argv):
# if argc != 2:
# printUsage()
# return
# 기준 경로, 사용할 경로
# 사용할 경로가 상대경로면 기준 경로를 이용해 계산,
# 사용할 경로가 절대경로만 경로가 유효한지 확인해서 그대로 반환, 아니라면 기준경로 반환
# 기준경로는 따로 입력되는게 없으면 실행 경로
def GetAbsPath(PathCri, PathTrg):
if True == util.IsEmptyStr(PathCri):
PathCri = os.getcwd()
PathRet = PathCri
if True == os.path.isabs(PathTrg):
PathRet = os.path.join(PathCri, PathTrg)
def WorkingFolderCheck(path, xlsPath):
if False == os.path.exists(path):
print(f"Error: Working Folder is not exist. {path}")
return False
pathList = ["A", "B", "Pole"]
for folder in pathList:
fullPath = os.path.join(path, folder)
if False == os.path.isdir(fullPath):
print(f"Error: {folder} Folder is not exist. {fullPath}")
return False
mImgPath = path
mCurPath = os.getcwd()
if False == os.path.exists(xlsPath):
mXlsPath = os.getcwd()
else:
PathRet = PathTrg
mXlsPath = xlsPath
if False == os.path.exists(PathTrg):
PathRet = PathCri
return PathTrg
def ExtractArchives(pathArc, pathTrg):
if False == os.path.exists(pathArc):
return []
ZIPList = os.listdir(pathArc)
return True
retUNZipList = []
for zipFile in ZIPList:
zipName, zipExt = os.path.splitext(zipFile)
trgPath = os.path.join(pathTrg, zipName)
# 압축을 푼 폴더가 존재한다면 넘어간다. 이후 보강해야 함
if True == os.path.exists(trgPath):
continue
zipPath = os.path.join(pathArc, zipFile)
util.ExtractZIP(zipPath, trgPath)
retUNZipList.append(trgPath)
return retUNZipList
def GetPoleInfos(xls, nRow):
if None == xls or False == isinstance(xls, myxl.PoleXLS):
@@ -70,27 +46,6 @@ def GetPoleInfos(xls, nRow):
return retList
def GetJPGFileList(listUNZip, pathImage):
trgDirList = []
# 압축 푼 폴더가 있으면 그것만, 아니면 이미지 폴더 전부를 다
if len(listUNZip) > 0:
trgDirList = listUNZip
else:
trgDirList = util.GetSubDirectories(pathImage)
retImgList = []
for dirName in trgDirList:
imgSubDirPath = os.path.join(pathImage, dirName)
contents = os.listdir(imgSubDirPath)
for item in contents:
name, ext = os.path.splitext(item)
if ext.lower() == '.jpg':
imgPath = os.path.join(imgSubDirPath, item)
retImgList.append(imgPath)
return retImgList
# 필요한 이미지 정보를 모아 한 시트에 전부 넣는다.
def CollectImageData(xls, sheetTrg, listImg, pathImgDir):
if None == xls or False == isinstance(xls, myxl.PoleXLS) or True == util.IsEmptyStr(sheetTrg):
@@ -107,11 +62,13 @@ def CollectImageData(xls, sheetTrg, listImg, pathImgDir):
continue
all_date, lat, lon = exEXIF.GetDateInfo(item)
pathRel = os.path.relpath( item, pathImgDir )
pathRel = os.path.relpath( item, pathImgDir)
level = util.GetParentDirName(item, 1)
if level == "Thumb":
continue
print(f"DBG : {nIdx}, CollectImageData : {item}, {pathImgDir}, {pathRel}")
# 0 : 지번, 1 : 구역, 2 : 도로명, 3 : 도로명 구역
juso = vwapi.GetJusofromGPS(lat, lon)
@@ -173,8 +130,6 @@ def CollectPoleInfoData(xls, TrgSheetName, DBSheetName, pathImgDir):
if listValues[7] == "" or listValues[7] == "0.0,0.0":
continue
print( listValues )
xls.SetCellValueStr(TrgSheetName, 1, nTrgRow, str(nTrgRow -1))
xls.SetCellValueStr(TrgSheetName, 2, nTrgRow, listValues[7])
@@ -264,40 +219,44 @@ def CollectPoleData( xls, TrgSheetName, InfoSheetName, DBSheetName, pathImgDir )
return nTrgRow
def main():
# 경로를 정리하여 절대 경로로 변환해서 저장한다.
CurPath = GetAbsPath(mCurPath, "")
ArcDirPath = GetAbsPath(CurPath, mArcPath)
XlsDirPath = GetAbsPath(CurPath, mXlsPath)
ImgDirPath = GetAbsPath(CurPath, mImgPath)
# 압축해제
listUNZip = ExtractArchives(ArcDirPath, ImgDirPath)
def GetJPGFileLIst(pathImageDir):
trgDirList = util.GetSubDirectories(pathImageDir)
#print(f"DBG : GetJPGFileLIst : {pathImageDir}, {trgDirList}")
#input("Press Enter to continue...")
retImgList = []
for dirName in trgDirList:
if dirName.lower() == "thumb":
continue
imgSubDirPath = os.path.join(pathImageDir, dirName)
contents = os.listdir(imgSubDirPath)
for item in contents:
name, ext = os.path.splitext(item)
if ext.lower() == '.jpg':
imgPath = os.path.join(imgSubDirPath, item)
retImgList.append(imgPath)
return retImgList
def Process(PathImg, PathXLS):
# 이미지 파일 목록 작성
trgImgList = GetJPGFileList(listUNZip, ImgDirPath)
trgImgList = GetJPGFileLIst(PathImg)
# 엑셀을 연다.
XLSPath = os.path.join(XlsDirPath, "EPoleDB.xlsx")
XLSPath = os.path.join(PathXLS, "EPoleDB.xlsx")
tempxls = myxl.PoleXLS(XLSPath)
tempxls.DBXLSOpen()
# 이미지 정보를 전부 모아서 한 시트에 저장
CollectImageData(tempxls, "ImgInfo", trgImgList, ImgDirPath)
CollectImageData(tempxls, "ImgInfo", trgImgList, PathImg)
# 모아 놓은 이미지 정보에서 점봇대 정보를 골라서 저장
CollectPoleInfoData(tempxls, "PoleInfo", "ImgInfo", ImgDirPath)
CollectPoleInfoData(tempxls, "PoleInfo", "ImgInfo", PathImg)
# 점봇대 정보 시트를 바탕으로 제보받은 이미지를 판별, 계산하여 저장
CollectPoleData(tempxls, "Poles", "PoleInfo", "ImgInfo", ImgDirPath)
CollectPoleData(tempxls, "Poles", "PoleInfo", "ImgInfo", PathImg)
tempxls.DBXLSClose()
def printUsage():
print("Usage : python main.py <Image Folder Path> <Excel File Name>")
# For Main Loop
if __name__ == '__main__':
# argc = len(sys.argv)
# argv = sys.argv
# main(argc, argv)
main()
tempxls.DBXLSClose()

View File

@@ -1,14 +1,17 @@
import sys
import os
import shutil
import sys
import UtilPack as util
import VWorldAPIs as vw
import ExtEXIFData as exif
import MgrUtilityPoleConverter as mgrConv
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton, QVBoxLayout, QLineEdit, QHBoxLayout, QListWidget, QTableWidget, QRadioButton, QLabel, QFileDialog, QListWidgetItem
from PyQt5.QtGui import QPixmap, QTransform
from PyQt5.QtCore import Qt, QSettings, QRect
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton, QVBoxLayout, QLineEdit, \
QHBoxLayout, QTableWidget, QTableWidgetItem, QAbstractItemView, QHeaderView, QFileDialog, \
QListWidget, QListWidgetItem, QMessageBox
from PyQt5.QtGui import QResizeEvent, QCloseEvent, QColor
QApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
class MyApp(QMainWindow):
@@ -16,134 +19,10 @@ class MyApp(QMainWindow):
super().__init__()
self.initUI()
self.loadINI()
self.initDB()
#
def closeEvent(self, a0: 'QCloseEvent | None'):
filenameLog = f"{util.GetCurrentTime()}_DbgLog.txt"
pathDbgLog = os.path.join( self.pathLog, filenameLog)
util.SaveDbgMessages(pathDbgLog)
self.saveINI()
super().closeEvent(a0)
#
def resizeEvent(self, a0: 'QResizeEvent | None'):
pass
#
def loadINI(self):
pass
#
def saveINI(self):
pass
#
def initDB(self):
pass
#
def MakeUI_Left(self):
layout = QVBoxLayout()
layout_top = QHBoxLayout()
self.listWidget_Folders = QListWidget()
self.listWidget_Folders.setFixedHeight(100)
layout_Btns = QVBoxLayout()
btn_FolderAdd = QPushButton("폴더 추가")
btn_FolderAdd.clicked.connect(self.on_btnFolderAdd_clicked)
btn_FolderDel = QPushButton("폴더 삭제")
btn_FolderDel.clicked.connect(self.on_btnFolderDel_clicked)
btn_FolderParss = QPushButton("폴더 파싱")
btn_FolderParss.clicked.connect(self.on_btnFolderParse_clicked)
layout_Btns.addWidget(btn_FolderAdd)
layout_Btns.addWidget(btn_FolderDel)
layout_Btns.addWidget(btn_FolderParss)
def closeEvent(self, event):
event.accept()
layout_top.addWidget(self.listWidget_Folders)
layout_top.addLayout(layout_Btns)
self.tableWidget_Src = QTableWidget()
self.tableWidget_Src.verticalHeader().setVisible(False)
self.tableWidget_Src.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.tableWidget_Src.setSortingEnabled(True)
self.tableWidget_Src.setColumnCount(5)
self.tableWidget_Src.setHorizontalHeaderLabels(["Path", "Title", "H.ID", "Img Cnt", "Dw Cnt"])
self.tableWidget_Src.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tableWidget_Src.horizontalHeader().sectionClicked.connect(self.on_tableWidget_Src_headerClicked)
self.tableWidget_Src.setSelectionBehavior(QAbstractItemView.SelectRows)
self.tableWidget_Src.setSelectionMode(QAbstractItemView.SingleSelection)
self.tableWidget_Src.itemSelectionChanged.connect(self.on_tableWidget_Src_itemSelectionChanged)
layout.addLayout(layout_top)
layout.addWidget(self.tableWidget_Src)
return layout
#
def MakeUI_Center(self):
layout = QVBoxLayout()
btn_Emptyfolder = QPushButton("빈 폴더 이동")
btn_Emptyfolder.clicked.connect(self.on_btn_Emptyfolder_clicked)
btn_ChkDuplicate = QPushButton("중복 검사 및 제거")
btn_ChkDuplicate.clicked.connect(self.on_btn_ChkDuplicate_clicked)
btn_Archive = QPushButton("압축 및 데이터 저장")
btn_Archive.clicked.connect(self.on_btn_Archive_clicked)
btn_EnterCalibre = QPushButton("컬리버에 삽입")
btn_EnterCalibre.clicked.connect(self.on_btn_EnterCalibre_clicked)
layout.addWidget(btn_Emptyfolder)
layout.addWidget(btn_ChkDuplicate)
layout.addWidget(btn_Archive)
layout.addWidget(btn_EnterCalibre)
return layout
#
def MakeUI_Right(self):
layout_top = QHBoxLayout()
self.edit_DB = QLineEdit(self)
self.edit_DB.setReadOnly(True)
self.edit_DB.setText("...")
btn_DB = QPushButton("...")
btn_DB.setFixedWidth(50)
btn_DB.clicked.connect(self.on_btnDB_clicked)
layout_top.addWidget(self.edit_DB)
layout_top.addWidget(btn_DB)
self.tableWidget_DB = QTableWidget()
self.tableWidget_DB.verticalHeader().setVisible(False)
self.tableWidget_DB.setColumnCount(5)
self.tableWidget_DB.setHorizontalHeaderLabels(["Title", "Author", "Cover", "Exts", "ID"])
self.tableWidget_DB.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tableWidget_DB.setSelectionBehavior(QAbstractItemView.SelectRows)
self.tableWidget_DB.setSelectionMode(QAbstractItemView.SingleSelection)
self.tableWidget_DB.itemSelectionChanged.connect(self.on_tableWidget_DB_itemSelectionChanged)
layout = QVBoxLayout()
layout.addLayout(layout_top)
layout.addWidget(self.tableWidget_DB)
return layout
#
def MakeUI(self):
layout_L = self.MakeUI_Left()
layout_C = self.MakeUI_Center()
layout_R = self.MakeUI_Right()
# 레이아웃 설정
layout = QHBoxLayout()
layout.addLayout(layout_L, stretch = 10)
layout.addLayout(layout_C, stretch = 1)
layout.addLayout(layout_R, stretch = 10)
return layout
#
def initUI(self):
layout = self.MakeUI()
@@ -151,17 +30,178 @@ class MyApp(QMainWindow):
central_widget = QWidget()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.setWindowTitle('Manga Database')
self.resize(800, 600)
self.setWindowTitle('Poles Database')
self.move(100, 300)
self.show()
# 테이블의 특정 행에 배경색을 설정한다
# nRow: 배경색을 설정할 행 번호, color: 배경색 (Qt.GlobalColor)
def SrcTableRowBgColor(self, nRow:int, color:Qt.GlobalColor) -> None:
for col in range(self.tableWidget_Src.columnCount()):
item = self.tableWidget_Src.item(nRow, col)
if item:
item.setBackground(Qt.GlobalColor(color))
def loadINI(self):
print("Load INI")
def saveINI(self):
print("Save INI")
def MakeUI_Left_Top(self):
label_SrcPath = QLabel("Source Path")
self.edit_SrcPath = QLineEdit("", self)
self.edit_SrcPath.setReadOnly(True)
btn_SrcPath_OK = QPushButton("...", self)
btn_SrcPath_OK.clicked.connect(self.on_click_SourcePath)
self.edit_SrcPath.setEnabled(False)
btn_SrcPath_OK.setEnabled(False)
layout01 = QHBoxLayout()
layout01.addWidget(label_SrcPath)
layout01.addWidget(self.edit_SrcPath)
layout01.addWidget(btn_SrcPath_OK)
label_ImgPath = QLabel("Image Path")
self.edit_ImgPath = QLineEdit("", self)
self.edit_ImgPath.setReadOnly(True)
btn_ImgPath_OK = QPushButton("...", self)
btn_ImgPath_OK.clicked.connect(self.on_click_ImagePath)
layout02 = QHBoxLayout()
layout02.addWidget(label_ImgPath)
layout02.addWidget(self.edit_ImgPath)
layout02.addWidget(btn_ImgPath_OK)
self.btn_ParseImages = QPushButton("Parse!!", self)
self.btn_ParseImages.clicked.connect(self.on_click_btn_ParseImages)
self.btn_ParseImages.setMaximumWidth(410)
layout = QVBoxLayout()
layout.addLayout(layout01)
layout.addLayout(layout02)
layout.addWidget(self.btn_ParseImages)
return layout
def MakeUI_Left_Mid(self):
# 이미지 목록
self.list_Images = QListWidget(self)
self.list_Images.itemSelectionChanged.connect(self.on_SelChanged_ImageList)
self.list_Images.setMinimumWidth(100)
self.list_Images.setMaximumWidth(200)
# 이미지 정보 테이블
self.list_Info = QListWidget(self)
self.list_Info.setMinimumWidth(100)
self.list_Info.setMaximumWidth(200)
layout_r = QVBoxLayout()
layout_r.addWidget(self.list_Info)
layout = QHBoxLayout()
layout.addWidget(self.list_Images)
layout.addLayout(layout_r)
return layout
def MakeUI_left_Down(self):
label_ExlPath = QLabel("DB(xlsx) Path")
self.edit_ExlPath = QLineEdit("", self)
self.edit_ExlPath.setReadOnly(True)
btn_ExlPath_OK = QPushButton("OK", self)
layout01 = QHBoxLayout()
layout01.addWidget(label_ExlPath)
layout01.addWidget(self.edit_ExlPath)
layout01.addWidget(btn_ExlPath_OK)
btn_Exl_Export = QPushButton("Export to EXCEL", self)
btn_Exl_Export.clicked.connect(self.on_click_btn_ExportToExcel)
layout = QVBoxLayout()
layout.addLayout(layout01)
layout.addWidget(btn_Exl_Export)
return layout
def MakeUI_Left(self):
lsyout_lefttop = self.MakeUI_Left_Top()
layout_leftmid = self.MakeUI_Left_Mid()
layout_leftDwn = self.MakeUI_left_Down()
layout = QVBoxLayout()
layout.addLayout(lsyout_lefttop)
layout.addLayout(layout_leftmid)
layout.addLayout(layout_leftDwn)
return layout
def MakeUI(self):
layout_L = self.MakeUI_Left()
# show Image in middle Layout
self.label_Image = QLabel(self)
# Load Default Image
pixmap = QPixmap("layoutImg.jpeg")
scaled_Pix = pixmap.scaledToWidth(450)
self.label_Image.setPixmap(scaled_Pix)
self.label_Image.setMaximumWidth(450)
# Show GPS Mapped map in Right Layout
self.webview_map = QWebEngineView()
self.webview_map.setFixedWidth(450)
self.webview_map.setUrl(QUrl("file:///Users/minarinari/Workspace/Python/UtilityPole_Info/sample_Event.html"))
# 레이아웃 설정
layout = QHBoxLayout()
layout.addLayout(layout_L)
layout.addWidget(self.label_Image)
layout.addWidget(self.webview_map)
return layout
def on_click_SourcePath(self):
folder_path = QFileDialog.getExistingDirectory(self, '폴더 선택', '')
self.edit_SrcPath.setText(folder_path)
def on_click_ImagePath(self):
folder_path = QFileDialog.getExistingDirectory(self, '폴더 선택', '')
self.edit_ImgPath.setText(folder_path)
def on_click_btn_ParseImages(self):
self.btn_ParseImages.setEnabled(False)
pathImage= self.edit_ImgPath.text()
mgrConv.Process(pathImage,"/home/gerd/Workspace/Python/UtilityPole_Info" )
self.btn_ParseImages.setEnabled(True)
print("Parse END!")
def on_click_btn_ExportToExcel(self):
print("Excel!!")
def on_SelChanged_ImageList(self):
items = self.list_Images.selectedItems()
for item in items:
listData = item.data(Qt.UserRole)
self.list_Info.clear()
for content in listData:
row = QListWidgetItem(content)
self.list_Info.addItem(row)
pixmap = QPixmap(listData[0])
szLabel = self.label_Image.size()
resized_pixmap = pixmap.scaled(100, 100)
rotated_pixmap = resized_pixmap.transformed(QTransform().rotate(90))
self.label_Image.setPixmap(pixmap)
# def open_image(self):
# # 이미지 파일 열기 다이얼로그
# file_name, _ = QFileDialog.getOpenFileName(self, "Open Image File", "", "Images (*.png *.xpm *.jpg)")
# if file_name:
# # QPixmap을 사용해 이미지를 QLabel에 설정
# pixmap = QPixmap(file_name)
# self.label.setPixmap(pixmap)
# self.label.setScaledContents(True)

View File

@@ -2,19 +2,19 @@
과정
1. 여러 명의 인원이 전신주 및 공중선 사진을 찍는다.
2. 찍은 사진을 압축하여 네이버 메일을 통해 지정된 계정으로 이메일을 보낸다.
2. 찍은 사진을 압축하여 메일을 통해 지정된 계정으로 이메일을 보낸다.
3. 이메일을 통해 받은 파일을 어떤 한 폴더에 모아 저장한다.
4 스크립트 동작
4.1 저장 폴더를 읽어 임시 폴더에 압축을 푼다. 이미 풀려 있으면 넘어간다.
4.2 임시 폴더의 이미지를 읽어 엑셀 파일에 리스트업 한다.
4.3 연번, 점검일자, 구역, 전신주번호, 현장사진, 등급부여. 비고
4. 스크립트 동작
<br>4.1. 저장 폴더를 읽어 임시 폴더에 압축을 푼다. 이미 풀려 있으면 넘어간다.
<br>4.2. 임시 폴더의 이미지를 읽어 엑셀 파일에 리스트업 한다.
<br>4.3. 연번, 점검일자, 구역, 전신주번호, 현장사진, 등급부여. 비고
5. 생성된 엑셀 파일을 권한이 있는 사람이 열어 전신주의 등급을 분류한다.
6. 스크립트 동작
6.1 등급 분류를 완료한 엑셀 파일을 읽는다.
6.2 전신주 번호를 통해 각 전신주 사진을 지도에 매핑
6.3 분류된 등급에 따라 다른 색깔을 칠한다.
고민해야 할 걸
- 전신주 번호를 GPS 좌표와 매핑시켜 제보 사진만으로도 전신부 번호를 검색할 수 있어햐 한다.
<br>6.1. 등급 분류를 완료한 엑셀 파일을 읽는다.
<br>6.2. 전신주 번호를 통해 각 전신주 사진을 지도에 매핑
<br>6.3. 분류된 등급에 따라 다른 색깔을 칠한다.
고민해야 할 점
- 전신주 번호를 GPS 좌표와 매핑시켜 제보 사진만으로도 전신주 번호를 검색할 수 있어햐 한다.
- 한번의 실행으로 위 과정을 통합할 수 있는 UI

View File

@@ -14,11 +14,27 @@ from pathlib import Path
m_dbgLevel = 0
listDbgStr: list[str] = []
#for debug
def DbgOut(strInput:str, bPrint:bool = False):
strMsg = (f"{GetCurrentTime()} : {strInput}")
listDbgStr.append(strMsg)
if True == bPrint:
print(strMsg)
#
def IsEmptyStr(string: str) -> bool:
temp = f"{string}"
return 0 == len(temp.strip())
def printDbgMessages():
for line in listDbgStr:
print(line)
#
def SaveDbgMessages(Path: str):
try:
with open(Path, 'w') as file:
for line in listDbgStr:
file.write(line + "\n")
except IOError:
DbgOut(f"Error: Could not write to the file at {Path}.", True)
#
@@ -39,30 +55,10 @@ def GetCurrentTime() -> str:
return strRet
#for debug
def DbgOut(strInput:str, bPrint:bool = False):
strMsg = (f"{GetCurrentTime()} : {strInput}")
listDbgStr.append(strMsg)
if True == bPrint:
print(strMsg)
#
def printDbgMessages():
for line in listDbgStr:
print(line)
#
def SaveDbgMessages(Path: str):
try:
with open(Path, 'w') as file:
for line in listDbgStr:
file.write(line + "\n")
except IOError:
DbgOut(f"Error: Could not write to the file at {Path}.", True)
def IsEmptyStr(string: str) -> bool:
temp = f"{string}"
return 0 == len(temp.strip())
# 입력된 경로의 자식 폴더를 찾아 반환한다.
# 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다
@@ -148,6 +144,7 @@ def IsFinalFolder(path : str) -> bool:
return bRet;
# 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다.
def FindFileFromExt(path: str, ext: str)-> list[str]:
bDot = False
@@ -281,11 +278,6 @@ def PrintJSONTree(data, indent: int=0 ) -> None:
else:
print(' ' * indent + str(data))
#
def IsPathWithin(base_path: str, target_path: str) -> bool:
base = Path(base_path).resolve()
target = Path(target_path).resolve()
return target.is_relative_to(base)
# 랜덤 UUID 생성
def UUIDGenRandom():
@@ -327,11 +319,6 @@ def CalculateFileHash(strFilePath: str, method: str="sha256")-> str:
# method (str): 사용할 해시 알고리즘
#Returns:
# bool: 파일이 정상인지 여부
def VerifyIsValidFile(strPath: str, strCompHash: str, strMethod: str="sha256")->bool:
Hash_Calcd = CalculateFileHash(strPath, strMethod)
return strCompHash.lower() == Hash_Calcd.lower()
"""
# 사용 예시
if __name__ == "__main__":
@@ -343,4 +330,73 @@ if __name__ == "__main__":
print("파일이 정상입니다!")
else:
print("파일이 손상되었거나 다릅니다!")
"""
"""
def VerifyIsValidFile(strPath: str, strCompHash: str, strMethod: str="sha256")->bool:
Hash_Calcd = CalculateFileHash(strPath, strMethod)
return strCompHash.lower() == Hash_Calcd.lower()
# 세로 크기를 가로 비율대로 줄여서 반환 (가로를 기준으로 세로 길이)
def GetRSzHeight(nWidth, nHeight, nTrgW):
if nWidth <= 0 or nTrgW <= 0:
return 0;
fRatio = nTrgW / nWidth
nTrgH = round(nHeight * fRatio)
return nTrgH
# 가로 크기를 세로 비율대로 줄여서 반환 (세로를 기준으로 가로 길이)
def GetRSzWidth(nWidth, nHeight, nTrgH):
if nHeight <= 0 or nTrgH <= 0:
return 0;
fRatio = nTrgH / nHeight
nTrgW = round(nWidth * fRatio)
return nTrgW
# 엑셀 열 너비는 약 7.5픽셀 단위
# 엑셀 행 높이는 약 25픽셀 단위
# 이대로는 사이즈가 안 맞아서 적당히 곱해줌
def GetCellSize(nImgW, nImgH, DPI = 96):
column_width = (nImgW / DPI) * 7.5 * 2
row_height = (nImgH / DPI) * 25 * 3
return column_width, row_height
def GetSheetInfoValue(text):
if text is None:
return "", ""
pattern = r'<(.*?)>'
matches = re.findall(pattern, text)
cleaned = re.sub(pattern, '', text)
return cleaned, matches
# 두 GPS 좌표 간 거리 구하기 (미터)
def GetDistanceGPS(lat1, lon1, lat2, lon2):
# 지구의 반지름 (단위: 미터)
R = 6371008.8
# 위도와 경도를 라디안으로 변환
lat1 = math.radians(lat1)
lon1 = math.radians(lon1)
lat2 = math.radians(lat2)
lon2 = math.radians(lon2)
# 차이 계산
dlat = lat2 - lat1
dlon = lon2 - lon1
# Haversine 공식 적용
a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
# 거리 계산
distance = R * c
return distance

View File

@@ -1,21 +1,27 @@
appnope==0.1.4
asgiref==3.8.1
asttokens==2.4.1
beautifulsoup4==4.14.2
branca==0.8.2
bs4==0.0.2
certifi==2025.10.5
charset-normalizer==3.4.4
cloudpickle==3.0.0
comm==0.2.2
debugpy==1.8.2
decorator==5.1.1
Django==5.0.7
Django==5.2.8
et-xmlfile==1.1.0
executing==2.0.1
folium==0.20.0
idna==3.11
image==1.5.33
ipykernel==6.29.5
ipython==8.26.0
jedi==0.19.1
jupyter_client==8.6.2
jupyter_core==5.7.2
Jinja2==3.1.6
MarkupSafe==3.0.3
matplotlib-inline==0.1.7
nest-asyncio==1.6.0
numpy==2.2.6
opencv-python==4.12.0.88
openpyxl==3.1.5
packaging==24.1
parso==0.8.4
@@ -27,14 +33,23 @@ psutil==6.0.0
ptyprocess==0.7.0
pure_eval==0.2.3
Pygments==2.18.0
pyproj==3.7.2
PyQt5==5.15.11
PyQt5-Qt5==5.15.17
PyQt5_sip==12.17.1
PyQtWebEngine==5.15.7
PyQtWebEngine-Qt5==5.15.17
pytesseract==0.3.13
python-dateutil==2.9.0.post0
pyzmq==26.0.3
rarfile==4.2
requests==2.32.5
six==1.16.0
spyder-kernels==2.5.2
soupsieve==2.8
sqlparse==0.5.1
stack-data==0.6.3
tornado==6.4.1
traitlets==5.14.3
typing_extensions==4.15.0
urllib3==2.5.0
wcwidth==0.2.13
wurlitzer==3.1.1
xyzservices==2025.10.0