중앙선 이설전 마지막 풍경들 - 의성역~우보역 구간(2024.12.01)

 

 중앙선 전구간 이설이 완료되기 약 3주 정도 남은 시점에 곧 사라지게 될 풍경들을 사진에 남겨보고자 한 번 더 다녀와보았습니다. 이번에는 의성역~우보역 구간을 돌아다녀 보았습니다.

 

이화건널목

 

 

시간표를 확인하지 않고 오는 바람에 2시간 정도 기다리게 되었습니다. 남는 시간동안 동네를 돌아다녀봅니다.

 

 

 

군위가 귀산 박씨 집성촌인듯 보입니다.

 

 

낮선 외지인을 반기는 강아지

 

 

 

 

우보역

 

 

철길을 둘러보던 도중 길가 한복판에 우보역을 발견하였습니다.

 

 

역내에 상주하는 직원이 있는지 출입문이 막혀있지는 않았습니다.

 

 

우보역 시비

 

 

플랫폼은 영업하던 시절 그대로 유지되는듯 보입니다.

 

 

승강장으로 진입하는 건널목은 철거되어 있습니다.

 

 

역 구내를 둘러보고 바로 옆에 있는 건널목으로 이동합니다.

금천건널목

 

 

철길이 도로 바로 옆에 붙어있어 구도가 잘 나올거같습니다.

 

 

 

 

대리건널목

 

 

 

탑리역에서 의성역 방면으로 가면 처음으로 볼 수 있는 대리건널목

 

 

마을길 바로 옆을 따라가는 철길

 

 

 

만천건널목

 

 

 

300x250

중앙선 이설전 마지막 풍경들 - 북영천역~갑현역 구간(2024.11.22)

 

 다음달(2024년 12월 21일) 중앙선의 모든 구간 이설이 완공되어 KTX가 서울 청량리역에서부터 부산 부전역까지 달릴 예정입니다. 즉, 현재 마지막으로 남은 기존선 구간(안동 - 북영천) 또한 다음달부터는 열차가 더이상 달리지 않을 예정입니다.

 차후 이설되어 볼 수 없게될 풍경들을 사진으로 남기고자 날씨가 화창하던날 마지막으로 남은 중앙선 기존구간을 다녀왔습니다.

 

 첫 번째로 방문한 곳은 북영천역에서 출발한 열차가 처음으로 건널목을 통과하는 호당4건널목입니다.

 

호당4건널목

 

 

 

운좋게 도착하자마자 열차가 지나가는 것을 사진으로 남기는군요.

 

 

 

호당3건널목

 

호당2건널목

 

 

 

 

삼부건널목

 

 

 

 

 

화산역

 

 

 

 

 

화산역 광장에서 바라본 입구길. 역 바로 옆에 붉은 철문의 구 역세권 집이 인상적이었습니다.

 

 

역 내부를 보니 꽤 오래전 폐쇄된 흔적들을 볼 수 있었습니다.

 

 

유성1건널목

 

 

 

유성1건널목에서 바라본 화산역 구내

 

 

화남건널목

 

완전3건널목

 

 

주변에 건널목밖에 없는 이 곳에 새마을운동 기념 공원이 있었습니다.

 

 

왕복 4차선 도로가 이 건널목에서만 2차선으로 줄어듭니다. 아마도 선로이설 후 추가 공사를 하겠죠

 

 

공원에서 쉬다가 기차 구경하기는 딱 좋은 곳이었습니다.

 

 

하지만 내년부터는 이 곳에서 더이상 열차를 볼 수 없게 됩니다.

 

 

 내년이면 이 곳도 4차선으로 확장되어 지금의 흔적은 볼 수 없을 것으로 보입니다.

 

신덕2건널목

 

 

한적한 시골 입구에 위치한 신덕2건널목입니다

 

 

마을 주변을 돌아다니던 도중 요란한 소리가 울려퍼지고

 

 

신녕역을 출발한 열차가 들어옵니다.

 

 

열차가 떠난 직후 모습

 

용천건널목

 

 

 

가일1건널목

 

 

 

왠지 침목으로 만든것으로 추정됩니다.

 

 

석촌2건널목

 

 

건널목 인근 회사에서 키우는 강아지가 일을 열심히 하는군요.

 

 

300x250

Flask 라이브러리로 이미지를 업로드하고 볼 수 있는 서버 구축하기

