Project

General

Profile

Files » altmain.py

altmain python file for the program - Pratama Kwee BRANDON, 11/12/2025 08:22 PM

 
1
"""
2
@file altmain.py
3
@brief Projection Splitter with Overlap Blending using PyTorch.
4

    
5
@details
6
This module provides a projection image processing system designed for 
7
multi-projector setups or panoramic displays. It takes an input image, 
8
splits it into two halves (left and right), and applies an overlap blending 
9
region between them to ensure seamless projection alignment.
10

    
11
It supports multiple blending modes (linear, quadratic, and Gaussian) 
12
and performs GPU-accelerated computation via PyTorch for high efficiency.
13
"""
14

    
15
import cv2
16
import numpy as np
17
import math
18
from scipy.special import erf
19
import torch
20
import config_reader
21

    
22

    
23
class ProjectionSplit:
24
    """
25
    @brief Handles image splitting and blending for projection alignment.
26

    
27
    @details
28
    The ProjectionSplit class processes both static images and individual 
29
    frames from video sources. It divides the input image into two parts 
30
    with a configurable overlap and applies smooth blending transitions 
31
    using selected mathematical models.
32

    
33
    The blending operations are optimized with PyTorch and support the 
34
    Metal backend for GPU acceleration on macOS devices.
35
    """
36

    
37
    def __init__(self):
38
        """@brief Initialize the ProjectionSplit object and configuration.
39

    
40
        @details
41
        This constructor initializes placeholders for the left, right, 
42
        and main images, and loads configuration parameters (e.g., 
43
        blending coefficients) from the `config.ini` file via ConfigReader.
44
        """
45
        self.image_left = None
46
        self.image_right = None
47
        self.image_main = None
48
        self.cfg = config_reader.ConfigReader("config.ini")
49

    
50
    def process_frame(self, image, overlap: int = 75, blend_type: str = "exponential"):
51
        """@brief Process a single input frame into left and right projections with overlap blending.
52

    
53
        @details
54
        This method divides an input image frame into two halves and applies 
55
        a blending function to the overlapping region between them. The 
56
        blending type determines the transition smoothness between projectors.
57

    
58
        Available blend types:
59
        - **linear**: Simple linear transition.
60
        - **quadratic**: Smoother parabolic blending.
61
        - **gaussian**: Natural soft transition curve based on Gaussian distribution.
62

    
63
        The blending is executed on GPU via PyTorch for efficient computation.
64

    
65
        @param image The input image (NumPy array) in BGR or BGRA format.
66
        @param overlap Integer specifying the pixel width of the overlapping area. Default: 75.
67
        @param blend_type String specifying the blending function ("linear", "quadratic", "gaussian").
68
        @throws FileNotFoundError If the image is None or not found.
69
        @throws ValueError If an invalid blend_type is specified.
70
        """
71
        if image is None:
72
            raise FileNotFoundError("Error: input.png not found or could not be loaded.")
73
        self.image_main = image
74

    
75
        # Ensure alpha channel
76
        if image.shape[2] == 3:
77
            image = cv2.cvtColor(image, cv2.COLOR_BGR2BGRA)
78

    
79
        height, width = image.shape[:2]
80
        split_x = width // 2
81

    
82
        # Define overlapping regions
83
        left_end = split_x + overlap // 2
84
        right_start = split_x - overlap // 2
85

    
86
        left_img = image[:, :left_end].copy()
87
        right_img = image[:, right_start:].copy()
88

    
89
        # Generate normalized overlap vector
90
        x = np.linspace(0, 1, overlap)
91

    
92
        # Select blending function
93
        if blend_type == "linear":
94
            alpha_left_curve = 1 - self.cfg.get_linear_parameter() * x
95
            alpha_right_curve = 1 - self.cfg.get_linear_parameter() + self.cfg.get_linear_parameter() * x
96
        elif blend_type == "quadratic":
97
            alpha_left_curve = (1 - x) ** 2
98
            alpha_right_curve = x ** 2
99
        elif blend_type == "gaussian":
100
            sigma = 0.25
101
            g = 0.5 * (1 + erf((x - 0.5) / (sigma * np.sqrt(2))))
102
            alpha_left_curve = 1 - g
103
            alpha_right_curve = g
104
        else:
105
            raise ValueError(f"Unknown blend_type '{blend_type}'")
106

    
107
        # GPU accelerated blending using PyTorch
108
        device = "mps"  # Metal backend (for macOS)
109
        left_img_t = torch.from_numpy(left_img).to(device, dtype=torch.float32)
110
        right_img_t = torch.from_numpy(right_img).to(device, dtype=torch.float32)
