浏览代码

优化图像显示功能,调整响应式布局和控件状态管理,提升用户交互体验

zhch158_admin 2 月之前
父节点
当前提交
04eccf01e4
共有 1 个文件被更改,包括 132 次插入84 次删除
  1. 132 84
      ocr_validator_layout.py

+ 132 - 84
ocr_validator_layout.py

@@ -30,7 +30,10 @@ class OCRLayoutManager:
         self._rotated_image_cache = {}
         self._cache_max_size = 10
         self._orientation_cache = {}  # 缓存方向检测结果
-        # self._auto_detected_angle = 0.0  # 自动检测的旋转角度缓存
+        self._auto_detected_angle = 0.0  # 自动检测的旋转角度缓存
+        self.show_all_boxes = False
+        self.fit_to_container = True
+        self.zoom_level = 1.0
     
     def clear_image_cache(self):
         """清理所有图像缓存"""
@@ -375,17 +378,40 @@ class OCRLayoutManager:
             self.create_aligned_image_display(zoom_level, "compact")
     
     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("🖼️ 原图标注")
         
         # 图片控制选项
         col1, col2, col3, col4 = st.columns(4)
         with col1:
             # 判断{layout_type}_zoom_level是否有值,如果有值直接使用,否则使用传入的zoom_level
-            current_zoom = self.validator.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.validator.zoom_level:
-                self.validator.zoom_level = current_zoom
+            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:
@@ -394,20 +420,20 @@ class OCRLayoutManager:
             show_all_boxes = st.checkbox(
                 "显示所有框",
                 # value=st.session_state[f"{layout_type}_show_all_boxes"],
-                value = self.validator.show_all_boxes,
+                value = self.show_all_boxes,
                 key=f"{layout_type}_show_all_boxes"
             )
-            if show_all_boxes != self.validator.show_all_boxes:
-                self.validator.show_all_boxes = show_all_boxes
+            if show_all_boxes != self.show_all_boxes:
+                self.show_all_boxes = show_all_boxes
         with col3:
             # 判断{layout_type}_fit_to_container是否有值,如果有值直接使用,否则默认True
             fit_to_container = st.checkbox(
                 "适应容器", 
-                value=self.validator.fit_to_container,
+                value=self.fit_to_container,
                 key=f"{layout_type}_fit_to_container"
             )
-            if fit_to_container != self.validator.fit_to_container:
-                self.validator.fit_to_container = fit_to_container
+            if fit_to_container != self.fit_to_container:
+                self.fit_to_container = fit_to_container
         with col4:
             # 显示当前角度状态
             current_angle = self.get_rotation_angle()
@@ -426,7 +452,7 @@ class OCRLayoutManager:
                     # key=f"{layout_type}_manual_angle"
                 )
                 # if st.button("应用手动角度", key=f"{layout_type}_apply_manual"):
-                if not hasattr(self, '_auto_detected_angle') or self._auto_detected_angle != manual_angle:
+                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
@@ -444,8 +470,7 @@ class OCRLayoutManager:
             
             with col3:
                 if st.button("🔄 重置角度", key=f"{layout_type}_reset_angle"):
-                    if hasattr(self, '_auto_detected_angle'):
-                        delattr(self, '_auto_detected_angle')
+                    self._auto_detected_angle = 0.0
                     st.success("已重置旋转角度")
                     # 需要清除图片缓存,以及text_bbox_mapping中的bbox
                     self.clear_image_cache()
@@ -503,6 +528,9 @@ class OCRLayoutManager:
         
         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)
@@ -532,74 +560,84 @@ class OCRLayoutManager:
                     'displayModeBar': True,
                     'modeBarButtonsToRemove': ['zoom2d', 'select2d', 'lasso2d', 'autoScale2d'],
                     'scrollZoom': True,
-                    'doubleClick': 'reset'
+                    'doubleClick': 'reset',
+                    'responsive': False,  # 关键:禁用响应式,使用固定尺寸
+                    'toImageButtonOptions': {
+                        'format': 'png',
+                        'filename': 'ocr_image',
+                        'height': None,  # 使用当前高度
+                        'width': None,   # 使用当前宽度
+                            'scale': 1
+                        }
                 }
                 
                 st.plotly_chart(
                     fig, 
-                    use_container_width=fit_to_container,
+                    # use_container_width=fit_to_container,
+                    use_container_width=False,
                     config=plot_config,
                     key=f"{layout_type}_plot"
                 )