공대생의 팁 2024. 10. 26. 01:19



 ※ 이 프로젝트는 ChatGPT로 생성된 코드를 사용하였습니다.

 

 프로그래밍을 생업으로 일하시는 분들이라면 한 번 즈음은 자신이 사용해보지 못했던 프로그래밍 언어를 사용해야 할 수 밖에 없는 경험이 있으셨을 것입니다. 저 또한 Python을 위주로 개발을 하다보니 간혹 C/C++를 사용할 일이 있을 때엔 버벅거리면서 코딩을 간신히 해내곤 합니다.

 

 그런 와중에 프로그램 시연을 해야 하는 상황이 되었는데 막상 준비하려 하니 1주일이라는 시간을 갖고는 GUI 프로그램을 뚝딱 만드는건 사실상 불가능한 상황이었습니다. 그러한 고민을 하던 상황에서 혹시나 Chat GPT에게 이러한 고민을 털어놨더니 Web프로그래밍으로 짧은 시간 내에 시각화를 할 수 있는 프로젝트를 만들 수 있다는 답변을 하는 것이었습니다!

 

 지금까지 Web프로그래밍 관련 지식은 20년전 개인 홈페이지 제작을 위해 제로보드를 다루었던 경험밖에 없는 저에게 직접 Web프로그래밍을 하는 것은 불가능했습니다. 하지만 우리는 언제나 그랬듯이 방법을 찾아내왔지요?

 

 이번 포스팅에서는 Web프로그래밍 경험이 없는 사람의 입장에서 ChatGPT에게 다음과 같은 질문을 하였고 아래와 같은 질문을 통해 매우 훌륭한 Web프로그램을 완성하였습니다.

 

Python을 사용해서 다음과 같은 프로그램을 만들어줘.
 - 이미지를 웹사이트에 업로드 하고, 이를 볼 수 있는 웹사이트 제작
 - 이미지를 볼 때 마우스의 휠로 이미지의 축소/확대 기능 적용
 - 마우스로 드래그하여 확대된 이미지를 이동하며 볼 수 있도록함
 - 이미지 뷰어의 아래쪽에는 Thumbnail 방식으로 업로드된 이미지를 클릭하여 볼 수 있도록 함
 - 클립보드에 복사된 이미지를 웹페이지에 붙여넣기를 하는 방법으로 이미지를 업로드
 - 업로드된 이미지는 OpenCV를 사용하여 grayscale 이미지를 생성하고, 원본 및 변환된 이미지를 볼 수 있도록 함

 

 

 프롬프트의 전반적인 구조는 위와 같이 하여 질문을 하였고 몇몇 부분은 추가로 프롬프트를 작성하였으며, 부족한 부분은 직접 다듬었을 뿐인데 다행히도 원하는 대로 동작하는 웹사이트가 완성되었습니다!

 

아래는 ChatGPT가 Flask를 사용하여 만들어낸 프로젝트입니다. 아래는 프로젝트의 파일 구조입니다. app.py가 html을 제어하여 웹페이지를 동작시키는 것으로 이해하면 얼추 맞을 겁니다.

 

 

templates/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Drag and Drop Image Upload</title>
    <style>
        body {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: flex-start;
            height: 100vh;
            margin: 0;
            background-color: #f5f5f5;
            overflow: hidden;
            /* Hide scrollbars */
        }
        #drop-area {
            border: 2px dashed #ccc;
            padding: 80px;
            width: 100%;
            height: 80px;
            /* Increased height */
            box-sizing: border-box;
            /* Include padding and border in the element's total width and height */
            text-align: center;
            background-color: #fff;
            position: fixed;
            top: 0;
            left: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            /* Center the text */
        }
 
        #drop-area p {
            margin: 0;
            font-size: 24px;
            font-weight: bold;
            /* Bold text */
        }
 
        #loading {
            display: none;
            /* Hide loading initially */
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: rgba(128, 128, 128, 0.7);
            /* Gray with 70% opacity */
            color: white;
            padding: 40px;
            /* Increased padding */
            border-radius: 10px;
            /* Increased border-radius */
            text-align: center;
            z-index: 1000;
            /* Ensure loading screen overlaps all content */
            font-size: 32px;
            /* Larger font size for loading text */
            font-weight: bold;
            /* Bold text for loading */
        }
 
        #gallery {
            display: flex;
            justify-content: center;
            align-items: center;
            width: 100%;
            height: calc(100vh - 280px);
            /* Full height minus drop area height, thumbnails, and border */
            margin-top: 95px;
            /* Adjusted to match drop-area height and border */
            overflow: hidden;
            position: relative;
            /* Keep positioned relative to viewport */
            border-bottom: 1px solid black;
            /* Add bottom border for separation */
        }
 
        #thumbnails {
            display: flex;
            justify-content: center;
            align-items: center;
            width: 100%;
            height: 100px;
            margin: 5px;
            overflow-x: auto;
            background-color: #f5f5f5;
            position: fixed;
            bottom: 0;
            left: 0;
        }
 
        #thumbnails img {
            height: 80px;
            margin: 10px;
            cursor: pointer;
            transition: transform 0.25s ease;
        }
 
        #thumbnails img:hover {
            transform: scale(1.1);
        }
 
        img {
            max-width: 100%;
            max-height: 100%;
            transition: transform 0.25s ease;
            /* Smooth transition for transform */
            cursor: grab;
            /* Cursor shows grab icon */
        }
 
        img:active {
            cursor: grabbing;
            /* Cursor shows grabbing icon */
        }
    </style>
