diff --git a/CardiAP.ipynb b/CardiAP.ipynb index 9ceb054..161d572 100644 --- a/CardiAP.ipynb +++ b/CardiAP.ipynb @@ -58,7 +58,8 @@ "\n", "pd.set_option('display.max_rows', 20)\n", "warnings.filterwarnings(\"ignore\", category=np.VisibleDeprecationWarning) \n", - "warnings.filterwarnings(\"ignore\", category=RuntimeWarning) " + "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + "uploader = {'value': {}}" ] }, { @@ -150,19 +151,19 @@ "source": [ "def window_open_button(url):\n", " display(Javascript(f'window.open(\"{url.tooltip}\");'))\n", - " \n", + "\n", "def load_cv2_image_from_bytes(bytes_):\n", " return cv2.imdecode(np.frombuffer(bytes_, dtype=np.uint8), cv2.IMREAD_COLOR)\n", "\n", - "def get_uploader_contents(uploader):\n", - " return [uploader.value[file][\"content\"] for file in list(uploader.value)]\n", + "def get_uploader_contents():\n", + " return uploader['value'].values()\n", "\n", - "def get_uploader_filenames(uploader): \n", - " return list(uploader.value.keys())\n", + "def get_uploader_filenames():\n", + " return list(uploader['value'].keys())\n", "\n", "def display_html(html):\n", " display(widgets.HTML(value=html))\n", - " \n", + "\n", "def create_download_link( df, title = \"Download CSV file\", filename = \"data.csv\"):\n", " csv = df.to_csv(sep='\\t')\n", " b64 = base64.b64encode(csv.encode())\n", @@ -170,7 +171,7 @@ " html = '{title}'\n", " html = html.format(payload=payload,title=title,filename=filename)\n", " return HTML(html)\n", - " \n", + "\n", "def create_inttext(description, min = None, max = None):\n", " return widgets.BoundedIntText(\n", " value=0,\n", @@ -179,9 +180,9 @@ " description=description,\n", " disabled=False\n", " )\n", - " \n", + "\n", "def plot_analysis_result_slice(df,slice_number):\n", - " \n", + "\n", " fig, ((ax1, ax2)) = plt.subplots(2, figsize=(20,10))\n", "\n", " ax1.plot(df.loc[slice_number, \"amplitudes\"])\n", @@ -192,8 +193,8 @@ " ax2.plot(df.loc[slice_number, \"intensities\"])\n", " ax2.set_title('Intensities')\n", " ax2.grid()\n", - " \n", - " plt.show() \n", + "\n", + " plt.show()\n", "\n", "def read_progress_img():\n", " animatedGif = \"./assets/progress.gif\"\n", @@ -207,9 +208,9 @@ " width=100,\n", " height=100)\n", "\n", - " \n", + "\n", "def render_image_visualization(analysis_results):\n", - " render_analysis_results_visualization(analysis_results, \n", + " render_analysis_results_visualization(analysis_results,\n", " title = \"Peaks\",\n", " kind = \"image\",\n", " download_filename = \"complete_cell\",\n", @@ -217,48 +218,48 @@ "\n", "\n", "def render_slice_visualization(analysis_results, slice_number):\n", - " render_analysis_results_visualization(analysis_results, \n", + " render_analysis_results_visualization(analysis_results,\n", " title = \"Slice\",\n", " kind = \"slices\",\n", " download_filename = f\"slice_{slice_number}\",\n", " progress = with_progress,\n", - " slice_number = slice_number) \n", - " \n", + " slice_number = slice_number)\n", + "\n", "def render_analysis_results_visualization(analysis_results, title, kind, download_filename, progress, slice_number = 0):\n", " \"\"\"\n", - " Renders analysis results into three sections: \n", + " Renders analysis results into three sections:\n", " - Peaks table\n", " - Table download\n", " - Peaks plot\n", " \"\"\"\n", " def filter_slice(table):\n", " return table[table.slice_number == slice_number].drop(columns = \"slice_number\")\n", - " \n", + "\n", " display_html(f\"
\n", " \n", " Insufficient {e.feature()} data @ {shape}. Please try selecting a different image area or settings\n", "
\n", " \"\"\")\n", - " \n", + "\n", " results_box.clear_output()\n", " results_tab = widgets.Tab()\n", " results_tab.children = children\n", " display(results_tab)\n", - " for index, filename in enumerate(get_uploader_filenames(uploader)):\n", + " for index, filename in enumerate(get_uploader_filenames()):\n", " results_tab.set_title(index, f\"Cell {filename}\")\n", "\n", "\n", @@ -471,7 +476,7 @@ "\n", " smoothing_title = widgets.HTML(\"Select your file to initialize the analysis
\"\n", + "header_text_upload = widgets.HTML(value=html_text_upload)\n", + "upload_vbox = widgets.VBox([header_text_upload, file_chooser], layout=box_upload_layout)\n", + "\n", + "# Pre-crop Control Section\n", + "\n", + "start_btn = widgets.Button(description=\"Start\", button_style='primary', icon = 'fa-play')\n", + "clear_btn = widgets.Button(description=\"Clear\", button_style='danger', icon = 'fa-trash', button_color = 'salmon')\n", + "\n", + "def display_the_pre_crop_controls_section():\n", + " global actions_vbox\n", + " actions_vbox = widgets.VBox([start_btn, clear_btn], layout=btn_layout)\n", + " display(actions_vbox)\n", + "\n", + "# Image Generation Confirmation Section\n", + "\n", + "generation_process_box_layout = box_layout\n", + "generation_process_box_layout.width = '450px'\n", + "generation_process_btn_layout = Layout(display='flex', flex_flow='row', margin= 'auto')\n", + "generation_process_content_question = widgets.Label(value=\"Are you sure you want to continue with the process?\")\n", + "generation_process_content_recommendation = widgets.Label(value=\"It is recommended to have at least twice the size of the original file.\")\n", + "generation_process_content_top_section = widgets.VBox([generation_process_content_question, generation_process_content_recommendation])\n", + "generation_process_confirm_btn = widgets.Button(description=\"Confirm\", button_style='primary', icon = 'fa-play', button_color= 'forestgreen')\n", + "generation_process_cancel_btn = widgets.Button(description=\"Cancel\", button_style='danger', icon = 'fa-ban', button_color= 'salmon')\n", + "generation_process_content_bottom_section = widgets.VBox([generation_process_confirm_btn, generation_process_cancel_btn], layout=generation_process_btn_layout)\n", + "\n", + "@selection_box.capture()\n", + "def display_the_generation_process_section(_change):\n", + " global generation_process_tab, file_name, file_extension, selected_file\n", + " selected_file = file_chooser.selected\n", + " file_name, file_extension = os.path.splitext(file_chooser.selected_filename)\n", + " generation_process_content = [widgets.VBox([generation_process_content_top_section, generation_process_content_bottom_section])]\n", + " generation_process_tab = widgets.Tab(children=generation_process_content, layout=generation_process_box_layout)\n", + " generation_process_tab.set_title(0, \"Generation Process\")\n", + " if not is_image(file_extension):\n", + " display(generation_process_tab)\n", + " else:\n", + " selecting_and_generating_temp_images(_)\n", + "\n", + "file_chooser.register_callback(callback=display_the_generation_process_section)\n", + "\n", + "def clear_the_generation_process_section(_button):\n", + " generation_process_tab.close()\n", + " actions_vbox.close()\n", + " clear_uploader()\n", + "\n", + "generation_process_cancel_btn.on_click(clear_the_generation_process_section)\n", + "\n", + "def is_image(file_extension):\n", + " image_extension = [\".jpeg\",\".jpg\",\".png\",\".svg\",\".tif\"]\n", + " return file_extension in image_extension\n", + "\n", + "def is_video(file_extension):\n", + " video_extension = [\".mp4\",\".avi\",\".flv\",\".3gpp\",\".mkv\",\".mks\", \".mov\"]\n", + " return file_extension in video_extension\n", + "\n", + "def is_slm(file_extension):\n", + " return file_extension == \".lsm\"\n", + "\n", + "@selection_box.capture()\n", + "def selecting_and_generating_temp_images(_button):\n", + " it_was_valid = True\n", + "\n", + " if is_image(file_extension):\n", + " image_extractor(selected_file, file_name)\n", + " elif is_video(file_extension):\n", + " video_frame_extractor(selected_file, file_name)\n", + " elif is_slm(file_extension):\n", + " lsm_frame_extractor(selected_file, file_name)\n", + " else:\n", + " it_was_valid = False\n", + " clear_the_generation_process_section(_)\n", + " print(f\"Error: The selected file does not have a valid extension {file_extension}\")\n", + "\n", + " if it_was_valid:\n", + " generation_process_tab.close()\n", + " display_the_pre_crop_controls_section()\n", + "\n", + "generation_process_confirm_btn.on_click(selecting_and_generating_temp_images)\n", + "\n", + "def save_temp_file(frame, selected_file_name, temp_folder):\n", + " global last_file_number\n", + " full_file_name = f\"{selected_file_name}_{last_file_number}.jpeg\"\n", + " generated_path = os.path.join(temp_folder, full_file_name)\n", + " rows, columns, = frame.shape\n", + " max_size = max(rows, columns)\n", + " square_matrix = np.zeros((max_size, max_size), dtype=np.uint8)\n", + " square_matrix[:rows, :columns] = frame\n", + " cv2.imwrite(generated_path, square_matrix)\n", + " last_file_number += 1\n", + "\n", + "def image_extractor(image_file, selected_file_name):\n", + " save_temp_file(cv2.cvtColor(cv2.imread(image_file), cv2.COLOR_BGR2GRAY), selected_file_name, voila_folder)\n", + "\n", + "def video_frame_extractor(video_file, selected_file_name):\n", + " capture = cv2.VideoCapture(video_file)\n", + " success, frame = capture.read()\n", + "\n", + " while success:\n", + " grayscale_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n", + " save_temp_file(grayscale_frame, selected_file_name, voila_folder)\n", + " success, frame = capture.read()\n", + "\n", + " capture.release()\n", + "\n", + "def lsm_frame_extractor(lsm_file, selected_file_name):\n", + " lsm_data = tifffile.imread(lsm_file)\n", + "\n", + " for _, frame in enumerate(lsm_data):\n", + " frame_normalized = frame / np.max(frame)\n", + " frame_uint8 = (frame_normalized * 255).astype(np.uint8)\n", + " save_temp_file(frame_uint8, selected_file_name, voila_folder)\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "def on_uploader_changed(_uploader):\n", - " \"\"\"\n", - " Override uploader._counter to \n", - " always be in sync with actual uploaded values. \n", - " \n", - " This is most probably a widgets bug\n", - " \"\"\"\n", - " uploader._counter = len(uploader.value)\n", - " \n", - "uploader.observe(on_uploader_changed, 'value')\n", "\n", "ksize = widgets.IntText(\n", " value=0,\n", @@ -529,17 +690,7 @@ " disabled=False\n", ")\n", "\n", - "html_text_upload = \"Upload your image to initialize the analysis
\"\n", - "header_text_upload = widgets.HTML(value=html_text_upload)\n", - "\n", - "box_upload = Layout(display='flex',\n", - " flex_flow='flex-wrap',\n", - " width='50%',\n", - " margin='0px 10px 5px 20px',\n", - " padding='2px 4% 0 1%',\n", - " justify_contentPeaksError='space-between')\n", "\n", - "upload_vbox = widgets.VBox([header_text_upload, uploader], layout=box_upload)\n", "\n", "box_layout = Layout(display='flex',\n", " flex_flow='column',\n", @@ -549,24 +700,6 @@ " padding=' 2px 5px 0 5px',\n", " justify_content='space-between')\n", "\n", - "btn_layout = Layout(display='flex',\n", - " flex_flow='row',\n", - " align_items='stretch',\n", - " width='50%',\n", - " margin='0px 10px 5px 20px',\n", - " padding='2px 4% 0 1%',\n", - " justify_content='flex-start')\n", - "\n", - "\n", - "start_btn = widgets.Button(description=\"Start\", button_style='primary', icon = 'fa-play')\n", - "clear_btn = widgets.Button(description=\"Clear\", button_style='danger', icon = 'fa-trash')\n", - "\n", - "clear_btn.style.button_color = 'salmon'\n", - "actions_vbox = widgets.VBox([clear_btn, start_btn], layout=btn_layout)\n", - "\n", - "crop_box = widgets.Output()\n", - "analysis_box = widgets.Output()\n", - "\n", "@crop_box.capture()\n", "def on_start_button_clicked(_button):\n", " \"\"\"\n", @@ -576,28 +709,68 @@ " \"\"\"\n", " global __last_crop_results__\n", " __last_crop_results__ = {}\n", - " contents = get_uploader_contents(uploader)\n", + " contents = get_uploader_contents()\n", + " names = uploader['value'].keys()\n", " clear_job_boxes()\n", - " \n", + "\n", " @analysis_box.capture()\n", " def callback(image_name, shape):\n", - " __last_crop_results__.update({image_name: shape.size})\n", " if len(contents) == len(__last_crop_results__):\n", " analysis_box.clear_output()\n", " render_results_box(__last_crop_results__)\n", - " \n", - " if uploader._counter == 0:\n", - " print(\"Please upload an image first\")\n", + "\n", + " if last_file_number == 0:\n", + " print(\"Error: To start the analysis phase, select a file first\")\n", + " elif is_video(file_extension) or is_slm(file_extension):\n", + " def upload_images_to_memory(base_file_name, temp_folder, format_file):\n", + " global last_updated_number\n", + " limit = last_updated_number + 10\n", + " generated_names = tuple()\n", + " while (last_file_number != last_updated_number) and (limit != last_updated_number) :\n", + " full_file_name = f\"{base_file_name}_{last_updated_number}\"\n", + " generated_path = os.path.join(temp_folder, full_file_name + format_file)\n", + " with open(generated_path, 'rb') as image_file:\n", + " uploader['value'][full_file_name] = image_file.read()\n", + " generated_names = generated_names + (full_file_name,)\n", + " last_updated_number += 1\n", + "\n", + " return generated_names\n", + "\n", + " def change_selection(change):\n", + " selected_value = change['new']\n", + " image_preview.value = uploader['value'][selected_value]\n", + "\n", + " def more_items_dropdown(_button):\n", + " new_values = upload_images_to_memory(file_name, voila_folder, extension_to_generate)\n", + " dropdown_images.options = dropdown_images.options + new_values\n", + " dropdown_images.value = new_values[0]\n", + " image_preview.value = uploader['value'][dropdown_images.value]\n", + "\n", + " image_preview = widgets.Image()\n", + " dropdown_images = widgets.Dropdown()\n", + " more_items_dropdown(_)\n", + " select_btn = widgets.Button(description=\"Select\", button_style='primary', icon = 'fa-check')\n", + " more_image_btn = widgets.Button(description=\"More Images\", button_style='primary', icon = 'fa-plus', style={'button_color': 'forestgreen'})\n", + " dropdown_vbox = widgets.VBox([dropdown_images, image_preview], layout=box_upload_layout)\n", + " dropdown_buttons = widgets.VBox([select_btn, more_image_btn], layout=btn_layout)\n", + " display(dropdown_vbox)\n", + " display(dropdown_buttons)\n", + "\n", + " more_image_btn.on_click(more_items_dropdown)\n", + " dropdown_images.observe(change_selection, names='value')\n", + "\n", " else:\n", - " images_buffers = [np.array(Image.open(io.BytesIO(buffer))) for buffer in contents] \n", - " display(crop(images_buffers, image_name_list = get_uploader_filenames(uploader), continuous_update = False, optimize = False, callback = callback))\n", - " \n", - "start_btn.on_click(on_start_button_clicked)\n", - " \n", + " full_file_name = f\"{file_name}\"\n", + " generated_path = os.path.join(voila_folder, full_file_name + extension_to_generate)\n", + " with open(generated_path, 'rb') as image_file:\n", + " uploader['value'][full_file_name] = image_file.read()\n", + "\n", "def on_clear_button_clicked(_button):\n", - " clear_uploader(uploader)\n", + " actions_vbox.close()\n", + " clear_uploader()\n", " clear_job_boxes()\n", "\n", + "start_btn.on_click(on_start_button_clicked)\n", "clear_btn.on_click(on_clear_button_clicked)" ] }, @@ -722,8 +895,7 @@ " athors_section_title, \n", " athors_section_description, \n", " citations_section_title, \n", - " citations_section_description, Runing_section], layout=layouts_intro)\n", - "\n" + " citations_section_description, Runing_section], layout=layouts_intro)" ] }, { @@ -822,7 +994,7 @@ "display(nav_bar_vbox)\n", "display(main_section_vbox)\n", "display(upload_vbox)\n", - "display(actions_vbox)\n", + "display(selection_box)\n", "display(crop_box)\n", "display(analysis_box)" ] diff --git a/requirements.txt b/requirements.txt index f4a41bd..998b52e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,5 @@ ipython==7.21.0 seaborn==0.12.2 matplotlib==3.6.3 interactivecrop==0.0.10 +tifffile==2023.7.10 +ipyfilechooser==0.6.0 \ No newline at end of file