|
|
@@ -163,8 +163,8 @@ class OCRLayoutManager:
|
|
|
except Exception as e:
|
|
|
st.error(f"❌ 图像加载失败: {e}")
|
|
|
return None
|
|
|
-
|
|
|
- def render_content_section(self, layout_type: str = "standard"):
|
|
|
+
|
|
|
+ def render_content_section(self, layout_type: str = "compact"):
|
|
|
"""渲染内容区域 - 统一方法"""
|
|
|
st.header("📄 OCR识别内容")
|
|
|
|
|
|
@@ -210,17 +210,9 @@ class OCRLayoutManager:
|
|
|
else:
|
|
|
st.warning(f"未找到包含 '{search_term}' 的内容")
|
|
|
|
|
|
- # 渲染方式选择
|
|
|
- render_mode = st.radio(
|
|
|
- "选择渲染方式",
|
|
|
- ["HTML渲染", "Markdown渲染", "DataFrame表格", "原始文本"],
|
|
|
- horizontal=True,
|
|
|
- key=f"{layout_type}_render_mode"
|
|
|
- )
|
|
|
-
|
|
|
- return display_content, render_mode
|
|
|
-
|
|
|
- def render_content_by_mode(self, content: str, render_mode: str, font_size: int, layout_type: str):
|
|
|
+ return display_content
|
|
|
+
|
|
|
+ def render_content_by_mode(self, content: str, render_mode: str, font_size: int, container_height: int, layout_type: str):
|
|
|
"""根据渲染模式显示内容 - 增强版本"""
|
|
|
if content is None or render_mode is None:
|
|
|
return
|
|
|
@@ -230,6 +222,9 @@ class OCRLayoutManager:
|
|
|
content_style = f"""
|
|
|
<style>
|
|
|
.{layout_type}-content-display {{
|
|
|
+ height: {container_height}px;
|
|
|
+ overflow-x: auto;
|
|
|
+ overflow-y: auto;
|
|
|
font-size: {font_size}px !important;
|
|
|
line-height: 1.4;
|
|
|
color: #333333 !important;
|
|
|
@@ -237,15 +232,15 @@ class OCRLayoutManager:
|
|
|
padding: 10px;
|
|
|
border-radius: 5px;
|
|
|
border: 1px solid #ddd;
|
|
|
- overflow-x: auto;
|
|
|
max-width: 100%;
|
|
|
}}
|
|
|
|
|
|
.{layout_type}-content-display table {{
|
|
|
- width: 100%;
|
|
|
+ width: 100%; /* 修改:从100%改为auto,让表格自适应内容 */
|
|
|
border-collapse: collapse;
|
|
|
margin: 10px 0;
|
|
|
- white-space: nowrap;
|
|
|
+ white-space: nowrap; /* 修改:允许文字换行 */
|
|
|
+ /* table-layout: auto; *? /* 新增:自动表格布局 */
|
|
|
}}
|
|
|
|
|
|
.{layout_type}-content-display th,
|
|
|
@@ -253,7 +248,11 @@ class OCRLayoutManager:
|
|
|
border: 1px solid #ddd;
|
|
|
padding: 8px;
|
|
|
text-align: left;
|
|
|
- min-width: 100px;
|
|
|
+ /* 移除:min-width固定限制 */
|
|
|
+ max-width: 300px; /* 新增:设置最大宽度避免过宽 */
|
|
|
+ word-wrap: break-word; /* 新增:长单词自动换行 */
|
|
|
+ word-break: break-all; /* 新增:允许在任意字符间换行 */
|
|
|
+ vertical-align: top; /* 新增:顶部对齐 */
|
|
|
}}
|
|
|
|
|
|
.{layout_type}-content-display th {{
|
|
|
@@ -261,6 +260,20 @@ class OCRLayoutManager:
|
|
|
position: sticky;
|
|
|
top: 0;
|
|
|
z-index: 1;
|
|
|
+ font-weight: bold; /* 新增:表头加粗 */
|
|
|
+ }}
|
|
|
+
|
|
|
+ /* 新增:针对数字列的特殊处理 */
|
|
|
+ .{layout_type}-content-display td.number {{
|
|
|
+ text-align: right;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-family: 'Monaco', 'Menlo', monospace;
|
|
|
+ }}
|
|
|
+
|
|
|
+ /* 新增:针对短文本列的处理 */
|
|
|
+ .{layout_type}-content-display td.short-text {{
|
|
|
+ white-space: nowrap;
|
|
|
+ min-width: 80px;
|
|
|
}}
|
|
|
|
|
|
.{layout_type}-content-display img {{
|
|
|
@@ -269,6 +282,31 @@ class OCRLayoutManager:
|
|
|
border-radius: 4px;
|
|
|
margin: 10px 0;
|
|
|
}}
|
|
|
+
|
|
|
+ /* 新增:响应式表格 */
|
|
|
+ @media (max-width: 768px) {{
|
|
|
+ .{layout_type}-content-display table {{
|
|
|
+ font-size: {max(font_size-2, 8)}px;
|
|
|
+ }}
|
|
|
+ .{layout_type}-content-display th,
|
|
|
+ .{layout_type}-content-display td {{
|
|
|
+ padding: 4px;
|
|
|
+ max-width: 150px;
|
|
|
+ }}
|
|
|
+ }}
|
|
|
+
|
|
|
+ .highlight-text {{
|
|
|
+ background-color: #ffeb3b !important;
|
|
|
+ padding: 2px 4px;
|
|
|
+ border-radius: 3px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #333333 !important;
|
|
|
+ }}
|
|
|
+
|
|
|
+ .selected-highlight {{
|
|
|
+ background-color: #4caf50 !important;
|
|
|
+ color: white !important;
|
|
|
+ }}
|
|
|
</style>
|
|
|
"""
|
|
|
st.markdown(content_style, unsafe_allow_html=True)
|
|
|
@@ -291,45 +329,21 @@ class OCRLayoutManager:
|
|
|
height=300,
|
|
|
key=f"{layout_type}_text_area"
|
|
|
)
|
|
|
-
|
|
|
- # 布局实现
|
|
|
- def create_standard_layout(self, font_size: int = 10, zoom_level: float = 1.0):
|
|
|
- """创建标准布局"""
|
|
|
- if zoom_level is None:
|
|
|
- zoom_level = self.config['styles']['layout']['default_zoom']
|
|
|
-
|
|
|
- # 主要内容区域
|
|
|
- layout = self.config['styles']['layout']
|
|
|
- left_col, right_col = st.columns([layout['content_width'], layout['sidebar_width']])
|
|
|
-
|
|
|
- with left_col:
|
|
|
- self.render_content_section("standard")
|
|
|
-
|
|
|
- # 显示内容
|
|
|
- if self.validator.md_content:
|
|
|
- display_content, render_mode = self.render_md_content("standard")
|
|
|
- self.render_content_by_mode(display_content, render_mode, font_size, "standard")
|
|
|
-
|
|
|
- with right_col:
|
|
|
- self.create_aligned_image_display(zoom_level, "compact")
|
|
|
-
|
|
|
- def create_compact_layout(self, font_size: int = 10, zoom_level: float = 1.0):
|
|
|
+
|
|
|
+ def create_compact_layout(self, config: Dict):
|
|
|
"""创建紧凑的对比布局"""
|
|
|
# 主要内容区域
|
|
|
- layout = self.config['styles']['layout']
|
|
|
- left_col, right_col = st.columns([layout['content_width'], layout['sidebar_width']])
|
|
|
+ layout = config['styles']['layout']
|
|
|
+ font_size = config['styles'].get('font_size', 10)
|
|
|
+ container_height = layout.get('default_height', 600) # 默认高度
|
|
|
+ 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')
|
|
|
|
|
|
with left_col:
|
|
|
- self.render_content_section("compact")
|
|
|
+ self.render_content_section(layout_type)
|
|
|
|
|
|
- # 只保留一个内容区域高度选择
|
|
|
- container_height = st.selectbox(
|
|
|
- "选择内容区域高度",
|
|
|
- [400, 600, 800, 1000, 1200],
|
|
|
- index=2,
|
|
|
- key="compact_content_height"
|
|
|
- )
|
|
|
-
|
|
|
# 快速定位文本选择器(使用不同的key)
|
|
|
if self.validator.text_bbox_mapping:
|
|
|
text_options = ["请选择文本..."] + list(self.validator.text_bbox_mapping.keys())
|
|
|
@@ -343,36 +357,6 @@ class OCRLayoutManager:
|
|
|
if selected_index > 0:
|
|
|
st.session_state.selected_text = text_options[selected_index]
|
|
|
|
|
|
- # 自定义CSS样式
|
|
|
- st.markdown(f"""
|
|
|
- <style>
|
|
|
- .compact-content {{
|
|
|
- height: {container_height}px;
|
|
|
- overflow-y: auto;
|
|
|
- font-size: {font_size}px !important;
|
|
|
- line-height: 1.4;
|
|
|
- border: 1px solid #ddd;
|
|
|
- padding: 10px;
|
|
|
- background-color: #fafafa !important;
|
|
|
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
|
- color: #333333 !important;
|
|
|
- }}
|
|
|
-
|
|
|
- .highlight-text {{
|
|
|
- background-color: #ffeb3b !important;
|
|
|
- padding: 2px 4px;
|
|
|
- border-radius: 3px;
|
|
|
- cursor: pointer;
|
|
|
- color: #333333 !important;
|
|
|
- }}
|
|
|
-
|
|
|
- .selected-highlight {{
|
|
|
- background-color: #4caf50 !important;
|
|
|
- color: white !important;
|
|
|
- }}
|
|
|
- </style>
|
|
|
- """, unsafe_allow_html=True)
|
|
|
-
|
|
|
# 处理并显示OCR内容
|
|
|
if self.validator.md_content:
|
|
|
# 高亮可点击文本
|
|
|
@@ -384,40 +368,30 @@ class OCRLayoutManager:
|
|
|
text,
|
|
|
f'<span class="{css_class}" title="{text[:50]}...">{text}</span>'
|
|
|
)
|
|
|
- st.markdown(
|
|
|
- f'<div class="compact-content">{highlighted_content}</div>',
|
|
|
- unsafe_allow_html=True
|
|
|
- )
|
|
|
-
|
|
|
+ self.render_content_by_mode(highlighted_content, "HTML渲染", font_size, container_height, layout_type)
|
|
|
+
|
|
|
with right_col:
|
|
|
# 修复的对齐图片显示
|
|
|
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(f"""
|
|
|
- <style>
|
|
|
- .aligned-image-container-{layout_type} {{
|
|
|
- margin-top: -70px;
|
|
|
- padding-top: 0px;
|
|
|
- }}
|
|
|
- .aligned-image-container-{layout_type} h1 {{
|
|
|
- margin-top: 0px !important;
|
|
|
- padding-top: 0px !important;
|
|
|
- }}
|
|
|
- /* 修复:确保Plotly图表容器没有额外边距 */
|
|
|
- .js-plotly-plot, .plotly {{
|
|
|
- margin: 0 !important;
|
|
|
- padding: 0 !important;
|
|
|
- }}
|
|
|
- </style>
|
|
|
- """, unsafe_allow_html=True)
|
|
|
-
|
|
|
- st.markdown(f'<div class="aligned-image-container-{layout_type}">', unsafe_allow_html=True)
|
|
|
st.header("🖼️ 原图标注")
|
|
|
|
|
|
- # 方向检测控制面板
|
|
|
+ # 图片控制选项
|
|
|
+ col1, col2, col3, col4 = st.columns(4)
|
|
|
+ with col1:
|
|
|
+ current_zoom = st.slider("图片缩放", 0.3, 2.0, zoom_level, 0.1, key=f"{layout_type}_zoom_level")
|
|
|
+ with col2:
|
|
|
+ show_all_boxes = st.checkbox("显示所有框", value=False, key=f"{layout_type}_show_all_boxes")
|
|
|
+ with col3:
|
|
|
+ fit_to_container = st.checkbox("适应容器", value=True, key=f"{layout_type}_fit_container")
|
|
|
+ with col4:
|
|
|
+ # 显示当前角度状态
|
|
|
+ current_angle = self.get_rotation_angle()
|
|
|
+ st.metric("当前角度", f"{current_angle}°")
|
|
|
+
|
|
|
+ # 方向检测控制面板
|
|
|
with st.expander("🔄 图片方向检测", expanded=False):
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
|
|
|
@@ -498,19 +472,7 @@ class OCRLayoutManager:
|
|
|
st.error(f"错误: {individual['error']}")
|
|
|
st.write("---")
|
|
|
|
|
|
- # 图片控制选项
|
|
|
- col1, col2, col3, col4 = st.columns(4)
|
|
|
- with col1:
|
|
|
- current_zoom = st.slider("图片缩放", 0.3, 2.0, zoom_level, 0.1, key=f"{layout_type}_zoom_level")
|
|
|
- with col2:
|
|
|
- show_all_boxes = st.checkbox("显示所有框", value=False, key=f"{layout_type}_show_all_boxes")
|
|
|
- with col3:
|
|
|
- fit_to_container = st.checkbox("适应容器", value=True, key=f"{layout_type}_fit_container")
|
|
|
- with col4:
|
|
|
- # 显示当前角度状态
|
|
|
- current_angle = self.get_rotation_angle()
|
|
|
- st.metric("当前角度", f"{current_angle}°")
|
|
|
-
|
|
|
+
|
|
|
# 使用增强的图像加载方法
|
|
|
image = self.load_and_rotate_image(self.validator.image_path)
|
|
|
|
|
|
@@ -538,32 +500,6 @@ class OCRLayoutManager:
|
|
|
scaled_bbox = [coord * current_zoom for coord in bbox]
|
|
|
all_boxes.append(scaled_bbox)
|
|
|
|
|
|
- # 增强的调试信息
|
|
|
- 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}°")
|
|
|
-
|
|
|
- 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}°")
|
|
|
-
|
|
|
# 创建交互式图片
|
|
|
fig = self.create_resized_interactive_plot(resized_image, selected_bbox, current_zoom, all_boxes)
|
|
|
|
|
|
@@ -613,6 +549,33 @@ class OCRLayoutManager:
|
|
|
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)
|
|
|
+
|
|
|
+ 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_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:
|
|
|
st.error(f"❌ 图片处理失败: {e}")
|