瀏覽代碼

优化图像旋转和缩放功能,调整缓存管理,提升用户交互体验

zhch158_admin 2 月之前
父節點
當前提交
6ecd0fbdf5
共有 1 個文件被更改,包括 62 次插入160 次删除
  1. 62 160
      ocr_validator_layout.py

+ 62 - 160
ocr_validator_layout.py

@@ -30,9 +30,9 @@ class OCRLayoutManager:
         self._rotated_image_cache = {}
         self._cache_max_size = 10
         self._orientation_cache = {}  # 缓存方向检测结果
-        self._auto_detected_angle = 0.0  # 自动检测的旋转角度缓存
+        self.rotated_angle = 0.0  # 自动检测的旋转角度缓存
         self.show_all_boxes = False
-        self.fit_to_container = True
+        self.fit_to_container = False
         self.zoom_level = 1.0
     
     def clear_image_cache(self):
@@ -81,8 +81,8 @@ class OCRLayoutManager:
                     return item['rotation_angle']
         
         # 如果没有预设角度,尝试自动检测
-        if hasattr(self, '_auto_detected_angle'):
-            return self._auto_detected_angle
+        if hasattr(self, 'rotated_angle'):
+            return self.rotated_angle
         
         return 0.0
     
@@ -342,11 +342,10 @@ class OCRLayoutManager:
         zoom_level = layout.get('default_zoom', 1.0)  # 默认缩放级别
         layout_type = "compact"
 
-        left_col, right_col = st.columns([layout['content_width'], layout['sidebar_width']], vertical_alignment='top')
+        left_col, right_col = st.columns([layout['content_width'], layout['sidebar_width']], vertical_alignment='top', border=True)
 
         with left_col:
-            self.render_content_section(layout_type)
-
+            # self.render_content_section(layout_type)
             # 快速定位文本选择器(使用不同的key)
             if self.validator.text_bbox_mapping:
                 text_options = ["请选择文本..."] + list(self.validator.text_bbox_mapping.keys())
@@ -354,6 +353,7 @@ class OCRLayoutManager:
                     "快速定位文本",
                     range(len(text_options)),
                     format_func=lambda x: text_options[x][:30] + "..." if len(text_options[x]) > 30 else text_options[x],
+                    label_visibility="collapsed",
                     key="compact_quick_text_selector"  # 使用不同的key
                 )
                 
@@ -380,39 +380,12 @@ class OCRLayoutManager:
     def create_aligned_image_display(self, zoom_level: float = 1.0, layout_type: str = "aligned"):
         """创建响应式图片显示"""
     
-        # # 添加响应式CSS
-        # st.markdown("""
-        # <style>
-        # .responsive-plot-container {
-        #     width: 100%;
-        #     max-width: 100%;
-        #     overflow: hidden;
-        # }
-        
-        # .responsive-plot-container .plotly-graph-div {
-        #     width: 100% !important;
-        #     max-width: 100% !important;
-        # }
-        
-        # /* 确保Plotly图表响应式 */
-        # .js-plotly-plot .plotly .svg-container {
-        #     width: 100% !important;
-        #     height: auto !important;
-        # }
-        # </style>
-        # """, unsafe_allow_html=True)
-
-        st.header("🖼️ 原图标注")
+        # st.header("🖼️ 原图标注")
         
         # 图片控制选项
-        col1, col2, col3, col4 = st.columns(4)
+        col1, col2, col3, col4 = st.columns(4, vertical_alignment="center", border= False)
+
         with col1:
-            # 判断{layout_type}_zoom_level是否有值,如果有值直接使用,否则使用传入的zoom_level
-            current_zoom = self.zoom_level
-            current_zoom = st.slider("图片缩放", 0.3, 2.0, current_zoom, 0.1, key=f"{layout_type}_zoom_level")
-            if current_zoom != self.zoom_level:
-                self.zoom_level = current_zoom
-        with col2:
             # 判断{layout_type}_show_all_boxes是否有值,如果有值直接使用,否则默认False
             # if f"{layout_type}_show_all_boxes" not in st.session_state:
             #     st.session_state[f"{layout_type}_show_all_boxes"] = False
@@ -425,137 +398,40 @@ class OCRLayoutManager:
             )
             if show_all_boxes != self.show_all_boxes:
                 self.show_all_boxes = show_all_boxes