111
        alpha_left_t = torch.from_numpy(alpha_left_curve).to(device, dtype=torch.float32)
112
        alpha_right_t = torch.from_numpy(alpha_right_curve).to(device, dtype=torch.float32)
113

    
114
        # Expand alpha for broadcast along image width
115
        alpha_left_2d = alpha_left_t.unsqueeze(0).unsqueeze(-1)
116
        alpha_right_2d = alpha_right_t.unsqueeze(0).unsqueeze(-1)
117

    
118
        # Apply blending on alpha channel
119
        left_img_t[:, -overlap:, 3] *= alpha_left_2d.squeeze(-1)
120
        right_img_t[:, :overlap, 3] *= alpha_right_2d.squeeze(-1)
121

    
122
        # Convert back to CPU for saving
123
        left_img = left_img_t.cpu().numpy().astype(np.uint8)
124
        right_img = right_img_t.cpu().numpy().astype(np.uint8)
125

    
126
        self.image_left = left_img
127
        self.image_right = right_img
128

    
129
        cv2.imwrite("left.png", left_img)
130
        cv2.imwrite("right.png", right_img)
131

    
132
    def process_images(self, overlap: int = 75, blend_type: str = "exponential"):
133
        """@brief Process a static image file and generate blended left/right outputs.
134

    
135
        @details
136
        Reads 'input.png' from the current working directory, applies 
137
        image splitting and overlap blending, and saves the processed 
138
        halves as 'left.png' and 'right.png'.
139

    
140
        This function uses the same internal logic as process_frame() 
141
        but is intended for static image files instead of real-time frames.
142

    
143
        @param overlap Integer pixel width of the overlapping region. Default: 75.
144
        @param blend_type String specifying blending mode ("linear", "quadratic", "gaussian").
145
        @throws FileNotFoundError If 'input.png' is not found.
146
        @throws ValueError If an invalid blending type is selected.
147
        """
148
        image = cv2.imread("input.png", cv2.IMREAD_UNCHANGED)
149
        self.image_main = image
150
        if image is None:
151
            raise FileNotFoundError("Error: input.png not found.")
152

    
153
        # Ensure image has alpha channel
154
        if image.shape[2] == 3:
155
            image = cv2.cvtColor(image, cv2.COLOR_BGR2BGRA)
156

    
157
        height, width = image.shape[:2]
158
        split_x = width // 2
159

    
160
        left_end = split_x + overlap // 2
161
        right_start = split_x - overlap // 2
162

    
163
        left_img = image[:, :left_end].copy()
164
        right_img = image[:, right_start:].copy()
165

    
166
        # Create blend curve
167
        x = np.linspace(0, 1, overlap)
168
        if blend_type == "linear":
169
            alpha_left_curve = 1 - self.cfg.get_linear_parameter() * x
170
            alpha_right_curve = 1 - self.cfg.get_linear_parameter() + self.cfg.get_linear_parameter() * x
171
        elif blend_type == "quadratic":
172
            alpha_left_curve = (1 - x) ** 2
173
            alpha_right_curve = x ** 2
174
        elif blend_type == "gaussian":
175
            sigma = 0.25
176
            g = 0.5 * (1 + erf((x - 0.5) / (sigma * np.sqrt(2))))
177
            alpha_left_curve = 1 - g
178
            alpha_right_curve = g
179
        else:
180
            raise ValueError(f"Unknown blend_type '{blend_type}'")
181

    
182
        # GPU blending with PyTorch
183
        device = "mps"
184
        left_img_t = torch.from_numpy(left_img).to(device, dtype=torch.float32)
185
        right_img_t = torch.from_numpy(right_img).to(device, dtype=torch.float32)
186
        alpha_left_t = torch.from_numpy(alpha_left_curve).to(device, dtype=torch.float32)
187
        alpha_right_t = torch.from_numpy(alpha_right_curve).to(device, dtype=torch.float32)
188

    
189
        alpha_left_2d = alpha_left_t.unsqueeze(0).unsqueeze(-1)
190
        alpha_right_2d = alpha_right_t.unsqueeze(0).unsqueeze(-1)
191

    
192
        left_img_t[:, -overlap:, 3] *= alpha_left_2d.squeeze(-1)
193
        right_img_t[:, :overlap, 3] *= alpha_right_2d.squeeze(-1)
194

    
195
        left_img = left_img_t.cpu().numpy().astype(np.uint8)
196
        right_img = right_img_t.cpu().numpy().astype(np.uint8)
197

    
198
        self.image_left = left_img
199
        self.image_right = right_img
200

    
201
        cv2.imwrite("left.png", left_img)
202
        cv2.imwrite("right.png", right_img)
(15-15/17)