rm(list=ls()) # 必要なライブラリを読み込む library(shiny) # 事前に定義された関数を含むRスクリプトを読み込む source("./script.R") # パネル用関数 orderInputPanel <- function() { conditionalPanel( condition = "output.showOrderInput", numericInput("target_order", "Target Order:", value = 1, min = 1), actionButton("confirm_order", "Confirm Order"), actionButton("cancel_order", "Cancel") ) } # キャプションの並び替え関数 sort_captions_by_search <- function(data, search_term) { if (is.null(search_term) || search_term == "") { return(data) } # 検索語でのマッチングスコアを計算 scores <- as.integer(grepl(search_term, data$caption, ignore.case = TRUE)) # スコアの降順でデータフレームを並び替え return(data[order(-scores), ]) } # UIを定義 ui <- fluidPage( # アプリケーションのタイトル titlePanel("SDtagEditoR"), # CSSを追加 tags$style(type = "text/css", "#image_captions { white-space: pre-wrap; }", ".shiny-action-button { margin-bottom: 50px; }" ), # JavaScript関数を追加 tags$head(tags$script(HTML(" function copyToClipboard(text) { var dummy = document.createElement('textarea'); document.body.appendChild(dummy); dummy.value = text; dummy.select(); document.execCommand('copy'); document.body.removeChild(dummy); } "))), tags$script(HTML(" function handleCaptionClick(caption, source) { var operationModeElement = document.getElementById('operation_mode_UI'); if (source === 'remove_RDB') { Shiny.setInputValue('remove_relation_index', caption, {priority: 'event'}); } else if (operationModeElement && operationModeElement.innerText === 'direct remove') { if (source === 'frequency') { Shiny.setInputValue('clicked_caption_frequency', caption, {priority: 'event'}); } else { Shiny.setInputValue('clicked_caption', caption, {priority: 'event'}); } } else if (operationModeElement && operationModeElement.innerText === 'Database Making') { Shiny.setInputValue('clicked_caption_RDB', caption, {priority: 'event'}); } else { copyToClipboard(caption); } } ")), # ユーザー入力を受け付ける部分 sidebarLayout( sidebarPanel( width = 3, uiOutput("operation_mode_UI"), # ディレクトリパスを入力するテキストボックス textInput("directory_path", "Enter the directory path:", value = ""), # キャプションデータを読み込むボタン actionButton("load_data", "Load Captions Data"), tags$br(), tags$br(), conditionalPanel( condition = "output.dataLoaded", # 画像の選択 selectInput("selected_image", "Choose an image:", choices = NULL), # 前後の画像に移動するボタン actionButton("prev_image", "Prev"), actionButton("next_image", "Next"), tags$br(), tags$br(), # Direct Removerモード actionButton("direct_remover", "Enter Direct Remover Mode"), conditionalPanel( condition = "output.operation_mode_UI === 'direct remove'", actionButton("exit_direct_remover", "Exit Direct Remover") ), tags$br(), tags$br(), # Database Makingモード actionButton("enter_database_mode", "Enter Database Making Mode"), conditionalPanel( condition = "output.operation_mode_UI === 'Database Making'", actionButton("exit_database_mode", "Exit Database Making Mode"), tags$br(), tags$br(), textInput("parent_caption_input", "Parent Caption:"), textInput("child_caption_input", "Child Caption:"), actionButton("add_to_RDB", "Add to Database"), actionButton("clear_inputs_RDB", "Clear"), tags$br(), tags$br(), fileInput("load_relation_database", "Load Relation DB", accept = ".csv"), downloadButton("save_relation_database", "Save Relation DB"), tags$br(), tags$br(), actionButton("aggregate_single", "Aggregation Single"), actionButton("aggregate_all", "Aggregation All") ), tags$br(), tags$br(), conditionalPanel( condition = "output.operation_mode_UI !== 'direct remove' && output.operation_mode_UI !== 'Database Making'", actionButton("shuffle_single", "Shuffle Single"), actionButton("shuffle_all", "Shuffle All"), tags$br(), tags$br(), numericInput("frequency_threshold", "Frequency Threshold:", value = 5, min = 1), actionButton("remove_low_freq", "Remove Low Frequency Captions"), tags$br(), tags$br(), textInput("edit_caption", "Caption to Edit:", ""), actionButton("remove_single", "Remove Single"), actionButton("remove_from_all", "Remove All"), # actionButton("edit_interactively", "Edit Interactively"), tags$br(), actionButton("add_single", "Add Single"), actionButton("add_to_all", "Add All"), tags$br(), actionButton("move_caption", "Move Single"), actionButton("move_caption_all", "Move All"), tags$br(), # actionButton("remove_related_single", "Remove Related Single"), # actionButton("remove_related_all", "Remove Related All"), orderInputPanel(), # textInputPanel(), # ここで関数を呼び出しています tags$br(), ), tags$br(), tags$br(), actionButton("restore_captions", "Restore captions"), tags$br(), actionButton("output_captions", "Output Edited Captions") ) ), mainPanel( fluidRow( column(6, imageOutput("image_display", width = "100%", height = "50%"), tags$br(), uiOutput("image_captions") ), column(6, verbatimTextOutput("log_output"), textInput("search_caption", "Search Caption:", ""), actionButton("search_button", "Search"), actionButton("clear_search", "Clear"), tags$br(), tags$br(), fluidRow( column(4, tableOutput("selected_image_captions")), column(4, tableOutput("caption_frequency_table_with_links")), column(4, tableOutput("relation_database_display")) ) ) ) ) ) ) # サーバーロジックを定義 server <- function(input, output, session) { # リアクティブな変数を定義 directory_path <- reactiveVal() captions_data <- reactiveVal() caption_frequency <- reactiveVal() operation_mode <- reactiveVal("Normal") log_text <- reactiveVal("Log:\n") ## ログ表示用 # ログを追加する関数 add_log <- function(message) { current_log <- log_text() new_log <- paste(current_log, message, "\n") log_text(new_log) } # ログを更新する関数 update_log <- function(message) { log_text(message) } ## パネルの条件分岐用 # 読み込み確認 output$dataLoaded <- reactive({ !is.null(captions_data()) }) outputOptions(output, "dataLoaded", suspendWhenHidden=FALSE) # Operation ModeをUI側に渡す output$operation_mode_UI <- renderText({ return(operation_mode()) }) outputOptions(output, "operation_mode_UI", suspendWhenHidden=FALSE) ## 一時保存用 # 一時保存用のリアクティブ変数を作成 temp_captions_data <- reactiveVal() # input$selected_imageが変化したときに、現在のcaptions_data()を一時保存 observeEvent(input$selected_image, { temp_captions_data(captions_data()) }, ignoreNULL = TRUE) ## 検索機能 searched_caption <- reactiveVal(NULL) observeEvent(input$search_button, { searched_caption(input$search_caption) }) observeEvent(input$clear_search, { searched_caption(NULL) updateTextInput(session, "search_caption", value = "") }) ## Load Data observeEvent(input$load_data, { # ボタンが押されたときに実行されるコード temp_directory_path <- isolate(input$directory_path) temp_captions_data <- read_captions_from_directory(temp_directory_path) # エラーが発生した場合、Shiny UI上でエラーメッセージを表示 if (is.null(temp_captions_data)) { showNotification("Error in read_captions_from_directory: No .txt files found in the specified directory.", type = "error") return() } temp_caption_frequency <- get_caption_frequency(temp_captions_data) # リアクティブな変数を更新 directory_path(temp_directory_path) captions_data(temp_captions_data) caption_frequency(temp_caption_frequency) # 画像の選択のための選択肢を更新 unique_images <- unique(captions_data()$image_path) updateSelectInput(session, "selected_image", choices = unique_images, selected = unique_images[1]) update_log("Captions data loaded.") }) # 画像の移動 observeEvent(input$prev_image, { current_index <- which(unique(captions_data()$image_path) == input$selected_image) if (current_index > 1) { updateSelectInput(session, "selected_image", selected = unique(captions_data()$image_path)[current_index - 1]) } }) observeEvent(input$next_image, { current_index <- which(unique(captions_data()$image_path) == input$selected_image) if (current_index < length(unique(captions_data()$image_path))) { updateSelectInput(session, "selected_image", selected = unique(captions_data()$image_path)[current_index + 1]) } }) # Direct Removerモード observeEvent(input$direct_remover, { operation_mode("direct remove") update_log("DIRECT REMOVER MODE.") }) observeEvent(input$exit_direct_remover, { operation_mode("Normal") update_log("Exited Direct Remover mode.") }) ## Relation Databaseモード relation_database <- reactiveVal(data.frame(parent_caption = character(0), child_caption = character(0), stringsAsFactors = FALSE)) observeEvent(input$enter_database_mode, { operation_mode("Database Making") update_log("DATABASE MAKING MODE.") }) observeEvent(input$exit_database_mode, { operation_mode("Normal") update_log("Exited Database Making mode.") }) observeEvent(input$clicked_caption_RDB, { if (input$parent_caption_input == "") { updateTextInput(session, "parent_caption_input", value = input$clicked_caption_RDB) } else { updateTextInput(session, "child_caption_input", value = input$clicked_caption_RDB) } }) observeEvent(input$add_to_RDB, { # 親キャプションまたは子キャプションが空欄の場合、または親キャプションと子キャプションが同じ場合、エラーログを出力 if (input$parent_caption_input == "" || input$child_caption_input == "" || input$parent_caption_input == input$child_caption_input) { showNotification("Error: Invalid parent or child caption.", type = "error") return() } # 同じ組み合わせがrelation_databaseに存在するかチェック existing_relation <- with(relation_database(), parent_caption == input$parent_caption_input & child_caption == input$child_caption_input) if (any(existing_relation)) { showNotification("Error: This relation already exists in the database.", type = "error") return() # このobserveEventの処理を終了 } # relation_databaseに関係を追加するコード new_relation <- data.frame(parent_caption = input$parent_caption_input, child_caption = input$child_caption_input) updated_database <- rbind(relation_database(), new_relation) relation_database(updated_database) update_log("Relation added to database.") }) observeEvent(input$clear_inputs_RDB, { updateTextInput(session, "parent_caption_input", value = "") updateTextInput(session, "child_caption_input", value = "") }) observeEvent(input$aggregate_single, { updated_data <- captions_data() # 選択されている画像のcaptionを別変数に保存 captions_for_selected_image <- updated_data$caption[updated_data$image_path == input$selected_image] if (nrow(relation_database()) > 0) { for (i in 1:nrow(relation_database())) { parent <- relation_database()$parent_caption[i] child <- relation_database()$child_caption[i] # parent_captionの存在確認を行い、存在する場合remove_caption_and_adjust_orderを実行 if (parent %in% captions_for_selected_image) { updated_data <- remove_caption_and_adjust_order(updated_data, input$selected_image, child) } } } captions_data(updated_data) # キャプションの頻度を更新 updated_caption_frequency <- get_caption_frequency(captions_data()) caption_frequency(updated_caption_frequency) update_log("Captions aggregated for this image.") }) observeEvent(input$aggregate_all, { updated_data <- captions_data() if (nrow(relation_database()) > 0) { for (image_path in unique(updated_data$image_path)) { # 各画像のcaptionを別変数に保存 captions_for_image <- updated_data$caption[updated_data$image_path == image_path] for (i in 1:nrow(relation_database())) { parent <- relation_database()$parent_caption[i] child <- relation_database()$child_caption[i] # parent_captionの存在確認を行い、存在する場合remove_caption_and_adjust_orderを実行 if (parent %in% captions_for_image) { updated_data <- remove_caption_and_adjust_order(updated_data, image_path, child) } } } } captions_data(updated_data) # キャプションの頻度を更新 updated_caption_frequency <- get_caption_frequency(captions_data()) caption_frequency(updated_caption_frequency) update_log("Captions aggregated for all images.") }) observeEvent(input$remove_relation_index, { index_to_remove <- as.numeric(input$remove_relation_index) # 削除する関係の情報を保存 parent_to_remove <- relation_database()$parent_caption[index_to_remove] child_to_remove <- relation_database()$child_caption[index_to_remove] # 関係を削除 relation_database(relation_database()[-index_to_remove, ]) # ログを更新 update_log(paste("Removed relation:", parent_to_remove, "->", child_to_remove)) }) # Relation Databaseをセーブ output$save_relation_database <- downloadHandler( filename = function() { paste("relation_database_", Sys.Date(), ".csv", sep="") }, content = function(file) { write.csv(relation_database(), file, row.names = FALSE) }, contentType = "text/csv" ) observeEvent(input$load_relation_database, { # ファイルが選択された場合のみ処理を実行 if (!is.null(input$load_relation_database$datapath)) { # CSVファイルを読み込む new_data <- read.csv(input$load_relation_database$datapath, stringsAsFactors = FALSE) # データの整合性チェック if (!all(c("parent_caption", "child_caption") %in% names(new_data))) { showNotification("Error: The file does not have the required columns (parent_caption and child_caption).", type = "error") return() } if (any(is.na(new_data$parent_caption)) || any(is.na(new_data$child_caption))) { showNotification("Error: The file contains missing values in parent_caption or child_caption.", type = "error") return() } if (any(nchar(new_data$parent_caption) == 0) || any(nchar(new_data$child_caption) == 0)) { showNotification("Error: The file contains empty strings in parent_caption or child_caption.", type = "error") return() } # relation_database()に新しいデータを格納 relation_database(new_data) update_log("Relation database loaded.") } }) ## Shuffle Order for Current Image observeEvent(input$shuffle_single, { if (operation_mode() != "Normal") { showNotification("Another operation is in progress. Please finish or cancel it first.", type = "error") return() } current_image <- input$selected_image updated_data <- captions_data() captions_for_current_image <- filter(updated_data, image_path == current_image) # Orderをランダムにする new_order <- sample(1:nrow(captions_for_current_image), nrow(captions_for_current_image)) captions_for_current_image$caption_order <- new_order # データを更新 updated_data[updated_data$image_path == current_image, ] <- captions_for_current_image captions_data(updated_data) update_log("captions of this image was shauffled.") }) # Shuffle Order for All Images observeEvent(input$shuffle_all, { if (operation_mode() != "Normal") { showNotification("Another operation is in progress. Please finish or cancel it first.", type = "error") return() } updated_data <- captions_data() unique_images <- unique(updated_data$image_path) for (image in unique_images) { captions_for_image <- filter(updated_data, image_path == image) new_order <- sample(1:nrow(captions_for_image), nrow(captions_for_image)) captions_for_image$caption_order <- new_order updated_data[updated_data$image_path == image, ] <- captions_for_image } captions_data(updated_data) update_log("captions of all images was shauffled.") }) # remove_low_freqボタンが押されたときの処理 observeEvent(input$remove_low_freq, { if (operation_mode() != "Normal") { showNotification("Another operation is in progress. Please finish or cancel it first.", type = "error") return() } if (!is.null(captions_data())) { threshold <- input$frequency_threshold updated_data <- remove_low_frequency_captions(captions_data(), threshold) captions_data(updated_data) # キャプションの頻度を更新 updated_caption_frequency <- get_caption_frequency(captions_data()) caption_frequency(updated_caption_frequency) } update_log("Low frequency captions removed.") }) # Remove Captions from All Images observeEvent(input$remove_from_all, { if (operation_mode() != "Normal") { showNotification("Another operation is in progress. Please finish or cancel it first.", type = "error") return() } # 指定されたキャプションをすべての画像から削除 target_caption <- input$edit_caption updated_data <- captions_data() unique_images <- unique(updated_data$image_path[updated_data$caption == target_caption]) for (image in unique_images) { updated_data <- remove_caption_and_adjust_order(updated_data, image, target_caption) } captions_data(updated_data) # キャプションの頻度を更新 updated_caption_frequency <- get_caption_frequency(captions_data()) caption_frequency(updated_caption_frequency) update_log("Caption removed from all images.") }) # Remode/Add Single/Add all/move captionの処理 showOrderInput <- reactiveVal(FALSE) showTextInput <- reactiveVal(FALSE) observeEvent(input$remove_single, { if (operation_mode() != "Normal") { showNotification("Another operation is in progress. Please finish or cancel it first.", type = "error") return() } target_caption <- input$edit_caption if (target_caption %in% captions_data()$caption[captions_data()$image_path == input$selected_image]) { updated_data <- remove_caption_and_adjust_order(captions_data(), input$selected_image, target_caption) captions_data(updated_data) update_log("Caption removed from this image.") } else { showNotification("Error: Caption not found in the selected image.", type = "error") } }) observeEvent(input$add_single, { if (operation_mode() != "Normal") { showNotification("Another operation is in progress. Please finish or cancel it first.", type = "error") return() } operation_mode("add_single") showOrderInput(TRUE) update_log("ADD SINGLE MODE.") }) observeEvent(input$add_to_all, { if (operation_mode() != "Normal") { showNotification("Another operation is in progress. Please finish or cancel it first.", type = "error") return() } operation_mode("add_all") showOrderInput(TRUE) update_log("ADD ALL MODE.") }) observeEvent(input$move_caption, { if (operation_mode() != "Normal") { showNotification("Another operation is in progress. Please finish or cancel it first.", type = "error") return() } target_caption <- input$edit_caption if (target_caption %in% captions_data()$caption[captions_data()$image_path == input$selected_image]) { operation_mode("move_caption") showOrderInput(TRUE) update_log("MOVE CAPTION SINGLE MODE.") } else { showNotification("Error: Caption not found in the selected image.", type = "error") } }) observeEvent(input$move_caption_all, { if (operation_mode() != "Normal") { showNotification("Another operation is in progress. Please finish or cancel it first.", type = "error") return() } operation_mode("move_caption_all") showOrderInput(TRUE) update_log("MOVE CAPTION ALL MODE.") }) observeEvent(input$confirm_order, { switch(operation_mode(), "add_single" = { # Add to Singleの処理 target_caption <- input$edit_caption target_order <- max(1, floor(input$target_order)) updated_data <- add_caption_at_order(captions_data(), input$selected_image, target_caption, target_order) captions_data(updated_data) update_log("Caption added to target order.") }, "add_all" = { target_caption <- input$edit_caption target_order <- target_order <- max(1, floor(input$target_order)) updated_data <- captions_data() unique_images <- unique(updated_data$image_path) for (image in unique_images) { updated_data <- add_caption_at_order(updated_data, image, target_caption, target_order) } captions_data(updated_data) update_log("Caption added to all images.") }, "move_caption" = { # Move Captionの処理 target_caption <- input$edit_caption target_order <- target_order <- max(1, floor(input$target_order)) updated_data <- move_caption_order(captions_data(), input$selected_image, target_caption, target_order) captions_data(updated_data) update_log("Caption moved to target order.") }, "move_caption_all" = { # Move Caption Allの処理 target_caption <- input$edit_caption target_order <- target_order <- max(1, floor(input$target_order)) updated_data <- captions_data() unique_images <- unique(updated_data$image_path[updated_data$caption == target_caption]) for (image in unique_images) { updated_data <- move_caption_order(updated_data, image, target_caption, target_order) } captions_data(updated_data) update_log("Caption moved to target order from all images.") } ) # キャプションの頻度を更新 updated_caption_frequency <- get_caption_frequency(captions_data()) caption_frequency(updated_caption_frequency) operation_mode("Normal") showOrderInput(FALSE) }) observeEvent(input$cancel_order, { operation_mode("Normal") showOrderInput(FALSE) update_log("Operation was canceled.") }) observeEvent(input$cancel_text, { operation_mode("Normal") showTextInput(FALSE) update_log("Operation was canceled.") }) output$showOrderInput <- reactive({ showOrderInput() }) outputOptions(output, "showOrderInput", suspendWhenHidden=FALSE) output$showTextInput <- reactive({ showTextInput() }) outputOptions(output, "showTextInput", suspendWhenHidden=FALSE) # Direct Remove MODE observeEvent(input$clicked_caption, { target_caption <- input$clicked_caption # 単一画像のキャプションを削除 updated_data <- remove_caption_and_adjust_order(captions_data(), input$selected_image, target_caption) captions_data(updated_data) # キャプションの頻度を更新 updated_caption_frequency <- get_caption_frequency(captions_data()) caption_frequency(updated_caption_frequency) update_log("Caption removed from this image.") }) observeEvent(input$clicked_caption_frequency, { target_caption <- input$clicked_caption_frequency # 指定されたキャプションをすべての画像から削除 updated_data <- captions_data() unique_images <- unique(updated_data$image_path[updated_data$caption == target_caption]) for (image in unique_images) { updated_data <- remove_caption_and_adjust_order(updated_data, image, target_caption) } captions_data(updated_data) # キャプションの頻度を更新 updated_caption_frequency <- get_caption_frequency(captions_data()) caption_frequency(updated_caption_frequency) update_log("Caption removed from all images.") }) # yes/no選択dialog show_confirmation_dialog <- function() { showModal( modalDialog( title = "Confirmation", "Are you sure?", footer = tagList( modalButton("No"), actionButton("yes_button", "Yes") ) ) ) } # リアクティブな変数を作成して、ユーザーの選択を保存 user_choice <- reactiveVal(NULL) # ユーザーの選択に基づいてリアクティブな値を更新 observeEvent(input$yes_button, { user_choice("Yes") removeModal() }) ## Restore captions: calling confirmation dialog observeEvent(input$restore_captions, { show_confirmation_dialog() observeEvent(user_choice(), { if (user_choice() == "Yes") { captions_data(temp_captions_data()) # キャプションの頻度を更新 updated_caption_frequency <- get_caption_frequency(captions_data()) caption_frequency(updated_caption_frequency) } # リアクティブな変数をリセット user_choice(NULL) }, ignoreNULL = TRUE) }) ## 書き出し observeEvent(input$output_captions, { show_confirmation_dialog() observeEvent(user_choice(), { if (user_choice() == "Yes") { # 出力ディレクトリを確認し、存在しない場合は作成 if (!dir.exists("./output")) { dir.create("./output") } # 各画像に対応するキャプションをCSV形式で書き出す unique_images <- unique(captions_data()$image_path) for (image_path in unique_images) { # ファイル名を生成 image_name <- basename(image_path) output_filename <- paste0("./output/", tools::file_path_sans_ext(image_name), ".txt") # キャプションをCSV形式で取得 csv_captions <- capture.output(print_image_captions_as_csv(captions_data(), image_path)) # データをファイルに書き出す writeLines(csv_captions, con = output_filename) } # ログに書き出し完了のメッセージを追加 update_log("Captions data written to ./output/ directory.") } # リアクティブな変数をリセット user_choice(NULL) }, ignoreNULL = TRUE) }) # レンダリング output$image_display <- renderImage({ list(src = file.path(input$selected_image), alt = "Selected Image", width = "80%") }, deleteFile = FALSE) output$image_captions <- renderUI({ if (!is.null(captions_data())) { selected_image_path <- input$selected_image captions <- filter(captions_data(), image_path == selected_image_path) %>% arrange(caption_order) %>% pull(caption) # キャプションをリンク付きのHTMLに変換 linked_captions <- lapply(captions, function(caption) { if (!is.null(searched_caption()) && grepl(searched_caption(), caption)) { sprintf("%s", caption, caption) } else { sprintf("%s", caption, caption) } }) HTML(paste(linked_captions, collapse = ", ")) } }) output$log_output <- renderText({ log_text() }) output$selected_image_captions <- renderTable({ if (!is.null(captions_data())) { selected_image_path <- input$selected_image selected_captions <- filter(captions_data(), image_path == selected_image_path) %>% select(caption, caption_order) %>% arrange(caption_order) # キャプションの並び替え sorted_captions <- sort_captions_by_search(selected_captions, searched_caption()) # caption列の各エントリにリンクを追加 sorted_captions$caption <- sprintf("%s", sorted_captions$caption, sorted_captions$caption) return(sorted_captions) } }, sanitize.text.function = function(x) x) output$caption_frequency_table_with_links <- renderTable({ if (!is.null(caption_frequency())) { freq_data <- caption_frequency() # キャプションの並び替え sorted_freq_data <- sort_captions_by_search(freq_data, searched_caption()) # caption列の各エントリにリンクを追加 sorted_freq_data$caption <- sprintf("%s", sorted_freq_data$caption, sorted_freq_data$caption) return(sorted_freq_data) } }, sanitize.text.function = function(x) x) output$relation_database_display <- renderTable({ db <- relation_database() # relation_databaseが空でない場合のみリンクを作成 if (nrow(db) > 0) { db$remove <- sprintf("Remove", 1:nrow(db)) } db }, sanitize.text.function = function(x) x) # HTMLをエスケープしないようにする } # アプリを実行 shinyApp(ui = ui, server = server)