+
+        with col2:
+            # if st.button("应用手动角度", key=f"{layout_type}_apply_manual"):
+            if st.button("🔄 旋转90度", type="secondary", key=f"{layout_type}_manual_angle"):
+                self.rotated_angle = (self.rotated_angle + 90) % 360
+                # st.success(f"已设置旋转角度为 {manual_angle}")
+                # 需要清除图片缓存,以及text_bbox_mapping中的bbox
+                self.clear_image_cache()
+                self.validator.process_data()
+                st.rerun()
+                
         with col3:
-            # 判断{layout_type}_fit_to_container是否有值,如果有值直接使用,否则默认True
-            fit_to_container = st.checkbox(
-                "适应容器", 
-                value=self.fit_to_container,
-                key=f"{layout_type}_fit_to_container"
-            )
-            if fit_to_container != self.fit_to_container:
-                self.fit_to_container = fit_to_container
+            if st.button("↺ 重置角度", key=f"{layout_type}_reset_angle"):
+                self.rotated_angle = 0.0
+                st.success("已重置旋转角度")
+                # 需要清除图片缓存,以及text_bbox_mapping中的bbox
+                self.clear_image_cache()
+                self.validator.process_data()
+                st.rerun()
+ 
         with col4:
             # 显示当前角度状态
             current_angle = self.get_rotation_angle()
             st.metric("当前角度", f"{current_angle}°", label_visibility="collapsed")
 
-         # 方向检测控制面板
-        with st.expander("🔄 图片方向检测", expanded=False):
-            col1, col2, col3 = st.columns([1, 1, 1], width='stretch')
-            
-            with col1:
-                manual_angle = st.selectbox(
-                    "设置角度",
-                    [0, 90, 180, 270],
-                    index = 0,
-                    label_visibility="collapsed",
-                    # key=f"{layout_type}_manual_angle"
-                )
-                # if st.button("应用手动角度", key=f"{layout_type}_apply_manual"):
-                if abs(self._auto_detected_angle - manual_angle) > 0.01 :
-                    self._auto_detected_angle = float(manual_angle)
-                    # st.success(f"已设置旋转角度为 {manual_angle}")
-                    # 需要清除图片缓存,以及text_bbox_mapping中的bbox
-                    self.clear_image_cache()
-                    self.validator.process_data()
-                    st.rerun()
-                    
-            with col2:
-                if st.button("🔍 自动检测方向", key=f"{layout_type}_detect_orientation"):
-                    if self.validator.image_path:
-                        with st.spinner("正在检测图片方向..."):
-                            detection_result = self.detect_and_suggest_rotation(self.validator.image_path)
-                            st.session_state[f'{layout_type}_detection_result'] = detection_result
-                        st.rerun()
-            
-            with col3:
-                if st.button("🔄 重置角度", key=f"{layout_type}_reset_angle"):
-                    self._auto_detected_angle = 0.0
-                    st.success("已重置旋转角度")
-                    # 需要清除图片缓存,以及text_bbox_mapping中的bbox
-                    self.clear_image_cache()
-                    self.validator.process_data()
-                    st.rerun()
-            
-            # 显示检测结果
-            if f'{layout_type}_detection_result' in st.session_state:
-                result = st.session_state[f'{layout_type}_detection_result']
-                
-                st.markdown("### 🎯 检测结果")
-                
-                # 结果概览
-                result_col1, result_col2, result_col3 = st.columns(3)
-                with result_col1:
-                    st.metric("建议角度", f"{result['detected_angle']}°")
-                with result_col2:
-                    st.metric("置信度", f"{result['confidence']:.2%}")
-                with result_col3:
-                    confidence_color = "🟢" if result['confidence'] > 0.7 else "🟡" if result['confidence'] > 0.4 else "🔴"
-                    st.metric("可信度", f"{confidence_color}")
-                
-                # 详细信息
-                st.write(f"**检测信息:** {result['message']}")
-                
-                if 'method_details' in result:
-                    st.write("**方法详情:**")
-                    for detail in result['method_details']:
-                        st.write(f"• {detail}")
-                
-                # 应用建议角度
-                if result['confidence'] > 0.3 and result['detected_angle'] != 0:
-                    if st.button(f"✅ 应用建议角度 {result['detected_angle']}°", key=f"{layout_type}_apply_suggested"):
-                        self._auto_detected_angle = result['detected_angle']
-                        st.success(f"已应用建议角度 {result['detected_angle']}°")
-                        # 需要清除图片缓存,以及text_bbox_mapping中的bbox
-                        self.clear_image_cache()
-                        self.validator.process_data()
-                        st.rerun()
-                
-                # 显示个别方法的结果
-                if 'individual_results' in result and len(result['individual_results']) > 1:
-                    with st.expander("📊 各方法检测详情", expanded=False):
-                        for i, individual in enumerate(result['individual_results']):
-                            st.write(f"**方法 {i+1}: {individual['method']}**")
-                            st.write(f"角度: {individual['detected_angle']}°, 置信度: {individual['confidence']:.2f}")
-                            st.write(f"信息: {individual['message']}")
-                            if 'error' in individual:
-                                st.error(f"错误: {individual['error']}")
-                            st.write("---")
-        
-       
         # 使用增强的图像加载方法
         image = self.load_and_rotate_image(self.validator.image_path)
         
         if image:
             try:
-                # 使用响应式容器包装
-                # st.markdown('<div class="responsive-plot-container">', unsafe_allow_html=True)
-
-                # 根据缩放级别调整图片大小
-                new_width = int(image.width * current_zoom)
-                new_height = int(image.height * current_zoom)
-                resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
-                
-                # 计算选中的bbox
-                selected_bbox = None
-                if st.session_state.selected_text and st.session_state.selected_text in self.validator.text_bbox_mapping:
-                    info = self.validator.text_bbox_mapping[st.session_state.selected_text][0]
-                    bbox = info['bbox']
-                    selected_bbox = [int(coord * current_zoom) for coord in bbox]
-
-                # 收集所有框
-                all_boxes = []
-                if show_all_boxes:
-                    for text, info_list in self.validator.text_bbox_mapping.items():
-                        for info in info_list:
-                            bbox = info['bbox']
-                            if len(bbox) >= 4:
-                                scaled_bbox = [coord * current_zoom for coord in bbox]
-                                all_boxes.append(scaled_bbox)
-                
+                resized_image, all_boxes, selected_bbox = self.zoom_image(image, self.zoom_level)
                 # 创建交互式图片
-                fig = self.create_resized_interactive_plot(resized_image, selected_bbox, current_zoom, all_boxes)
-                
+                fig = self.create_resized_interactive_plot(resized_image, selected_bbox, self.zoom_level, all_boxes)
+
                 plot_config = {
                     'displayModeBar': True,
                     'modeBarButtonsToRemove': ['zoom2d', 'select2d', 'lasso2d', 'autoScale2d'],
@@ -636,8 +512,8 @@ class OCRLayoutManager:
                 #         st.write("**配置信息:**")
                 #         st.write(f"工具类型: {rotation_config.get('coordinates_are_pre_rotated', 'unknown')}")
                 #         st.write(f"缓存状态: {len(self._rotated_image_cache)} 项")
-                #         if hasattr(self, '_auto_detected_angle'):
-                #             st.write(f"自动检测角度: {self._auto_detected_angle}°")
+                #         if hasattr(self, 'rotated_angle'):
+                #             st.write(f"自动检测角度: {self.rotated_angle}°")
                 
                         
             except Exception as e:
@@ -649,8 +525,34 @@ class OCRLayoutManager:
                 st.write(f"期望路径: {self.validator.image_path}")
 
     # st.markdown('</div>', unsafe_allow_html=True)
-    
-    def create_resized_interactive_plot(self, image: Image.Image, selected_bbox: Optional[List[int]], zoom_level: float, all_boxes: list[tuple]) -> go.Figure:
+
+    def zoom_image(self, image: Image.Image, current_zoom: float) -> Tuple[Image.Image, List[List[int]], Optional[List[int]]]:
+        """缩放图像"""
+        # 根据缩放级别调整图片大小
+        new_width = int(image.width * current_zoom)
+        new_height = int(image.height * current_zoom)
+        resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
+
+        # 计算选中的bbox
+        selected_bbox = None
+        if st.session_state.selected_text and st.session_state.selected_text in self.validator.text_bbox_mapping:
+            info = self.validator.text_bbox_mapping[st.session_state.selected_text][0]
+            bbox = info['bbox']
+            selected_bbox = [int(coord * current_zoom) for coord in bbox]
+
+        # 收集所有框
+        all_boxes = []
+        if self.show_all_boxes:
+            for text, info_list in self.validator.text_bbox_mapping.items():
+                for info in info_list:
+                    bbox = info['bbox']
+                    if len(bbox) >= 4:
+                        scaled_bbox = [coord * current_zoom for coord in bbox]
+                        all_boxes.append(scaled_bbox)
+
+        return resized_image, all_boxes, selected_bbox
+        
+    def create_resized_interactive_plot(self, image: Image.Image, selected_bbox: Optional[List[int]], zoom_level: float, all_boxes: List[List[int]]) -> go.Figure:
         """创建可调整大小的交互式图片 - 修复容器溢出问题"""
         fig = go.Figure()