+                # st.markdown('</div>', unsafe_allow_html=True)
                 
-                # 显示选中文本的详细信息
-                if st.session_state.selected_text and st.session_state.selected_text in self.validator.text_bbox_mapping:
-                    st.subheader("📍 选中文本详情")
+                # # 显示选中文本的详细信息
+                # if st.session_state.selected_text and st.session_state.selected_text in self.validator.text_bbox_mapping:
+                #     st.subheader("📍 选中文本详情")
                     
-                    info = self.validator.text_bbox_mapping[st.session_state.selected_text][0]
-                    bbox = info['bbox']
+                #     info = self.validator.text_bbox_mapping[st.session_state.selected_text][0]
+                #     bbox = info['bbox']
                     
-                    info_col1, info_col2 = st.columns(2)
-                    with info_col1:
-                        st.write(f"**文本内容:** {st.session_state.selected_text[:30]}...")
-                        st.write(f"**类别:** {info['category']}")
-                        # 显示旋转信息
-                        rotation_angle = self.get_rotation_angle()
-                        if rotation_angle != 0:
-                            st.write(f"**旋转角度:** {rotation_angle}°")
+                #     info_col1, info_col2 = st.columns(2)
+                #     with info_col1:
+                #         st.write(f"**文本内容:** {st.session_state.selected_text[:30]}...")
+                #         st.write(f"**类别:** {info['category']}")
+                #         # 显示旋转信息
+                #         rotation_angle = self.get_rotation_angle()
+                #         if rotation_angle != 0:
+                #             st.write(f"**旋转角度:** {rotation_angle}°")
                 
-                    with info_col2:
-                        st.write(f"**位置:** [{', '.join(map(str, bbox))}]")
-                        if len(bbox) >= 4:
-                            st.write(f"**大小:** {bbox[2] - bbox[0]} x {bbox[3] - bbox[1]} px")
+                #     with info_col2:
+                #         st.write(f"**位置:** [{', '.join(map(str, bbox))}]")
+                #         if len(bbox) >= 4:
+                #             st.write(f"**大小:** {bbox[2] - bbox[0]} x {bbox[3] - bbox[1]} px")
                 
-                # 错误标记功能
-                col1, col2 = st.columns(2)
-                with col1:
-                    if st.button("❌ 标记为错误", key=f"{layout_type}_mark_error"):
-                        st.session_state.marked_errors.add(st.session_state.selected_text)
-                        st.rerun()
+                # # 错误标记功能
+                # col1, col2 = st.columns(2)
+                # with col1:
+                #     if st.button("❌ 标记为错误", key=f"{layout_type}_mark_error"):
+                #         st.session_state.marked_errors.add(st.session_state.selected_text)
+                #         st.rerun()
                 
-                with col2:
-                    if st.button("✅ 取消错误标记", key=f"{layout_type}_unmark_error"):
-                        st.session_state.marked_errors.discard(st.session_state.selected_text)
-                        st.rerun()
+                # with col2:
+                #     if st.button("✅ 取消错误标记", key=f"{layout_type}_unmark_error"):
+                #         st.session_state.marked_errors.discard(st.session_state.selected_text)
+                #         st.rerun()
                 
-                # 增强的调试信息
-                with st.expander("🔍 图像和坐标调试信息", expanded=False):
-                    rotation_angle = self.get_rotation_angle()
-                    rotation_config = get_ocr_tool_rotation_config(self.validator.ocr_data, self.config)
+                # # 增强的调试信息
+                # with st.expander("🔍 图像和坐标调试信息", expanded=False):
+                #     rotation_angle = self.get_rotation_angle()
+                #     rotation_config = get_ocr_tool_rotation_config(self.validator.ocr_data, self.config)
                     