</head>
<body>
    <div id="loading">Loading...</div>
    <h2>Drag and Drop Image Upload</h2>
    <div id="drop-area">
        <p>Drag & drop image files here<br>Or paste image from clipboard</p>
    </div>
    <div id="gallery"></div>
    <div id="thumbnails"></div>
    <script>
        let dropArea = document.getElementById('drop-area');
        let gallery = document.getElementById('gallery');
        let thumbnails = document.getElementById('thumbnails');
        let loading = document.getElementById('loading');
        let currentImage;
        let scale = 1;
        let translateX = 0;
        let translateY = 0;
 
        dropArea.addEventListener('dragover', (event=> {
            event.preventDefault();
            dropArea.style.borderColor = 'green';
        });
 
        dropArea.addEventListener('dragleave', () => {
            dropArea.style.borderColor = '#ccc';
        });
 
        dropArea.addEventListener('drop', (event=> {
            event.preventDefault();
            dropArea.style.borderColor = '#ccc';
 
            thumbnails.innerHTML = ''// Clear existing thumbnails on each drop
            let files = event.dataTransfer.files;
            handleFiles(files);
        });
 
        function handleFiles(files) {
            ([...files]).forEach(uploadFile);
        }
 
        async function uploadFile(file) {
            // Show loading screen before starting upload
            loading.style.display = 'block';
            let url = '/upload';
            let formData = new FormData();
            formData.append('file', file);
 
            try {
                const response = await fetch(url, {
                    method: 'POST',
                    body: formData
                });
 
                const data = await response.json();
                // Hide loading screen after upload is successful
                displayImage(data.images[0]);
                loading.style.display = 'none';
 
                // Add the uploaded image to the thumbnails
                data.images.forEach(imgUrl => addThumbnail(imgUrl))
            } catch (error) {
                console.error('Upload failed');
                loading.style.display = 'none';
            }
        }
 
        function displayImage(imageUrl) {
            // Clear previous image
            gallery.innerHTML = '';
 
            // Create new image
            const img = document.createElement('img');
            img.src = imageUrl;
            gallery.appendChild(img);
 
            // Update current image reference
            currentImage = img;
 
            // Reset scale and position
            scale = 1;
            translateX = 0;
            translateY = 0;
            img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
 
            // Add drag-to-pan functionality
            let isDragging = false;
            let startX, startY;
 
            img.addEventListener('mousedown', (event=> {
                event.preventDefault();
                isDragging = true;
                startX = event.clientX - translateX;
                startY = event.clientY - translateY;
                img.style.cursor = 'grabbing';
            });
 
            document.addEventListener('mousemove', (event=> {
                if (isDragging) {
                    event.preventDefault();
                    translateX = event.clientX - startX;
                    translateY = event.clientY - startY;
 
                    // Restrict movement within the boundaries
                    const rect = img.getBoundingClientRect();
                    const galleryRect = gallery.getBoundingClientRect();
 
                    // Calculate overflows
                    const overflowX = Math.max(0, rect.width * scale - galleryRect.width);
                    const overflowY = Math.max(0, rect.height * scale - galleryRect.height);
 
                    // Clamp translateX and translateY within allowed bounds
                    translateX = Math.min(overflowX / 2, Math.max(-overflowX / 2, translateX));
                    translateY = Math.min(overflowY / 2, Math.max(-overflowY / 2, translateY));
                    img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
                }
            });
 
            document.addEventListener('mouseup', () => {
                if (isDragging) {
                    isDragging = false;
                    img.style.cursor = 'grab';
                }
            });
        }
 
        function addThumbnail(imageUrl) {
            const thumb = document.createElement('img');
            thumb.src = imageUrl;
            thumb.addEventListener('click', () => {
                displayImage(imageUrl);
            });
            thumbnails.appendChild(thumb);
        }
 
        // Zoom in/out handlers
        gallery.addEventListener('wheel', (event=> {
            if (currentImage) {
                event.preventDefault();
                if (event.deltaY > 0) {
                    // Scroll down - zoom out
                    scale *= 0.9;
                } else {
                    // Scroll up - zoom in
                    scale *= 1.1;
                }
 
                scale = Math.min(Math.max(.125, scale), 4); // Restrict scale
 
                // Restrict movement within the boundaries
                const rect = currentImage.getBoundingClientRect();
                const galleryRect = gallery.getBoundingClientRect();
 
                // Calculate overflows
                const overflowX = Math.max(0, rect.width * scale - galleryRect.width);
                const overflowY = Math.max(0, rect.height * scale - galleryRect.height);
 
                // Clamp translateX and translateY within allowed bounds
                translateX = Math.min(overflowX / 2, Math.max(-overflowX / 2, translateX));
                translateY = Math.min(overflowY / 2, Math.max(-overflowY / 2, translateY));
                currentImage.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
            }
        });
 
        // Add click event to hide loading screen in case of manual reset
        loading.addEventListener('click', () => {
            loading.style.display = 'none';
        });
 
        // Handle image paste from clipboard
        window.addEventListener('paste', (event=> {
            const items = event.clipboardData.items;
            for (const item of items) {
                if (item.type.startsWith('image/')) {
                    const file = item.getAsFile();
                    thumbnails.innerHTML = ''// Clear existing thumbnails on paste
                    handleFiles([file]);
                }
            }
        });
    </script>
</body>
</html>
cs

 

 

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from flask import Flask, request, url_for, render_template, send_from_directory, jsonify
from time import time
import os
import cv2
 
app = Flask(__name__)
 
UPLOAD_FOLDER = r'D:\server\static\uploads'
app.config['UPLOAD_FOLDER'= UPLOAD_FOLDER
app.config['MAX_CONTENT_PATH'= 16 * 1024 * 1024
 
# Ensure the upload directory exists
if not os.path.exists(app.config['UPLOAD_FOLDER']):
    os.makedirs(app.config['UPLOAD_FOLDER'])
 
@app.route('/')
def index():
    return render_template('index.html')
 
@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return 'No file part'
 
    file = request.files['file']
    if file.filename == '':
        return 'No selected file'
    if file:
        image_paths = []
        input_image_path = 'input_image.jpg'
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], input_image_path)
        file.save(filepath)
        image_paths.append(url_for('uploaded_file', filename=input_image_path) + '?' + str(int(time())))
        
        img = cv2.imread(filepath)
        gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        gray_filepath = os.path.join(app.config['UPLOAD_FOLDER'], 'gray_' + os.path.basename(filepath))
        cv2.imwrite(gray_filepath, gray_img)
        image_paths.append(url_for('uploaded_file', filename='gray_' + os.path.basename(filepath)) + '?' + str(int(time())))
 
        # Return the URL to access the uploaded file
        return jsonify(images=image_paths)
    return 'File not uploaded correctly'
 
@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
 
if __name__ == '__main__':
    # 모든 외부 접속 허용시 host='0,0,0,0' 설정
    app.run(debug=True, host='0,0,0,0', port=5000)
cs

 

 

- 결과

 

 
 

 

 
 

 저는 지금까지 제 블로그에는 제가 직접 작성한 소스코드를 업로드 해왔었습니다. 그러했던 제가 최근에는 ChatGPT에게 원하는 시스템의 전반적인 내용을 작성 시키고, 저는 그 코드에서 제가 원하는 기능들을 덮붙이는 식으로 일을 수행하고 있습니다. AI를 연구하는 입장에서 어느덧 AI와 함께 협업을 하며 일을 하는 세상이 되었다는 것이 참으로 놀라운 세상이네요. 앞으로도 ChatGPT를 사용해 업무 효율을 높일수 있는 기회들이 이번처럼 많아진다면 앞으로는 지금보다 업무 생산성이 많이 높아질 것이라 기대를 해봅니다.

300x250