diff --git a/README.md b/README.md index 717a863..48a52a5 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,11 @@ pip install -r requirements.txt ``` +または +```bash +python -m pip install -r .\requirements.txt +``` + ### Q2: 📁 参照 ボタンを押してもダイアログが開かない **A2**: WSL(Linux環境)で実行している可能性があります。このツールはWindows環境での使用を前提としています。 @@ -267,7 +272,7 @@ ```bash # 自動リロード有効 -streamlit run app.py +python -m streamlit run app.py # ポート指定 streamlit run app.py --server.port 8501 diff --git a/app.py b/app.py index c803e6f..98fad8e 100644 --- a/app.py +++ b/app.py @@ -26,7 +26,7 @@ ) # UI Constants -PREDICTION_OPTIONS = ["なし", "あり"] +PREDICTION_OPTIONS = ["あり", "なし"] # 「あり」を上に REASONS_OPTIONS = [ "石灰化プラークが多い", "石灰化プラークが少ない", @@ -39,6 +39,9 @@ MAX_CONFIDENCE = 100 DEFAULT_CONFIDENCE = 50 +# Image display settings +DEFAULT_IMAGE_WIDTH = 600 # Default image width in pixels (デフォルトより少し縮小) + # Page configuration st.set_page_config( page_title="IVUS合併症アノテーションツール", @@ -183,8 +186,8 @@ excel_path = st.text_input( "Excelラベルファイルパス", value=st.session_state.temp_excel_path, - help="「📁 参照」ボタンでExcelファイルを選択してください", - placeholder="「📁 参照」ボタンでファイルを選択", + help="「📄 参照」ボタンでExcelファイルを選択してください", + placeholder="「📄 参照」ボタンでファイルを選択", label_visibility="collapsed" ) # Update session state when user types @@ -192,7 +195,7 @@ st.session_state.temp_excel_path = excel_path with col2: - if st.button("📁 参照", key="btn_browse_excel", use_container_width=True): + if st.button("📄 参照", key="btn_browse_excel", use_container_width=True): if TKINTER_AVAILABLE: # Use directory of current path as initial dir if exists initial = os.path.dirname(st.session_state.temp_excel_path) if st.session_state.temp_excel_path else os.path.expanduser("~") @@ -222,7 +225,7 @@ elif not os.path.exists(data_root): errors.append(f"❌ データディレクトリが見つかりません: {data_root}") if not excel_path or excel_path.strip() == "": - errors.append("❌ Excelラベルファイルパスは必須です - 「📁 参照」ボタンでファイルを選択してください") + errors.append("❌ Excelラベルファイルパスは必須です - 「📄 参照」ボタンでファイルを選択してください") elif not os.path.exists(excel_path): errors.append(f"❌ Excelファイルが見つかりません: {excel_path}") @@ -299,9 +302,13 @@ st.metric("進捗", f"{progress:.1%}") -def render_case_selector(): - """サイドバーに症例ナビゲーションを表示。""" - st.sidebar.header("症例ナビゲーション") +def render_case_selector(container): + """症例ナビゲーションを表示(右側に配置)。 + + Args: + container: Streamlit container to render in + """ + container.header("症例ナビゲーション") current_case_id = get_current_case_id() @@ -311,7 +318,7 @@ for cid in st.session_state.case_ids ] - selected_idx = st.sidebar.selectbox( + selected_idx = container.selectbox( "症例を選択", range(len(st.session_state.case_ids)), index=st.session_state.current_case_idx, @@ -323,7 +330,7 @@ st.rerun() # Previous/Next buttons - col1, col2 = st.sidebar.columns(2) + col1, col2 = container.columns(2) with col1: if st.button("← 前へ", disabled=st.session_state.current_case_idx == 0): st.session_state.current_case_idx -= 1 @@ -338,41 +345,81 @@ # Display current case info if current_case_id: - st.sidebar.info(f"**現在の症例:** {current_case_id}") + container.info(f"**現在の症例:** {current_case_id}") -def render_image_viewer(case_id: Union[int, float]): +def render_image_viewer(case_id: Union[int, float], container): """ スライダー付き画像ビューアーを表示。 Args: case_id: 現在の症例ID + container: Streamlit container to render in """ images = get_case_images(st.session_state.data_root, case_id) if len(images) == 0: - st.error(f"症例 {case_id} の画像が見つかりません") + container.error(f"症例 {case_id} の画像が見つかりません") return + # Initialize frame index in session state if not exists + frame_key = f"frame_idx_{case_id}" + if frame_key not in st.session_state: + st.session_state[frame_key] = 0 + + # Image zoom slider (拡大率の調整) + zoom_percent = container.slider( + "画像拡大率 (%)", + min_value=30, + max_value=150, + value=80, # デフォルト値を少し縮小 + step=10, + key=f"zoom_{case_id}" + ) + # Image slider - frame_idx = st.slider( + frame_idx = container.slider( "フレーム番号", min_value=0, max_value=len(images) - 1, - value=0, + value=st.session_state[frame_key], key=f"frame_slider_{case_id}" ) - # Display image + # Update session state + st.session_state[frame_key] = frame_idx + + # Display image with custom width try: img = Image.open(images[frame_idx]) - st.image( + + # Calculate display width based on zoom percentage + display_width = int(DEFAULT_IMAGE_WIDTH * zoom_percent / 100) + + container.image( img, caption=f"症例 {case_id} - フレーム {frame_idx + 1}/{len(images)}", - use_container_width=True + width=display_width ) + + # Arrow buttons for frame navigation (画像の右下に配置) + col1, col2, col3 = container.columns([4, 1, 1]) + with col1: + st.write("") # Spacer + with col2: + if st.button("◀", key=f"prev_frame_{case_id}", disabled=frame_idx == 0, use_container_width=True): + st.session_state[frame_key] = frame_idx - 1 + st.rerun() + with col3: + if st.button("▶", key=f"next_frame_{case_id}", disabled=frame_idx >= len(images) - 1, use_container_width=True): + st.session_state[frame_key] = frame_idx + 1 + st.rerun() + except Exception as e: - st.error(f"画像読み込みエラー: {e}") + container.error(f"画像読み込みエラー: {e}") + + # Keyboard navigation support (キーボードの矢印キーでフレーム移動) + container.caption("💡 ヒント: スライダーをクリックしてから矢印キー(←→)でフレーム移動、または◀▶ボタンをクリックできます") def render_annotation_form(case_id: Union[int, float]) -> dict: @@ -519,7 +566,6 @@ return render_header() - render_case_selector() current_case_id = get_current_case_id() @@ -536,10 +582,19 @@ st.error(error_msg) return - # Main layout: image viewer in main area - render_image_viewer(current_case_id) + # Main layout: Left - Image viewer, Right - Navigation + Annotation + left_col, right_col = st.columns([3, 1]) - # Annotation form in sidebar + # Left side: Image viewer + with left_col: + render_image_viewer(current_case_id, left_col) + + # Right side: Navigation and Annotation + with right_col: + render_case_selector(right_col) + st.markdown("---") + + # Annotation form in sidebar (keeps original position) annotation_data = render_annotation_form(current_case_id) # Save button