-                    col_debug1, col_debug2, col_debug3 = st.columns(3)
-                    with col_debug1:
-                        st.write("**图像信息:**")
-                        st.write(f"原始尺寸: {image.width} x {image.height}")
-                        st.write(f"缩放后尺寸: {resized_image.width} x {resized_image.height}")
-                        st.write(f"当前角度: {rotation_angle}°")
+                #     col_debug1, col_debug2, col_debug3 = st.columns(3)
+                #     with col_debug1:
+                #         st.write("**图像信息:**")
+                #         st.write(f"原始尺寸: {image.width} x {image.height}")
+                #         st.write(f"缩放后尺寸: {resized_image.width} x {resized_image.height}")
+                #         st.write(f"当前角度: {rotation_angle}°")
                         
-                    with col_debug2:
-                        st.write("**坐标信息:**")
-                        if selected_bbox:
-                            st.write(f"选中框: {selected_bbox}")
-                        st.write(f"总框数: {len(all_boxes)}")
-                        st.write(f"文本框数: {len(self.validator.text_bbox_mapping)}")
+                #     with col_debug2:
+                #         st.write("**坐标信息:**")
+                #         if selected_bbox:
+                #             st.write(f"选中框: {selected_bbox}")
+                #         st.write(f"总框数: {len(all_boxes)}")
+                #         st.write(f"文本框数: {len(self.validator.text_bbox_mapping)}")
                         
-                    with col_debug3:
-                        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}°")
+                #     with col_debug3:
+                #         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}°")
                 
                         
             except Exception as e:
@@ -610,13 +648,10 @@ class OCRLayoutManager:
             if self.validator.image_path:
                 st.write(f"期望路径: {self.validator.image_path}")
 
-    st.markdown('</div>', unsafe_allow_html=True)
+    # 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:
-        """
-        创建可调整大小的交互式图片 - 修复图像显示和bbox对齐问题
-        图片,box坐标全部是已缩放,旋转后的坐标
-        """
+        """创建可调整大小的交互式图片 - 修复容器溢出问题"""
         fig = go.Figure()
         
         # 添加图片 - Plotly坐标系,原点在左下角
@@ -674,29 +709,42 @@ class OCRLayoutManager:
             )
     
         # 修复:优化显示尺寸计算
-        max_display_width = 800
-        max_display_height = 600
+        max_display_width = 1200
+        max_display_height = 1200
         
         # 计算合适的显示尺寸,保持宽高比
         aspect_ratio = image.width / image.height
         
-        if aspect_ratio > 1:  # 宽图
-            display_width = min(max_display_width, image.width)
-            display_height = int(display_width / aspect_ratio)
-        else:  # 高图
-            display_height = min(max_display_height, image.height)
-            display_width = int(display_height * aspect_ratio)
+        if self.fit_to_container:
+            # 自适应容器模式
+            if aspect_ratio > 1:  # 宽图
+                display_width = min(max_display_width, image.width)
+                display_height = int(display_width / aspect_ratio)
+            else:  # 高图
+                display_height = min(max_display_height, image.height)
+                display_width = int(display_height * aspect_ratio)
+            
+            # 确保不会太小
+            display_width = max(display_width, 400)
+            display_height = max(display_height, 300)
+        else:
+            # 固定尺寸模式,但仍要考虑容器限制
+            display_width = min(image.width, max_display_width)
+            display_height = min(image.height, max_display_height)
         
-        # 修复:设置合理的布局参数
+        # 设置布局 - 关键修改
         fig.update_layout(
-            width=display_width,
-            height=display_height,
-            margin=dict(l=0, r=0, t=0, b=0),  # 移除所有边距
+            width=display_width,    # 注释掉固定宽度
+            height=display_height,  # 注释掉固定高度
+            
+            margin=dict(l=0, r=0, t=0, b=0),
             showlegend=False,
             plot_bgcolor='white',
             dragmode="pan",
             
-            # 修复:X轴设置
+            # 关键:让图表自适应容器
+            # autosize=True,  # 启用自动调整大小
+            
             xaxis=dict(
                 visible=False,
                 range=[0, image.width],
@@ -704,7 +752,7 @@ class OCRLayoutManager:
                 fixedrange=False,
                 autorange=False,
                 showgrid=False,
-                zeroline=False
+                zeroline=False,
             ),
             
             # 修复:Y轴设置,确保范围正确