diff --git a/, b/, deleted file mode 100644 index 78a3ea1..0000000 --- a/, +++ /dev/null @@ -1,30 +0,0 @@ - - SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS - - Commands marked with * may be preceded by a number, _N. - Notes in parentheses indicate the behavior if _N is given. - A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. - - h H Display this help. - q :q Q :Q ZZ Exit. - --------------------------------------------------------------------------- - - MMOOVVIINNGG - - e ^E j ^N CR * Forward one line (or _N lines). - y ^Y k ^K ^P * Backward one line (or _N lines). - f ^F ^V SPACE * Forward one window (or _N lines). - b ^B ESC-v * Backward one window (or _N lines). - z * Forward one window (and set window to _N). - w * Backward one window (and set window to _N). - ESC-SPACE * Forward one window, but don't stop at end-of-file. - d ^D * Forward one half-window (and set half-window to _N). - u ^U * Backward one half-window (and set half-window to _N). - ESC-) RightArrow * Right one half screen width (or _N positions). - ESC-( LeftArrow * Left one half screen width (or _N positions). - ESC-} ^RightArrow Right to last column displayed. - ESC-{ ^LeftArrow Left to first column. - F Forward forever; like "tail -f". - ESC-F Like F but stop when search pattern is found. - r ^R ^L Repaint screen. - R Repaint screen, discarding buffered input. diff --git a/.gitignore b/.gitignore index 8c9cb8b..ddb8ab2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ Include/ Lib/ -Scripts/ pyvenv.cfg -wsgi.py +algae_env/ diff --git a/Aptfile b/Aptfile deleted file mode 100644 index 6f0ea40..0000000 --- a/Aptfile +++ /dev/null @@ -1,4 +0,0 @@ -libsm6 -libxrender1 -libfontconfig1 -libice6 \ No newline at end of file diff --git a/Procfile b/Procfile deleted file mode 100644 index 0886b0f..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: gunicorn wsgi:app --preload diff --git a/README.md b/README.md index 74036e1..fd7123c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,20 @@ # Algaeorithm -A web application that allows users to upload images or image urls of Chlamydomonas algae cells and returns the number of cells in the image as well as outlines or circles indicating the cells the algorithm has counted. -The application uses vanilla JavaScript for the frontend and the Python framework Flask for the backend, and it is deployed to Heroku at https://algaeorithm.herokuapp.com/ -Watch our demo video here. +## Project Overview +Algaeorithm is a web-based machine learning application now used in academic settings to analyze images of algae cells for bioenergy education and research. It is funded by the U.S. Department of Energy and Silicon Valley Clean Energy. The application uses vanilla JavaScript for the frontend and the Python framework Flask for the backend, and it is deployed through AWS here. The image analysis algorithm uses a TensorFlow convolutional neural network and a manual implementation of the YOLOv1 algorithm with Jax and Flax. + +## Accolades + + + +## Media + diff --git a/__pycache__/__init__.cpython-39.pyc b/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..5752937 Binary files /dev/null and b/__pycache__/__init__.cpython-39.pyc differ diff --git a/__pycache__/app.cpython-310.pyc b/__pycache__/app.cpython-310.pyc new file mode 100644 index 0000000..927949e Binary files /dev/null and b/__pycache__/app.cpython-310.pyc differ diff --git a/__pycache__/app.cpython-39.pyc b/__pycache__/app.cpython-39.pyc new file mode 100644 index 0000000..7a21c33 Binary files /dev/null and b/__pycache__/app.cpython-39.pyc differ diff --git a/app/__pycache__/app.cpython-310.pyc b/app/__pycache__/app.cpython-310.pyc new file mode 100644 index 0000000..927949e Binary files /dev/null and b/app/__pycache__/app.cpython-310.pyc differ diff --git a/app/__pycache__/app.cpython-39.pyc b/app/__pycache__/app.cpython-39.pyc index 11afe17..ff59ecd 100644 Binary files a/app/__pycache__/app.cpython-39.pyc and b/app/__pycache__/app.cpython-39.pyc differ diff --git a/app/app.py b/app/app.py index 51c6f63..056bb3e 100644 --- a/app/app.py +++ b/app/app.py @@ -1,111 +1,217 @@ from io import BytesIO -from skimage import io +import boto3 import matplotlib.pyplot as plt -from matplotlib.patches import Circle import numpy as np -from flask import Flask, render_template, request, redirect, send_file -from skimage.filters import threshold_otsu, threshold_local +from flask import Flask, render_template, request, send_from_directory, send_file from PIL import Image import os import numpy as np -from skimage.color import rgb2gray -from skimage.feature import peak_local_max -from skimage.segmentation import watershed -from scipy import ndimage, stats, optimize +from scipy import optimize, stats import cv2 import json import base64 import math +import os +import tensorflow as tf +from models.object_detection.utils import label_map_util +from models.object_detection.utils import visualization_utils as viz_utils +from models.object_detection.builders import model_builder +from models.object_detection.utils import config_util +import random app = Flask(__name__) +# Load pipeline config and build a detection model +cell_configs = config_util.get_configs_from_pipeline_file(os.path.join("app", "static", "cells", "pipeline.config")) +cell_detection_model = model_builder.build(model_config=cell_configs['model'], is_training=False) + +# Restore checkpoint +cell_ckpt = tf.compat.v2.train.Checkpoint(model=cell_detection_model) +cell_ckpt.restore(os.path.join("app", "static", "cells", 'ckpt-3')).expect_partial() + +category_index = label_map_util.create_category_index_from_labelmap(os.path.join("app", "static", "cells", "label_map.pbtxt")) + +def cells_fn(image): + image, shapes = cell_detection_model.preprocess(image) + prediction_dict = cell_detection_model.predict(image, shapes) + detections = cell_detection_model.postprocess(prediction_dict, shapes) + return detections + +# Load pipeline config and build a detection model +chlorella_configs = config_util.get_configs_from_pipeline_file(os.path.join("app", "static", "chlorella", "pipeline.config")) +chlorella_detection_model = model_builder.build(model_config=cell_configs['model'], is_training=False) + +# Restore checkpoint +chlorella_ckpt = tf.compat.v2.train.Checkpoint(model=cell_detection_model) +chlorella_ckpt.restore(os.path.join("app", "static", "chlorella", 'ckpt-3')).expect_partial() + +category_index = label_map_util.create_category_index_from_labelmap(os.path.join("app", "static", "chlorella", "label_map.pbtxt")) + +def chlorella_fn(image): + image, shapes = cell_detection_model.preprocess(image) + prediction_dict = cell_detection_model.predict(image, shapes) + detections = cell_detection_model.postprocess(prediction_dict, shapes) + return detections + +#Load pipeline config and build a detection model +diatom_configs = config_util.get_configs_from_pipeline_file(os.path.join("app", "static", "diatom", "pipeline.config")) +diatom_detection_model = model_builder.build(model_config=diatom_configs['model'], is_training=False) + +# Restore checkpoint +diatom_ckpt = tf.compat.v2.train.Checkpoint(model=diatom_detection_model) +diatom_ckpt.restore(os.path.join("app", "static", "diatom", 'ckpt-3')).expect_partial() + +category_index = label_map_util.create_category_index_from_labelmap(os.path.join("app", "static", "cells", "label_map.pbtxt")) + +def diatom_fn(image): + image, shapes = diatom_detection_model.preprocess(image) + prediction_dict = diatom_detection_model.predict(image, shapes) + detections = diatom_detection_model.postprocess(prediction_dict, shapes) + return detections + +def cells_fn(image): + image, shapes = cell_detection_model.preprocess(image) + prediction_dict = cell_detection_model.predict(image, shapes) + detections = cell_detection_model.postprocess(prediction_dict, shapes) + return detections + +# Load pipeline config and build a detection model +crop_configs = config_util.get_configs_from_pipeline_file(os.path.join("app", "static", "crop", "pipeline.config")) +crop_detection_model = model_builder.build(model_config=crop_configs['model'], is_training=False) + +# Restore checkpoint +crop_ckpt = tf.compat.v2.train.Checkpoint(model=crop_detection_model) +crop_ckpt.restore(os.path.join("app", "static", "crop", 'ckpt-3')).expect_partial() + +def crop_fn(image): + image, shapes = crop_detection_model.preprocess(image) + prediction_dict = crop_detection_model.predict(image, shapes) + detections = crop_detection_model.postprocess(prediction_dict, shapes) + return detections + +def auto_crop(image_data, threshold = 0.8): + image_np = np.array(image_data) + + input_tensor = tf.convert_to_tensor(np.expand_dims(image_np, 0), dtype=tf.float32) + detections = crop_fn(input_tensor) + + num_detections = int(detections.pop('num_detections')) + detections = {key: value[0, :num_detections].numpy() for key, value in detections.items()} + detections['num_detections'] = num_detections + + if detections["detection_scores"][0] < threshold: + return cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB) + + height = image_np.shape[0] + width = image_np.shape[1] + crop_box = detections["detection_boxes"][0] + + return cv2.cvtColor(image_np[int(crop_box[0]*height):int(crop_box[2]*height), int(crop_box[1]*width):int(crop_box[3]*width), :], cv2.COLOR_BGR2RGB) + +def chlamy_concentration(cell_boxes, total_area, cell_volume=271.8, depth=0.1): + avg_radius = np.mean(list(map(lambda box: (abs((box[3] - box[1]) / 2) + abs((box[2] - box[0]) / 2)) / 2, cell_boxes))) + expected_radius = (cell_volume * 3 / 4 / math.pi) ** (1/3) + return len(cell_boxes) / (((expected_radius ** 2) / (avg_radius ** 2) * total_area) * 10 ** -9 * depth) -def threshold_image(image, clear_background=True, block_size=35): - if not image.any(): - return - if len(image.shape) > 2: - image = image[:, :, 2] - if not clear_background: - thresh = threshold_local(image, block_size) +def diatom_concentration(cell_boxes, image_area, cell_length, mm_depth): + avg_length = np.mean(list(map(lambda x: ((x[2] - x[0]) ** 2 + (x[3] - x[1]) ** 2) ** 0.5, cell_boxes))) + return len(cell_boxes) / (((cell_length / avg_length) ** 2 * image_area) * 10 ** -9 * mm_depth + 1e-9) + +def intersection_over_union(box1, box2): + box1_x1 = box1[1] + box1_y1 = box1[0] + box1_x2 = box1[3] + box1_y2 = box1[2] + box2_x1 = box2[1] + box2_y1 = box2[0] + box2_x2 = box2[3] + box2_y2 = box2[2] + x1 = np.max(np.array([box1_x1, box2_x1])) + y1 = np.max(np.array([box1_y1, box2_y1])) + x2 = np.min(np.array([box1_x2, box2_x2])) + y2 = np.min(np.array([box1_y2, box2_y2])) + intersection = np.max(np.array([x2 - x1, 0])) * np.max(np.array([y2 - y1, 0])) + box1_area = abs((box1_x2 - box1_x1) * (box1_y2 - box1_y1)) + box2_area = abs((box2_x2 - box2_x1) * (box2_y2 - box2_y1)) + return intersection / (box1_area + box2_area - intersection + 1e-6) + +def calc_box_area(box): + return abs((box[3]-box[1])*(box[2]-box[0])) + +def suppress_boxes(detections, confidence_threshold=0.1, iou_threshold=0.5): + num_detections = int(detections.pop('num_detections')) + detections = {key: value[0, :num_detections].numpy() for key, value in detections.items()} + detections['num_detections'] = num_detections + # detection_classes should be ints. + detections['detection_classes'] = detections['detection_classes'].astype(np.int64) + + + final_boxes = enumerate(detections["detection_boxes"]) + + final_boxes = sorted([bbox for bbox in final_boxes if detections["detection_scores"][bbox[0]] > confidence_threshold and float(calc_box_area(bbox[1])) < 0.01], key=lambda bbox: detections["detection_scores"][bbox[0]], reverse=True) + if len(final_boxes) < 2: + final_boxes = list(enumerate(detections["detection_boxes"])) + remaining_bboxes = [] + while final_boxes: + top_bbox = final_boxes.pop(0) + final_boxes = [bbox for bbox in final_boxes if intersection_over_union(top_bbox[1], bbox[1]) < iou_threshold] + remaining_bboxes.append(top_bbox[1]) + print(remaining_bboxes) + box_areas = list(map(lambda x: (x[3] - x[1]) * (x[2] - x[0]), remaining_bboxes)) + adjusted_bboxes = [bbox for idx, bbox in enumerate(remaining_bboxes) if abs(stats.zscore(box_areas)[idx]) < 3] + return adjusted_bboxes + +def count_concentration_detections(image, cell_type, image_name, threshold=0.1, cell_volume=271.8, cell_length=16, depth=0.1): + if cell_type != 'chlorella': + cropped_image = auto_crop(image) + img_height = cropped_image.shape[0] + img_width = cropped_image.shape[1] + patch_size = round(np.mean(np.array([img_height, img_width])) * 0.4) + total_area = patch_size ** 2 + patch_results = {} + width_offset = max(round((img_width - patch_size) / 2), 0) + height_offset = max(round((img_height - patch_size) / 2), 0) + img_patch = cropped_image[height_offset:min(img_height, height_offset + patch_size), width_offset:min(img_width, width_offset + patch_size), :] else: - thresh = threshold_otsu(image) - if np.mean(image) > threshold_otsu(image): - binary = image < thresh + img_patch = cv2.cvtColor(np.array(image), cv2.COLOR_BGR2RGB) + patch_results = {} + img_height = img_patch.shape[0] + img_width = img_patch.shape[1] + total_area = img_height*img_width + + + #image_to_upload = np.array(cv2.cvtColor(img_patch, cv2.COLOR_BGR2RGB)) + #image_to_upload = Image.fromarray(image_to_upload.astype('uint8')) + #in_mem_file = BytesIO() + #image_to_upload.save(in_mem_file, format="jpeg") + #in_mem_file.seek(0) + #client = boto3.client("s3") + #client.put_object(Body=in_mem_file, Bucket="algaeorithm-photos", Key=image_name) + input_tensor = tf.convert_to_tensor(np.expand_dims(img_patch, 0), dtype=tf.float32) + if cell_type=="chlamy": + detections = cells_fn(input_tensor) + elif cell_type!="chlorella": + detections = diatom_fn(input_tensor) else: - binary = image < thresh - return binary.astype(np.uint8) * 255 - - -def image_to_contours_list(image, clear_background=True, block_size=35, min_ratio=0.1, max_ratio=3, min_distance=10): - gray_shape = rgb2gray(image).shape - # Threshold the image, and fill holes in contours - thresh = ndimage.morphology.binary_fill_holes( - threshold_image(image, clear_background, block_size)) - # Find peaks and convert to labels - D = ndimage.distance_transform_edt(thresh) - localMax = peak_local_max(D, indices=False, min_distance=min_distance, labels=thresh) - markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0] - labels = watershed(-D, markers, mask=thresh) - all_contours = [] - # Find contours in labels, and add largest contour to list of contours - for label in np.unique(labels): - if label == 0: - continue - mask = np.zeros(gray_shape, dtype="uint8") - mask[labels == label] = 255 - cnts, hierarchy = cv2.findContours( - mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - c = max(cnts, key=cv2.contourArea) - all_contours.append(c) - if not len(all_contours): - return [] - # Remove outliers in contour area - contour_areas = list(map(cv2.contourArea, all_contours)) - if len(all_contours) > 1: - all_contours = [contour for idx, contour in enumerate(all_contours) if abs(stats.zscore(contour_areas)[idx]) < 5] - contour_areas = list(map(cv2.contourArea, all_contours)) - return [contour for contour in all_contours if min_ratio * np.mean(contour_areas) < cv2.contourArea(contour)] - - -def remove_cells_within_cells(contours): - i1 = 0 - coords = [] - # Remove outlines that are swallowed up by other outlines - while i1 < len(contours): - c1 = contours[i1] - ((x1, y1), r1) = cv2.minEnclosingCircle(c1) - coords.append(cv2.minEnclosingCircle(c1)) - i2 = 0 - while i2 < len(contours): - c2 = contours[i2] - if not np.array_equal(c1, c2): - ((x2, y2), r2) = cv2.minEnclosingCircle(c2) - if ((((x2 - x1)**2) + ((y2-y1)**2))**0.5) < r1: - contours.pop(i2) - else: - i2 += 1 - else: - i2 += 1 - i1 += 1 - return contours, coords - - -def count_cells(image, clear_background=True, block_size=35, min_ratio=0.1, max_ratio=3, min_distance=10, return_outlines=False): - if not image.any(): - return -1 - # Calculate area once to get an idea of the area of each cell in pixels, then calculate again with new minimum distance which takes into account the new areas - all_contours = image_to_contours_list(image, clear_background=clear_background, block_size=block_size, - min_ratio=min_ratio, max_ratio=max_ratio, min_distance=min_distance) - if not all_contours: - return [], [] - contour_areas = list(map(cv2.contourArea, all_contours)) - new_min_distance = round(math.sqrt(np.mean(contour_areas)) / 3) - all_contours = image_to_contours_list(image, clear_background=clear_background, block_size=block_size, - min_ratio=min_ratio, max_ratio=max_ratio, min_distance=new_min_distance) - all_contours = sorted(all_contours, key=cv2.contourArea, reverse=True) - all_contours, coords = remove_cells_within_cells(all_contours) - return all_contours, coords + detections = chlorella_fn(input_tensor) + patch_width = img_patch.shape[1] + patch_height = img_patch.shape[0] + all_boxes = suppress_boxes(detections, threshold) + + scaled_boxes = list(map(lambda x: [x[0] * patch_height, x[1] * patch_width, x[2]*patch_height, x[3] * patch_width], all_boxes)) + patch_results["patch"] = img_patch + patch_results["detections"] = np.array(all_boxes) + estimated_cell_count = len(scaled_boxes) * img_height * img_width / total_area + concentration = chlamy_concentration(scaled_boxes, total_area, cell_volume, depth) if cell_type=="chlamy" else diatom_concentration(scaled_boxes, total_area, cell_length, depth) if cell_type != "chlorella" else chlamy_concentration(scaled_boxes, total_area, cell_volume/2, depth) + return estimated_cell_count, concentration, patch_results + +def annotate_patch(patch_results): + patch, detections = patch_results["patch"], patch_results["detections"] + patch_with_detections = patch.copy() + if detections.shape[0] > 0: + viz_utils.draw_bounding_boxes_on_image_array(patch_with_detections, detections) + return image_array_to_base64(np.array(cv2.cvtColor(patch_with_detections, cv2.COLOR_BGR2RGB))) def get_img_from_fig(fig, tight=True, dpi=180): buf = BytesIO() @@ -120,29 +226,6 @@ def get_img_from_fig(fig, tight=True, dpi=180): img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) return img - -def annotate_image(image, outlines, circles=False): - if not outlines: - return image_array_to_base64(image) - if circles: - fig = plt.figure() - ax = plt.axes(frameon=False) - ax.axis("off") - ax.get_xaxis().tick_bottom() - ax.axes.get_yaxis().set_visible(False) - ax.axes.get_xaxis().set_visible(False) - ax.set_aspect('equal') - ax.imshow(image) - for ((x1, y1), r1) in outlines[1]: - circ = Circle((x1, y1), r1) - ax.add_patch(circ) - fig.add_axes(ax) - return image_array_to_base64(get_img_from_fig(fig)) - else: - cv2.drawContours(image, outlines[0], -1, (255, 0, 0), 2) - return image_array_to_base64(image) - - def image_array_to_base64(arr): img = Image.fromarray(arr.astype('uint8')) file_object = BytesIO() @@ -152,14 +235,14 @@ def image_array_to_base64(arr): img.save(file_object, format="PNG") img_str = base64.b64encode(file_object.getvalue()) file_object.close() - return img_str.decode("utf-8") + return "data:image/jpeg;base64,"+img_str.decode("utf-8") def calculate_concentration(image, contours, cell_volume, mm_depth): avg_area = np.mean(list(map(cv2.contourArea, contours))) expected_area = (((cell_volume * 3 / 4 / math.pi) ** (1/3)) ** 2) * math.pi return len(contours) / ((expected_area / avg_area * image.shape[0] * image.shape[1]) * 10 ** -8 * mm_depth / 10) -def load_response(key, filename, filedata, counts, concentrations, csv_rows): +def load_response(key, filename, filedata, counts, concentrations, csv_rows, image_type): final_data[key][filename] = {} row_to_append = [filename, "N/A", "N/A"] depth = request.form.get("depth") if request.form.get("depth") else request.form.get("depth-"+filename) @@ -171,42 +254,49 @@ def load_response(key, filename, filedata, counts, concentrations, csv_rows): row_to_append.insert(1, "N/A") if filename==filedata: try: - img = np.asarray(io.imread(filename)) + img = np.asarray(cv2.imread(filename)) except: final_data[key][filename]["count"] = "N/A" final_data[key][filename]["concentration"] = "N/A" - csv_rows.append(row_to_append) + csv_rows[filename] = row_to_append return 0 else: try: img = np.asarray(Image.open(BytesIO(filedata.read()))) except: + print("error reading file") final_data[key][filename]["count"] = "N/A" final_data[key][filename]["concentration"] = "N/A" - csv_rows.append(row_to_append) + csv_rows[filename] = row_to_append return 0 - try: - cell_results = count_cells(img) - concentration = calculate_concentration(img, cell_results[0], 271.8, float(depth)) - except: - final_data[key][filename]["count"] = "N/A" - final_data[key][filename]["concentration"] = "N/A" - csv_rows.append(row_to_append) + #try: + if image_type=="chlamy": + threshold=0.5 + elif image_type=="chlorella": + threshold = 0.2 + else: + threshold = 0.1 + estimated_count, concentration, patch_results = count_concentration_detections(img, image_type, filename, threshold) + #except: + # final_data[key][filename]["count"] = "N/A" + # final_data[key][filename]["concentration"] = "N/A" + # final_data[key][filename]["image"] = image_array_to_base64(img) + # csv_rows[filename] = row_to_append + # return 0 if request.form.get("time-unit"): if request.form.get("time-"+filename): time_x.append(float(request.form.get("time-"+filename))) - counts_y.append(len(cell_results[0])) + counts_y.append(estimated_count) concentrations_y.append(concentration) - row_to_append[-2] = str(len(cell_results[0])) + row_to_append[-2] = str(round(estimated_count, 2)) row_to_append[-1] = str(round(concentration, 2)) csv_rows[filename] = row_to_append final_data[key][filename] = {} - final_data[key][filename]["count"] = "{}".format(len(cell_results[0])) + final_data[key][filename]["count"] = "{:.2e}".format(estimated_count) final_data[key][filename]["concentration"] = "{:.2e}".format(concentration) final_data[key][filename]["image"] = image_array_to_base64(img) - final_data[key][filename]["outlines"] = annotate_image(img, cell_results) - final_data[key][filename]["circles"] = annotate_image(img, cell_results, True) - counts.append(len(cell_results[0])) + final_data[key][filename]["output"] = annotate_patch(patch_results) + counts.append(estimated_count) concentrations.append(concentration) def boxplot(data, metric): @@ -319,7 +409,28 @@ def load_graphs(counts, concentrations): final_data["graphs"][metric]["Logistic Growth"] = is_logistic @app.route('/') def index_get(): - return render_template("index.html") + return render_template("bad_index.html") + +@app.route("/health") +def health_check(): + return "200 OK" + +#@app.route("/content") +#def content(): +# return render_template("content.html") + +@app.route("/press") +def press(): + return render_template("press.html") + +@app.route("/about") +def about(): + return render_template("about.html") + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static', 'logos'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') @app.route('/', methods=["POST"]) @@ -337,10 +448,11 @@ def index_post(): counts_y = [] global concentrations_y concentrations_y = [] + image_type = request.form.get("cell_type") for filename, file in request.files.items(): - load_response("file_counts", filename, file, counts, concentrations, csv_rows) + load_response("file_counts", filename, file, counts, concentrations, csv_rows, image_type) for image_url in json.loads(request.form.get("url")): - load_response("url_counts", image_url, image_url, counts, concentrations, csv_rows) + load_response("url_counts", image_url, image_url, counts, concentrations, csv_rows, image_type) csv_string = "" for row in list(csv_rows.values()): csv_string += ",".join(row) + "\r\n" @@ -352,3 +464,7 @@ def index_post(): else: final_data["stats"] = "No data available" return final_data + +@app.route("/synopsis") +def return_synopsis(): + return send_file(os.path.join("static", "files", 'synopsis.pdf'), attachment_filename='Synopsis.pdf') diff --git a/app/static/cells/checkpoint b/app/static/cells/checkpoint new file mode 100644 index 0000000..4a94b30 --- /dev/null +++ b/app/static/cells/checkpoint @@ -0,0 +1,8 @@ +model_checkpoint_path: "ckpt-3" +all_model_checkpoint_paths: "ckpt-1" +all_model_checkpoint_paths: "ckpt-2" +all_model_checkpoint_paths: "ckpt-3" +all_model_checkpoint_timestamps: 1642645527.8543444 +all_model_checkpoint_timestamps: 1642645870.9808345 +all_model_checkpoint_timestamps: 1642646188.033501 +last_preserved_timestamp: 1642645526.1558022 diff --git a/app/static/cells/ckpt-3.data-00000-of-00001 b/app/static/cells/ckpt-3.data-00000-of-00001 new file mode 100644 index 0000000..85a60e9 Binary files /dev/null and b/app/static/cells/ckpt-3.data-00000-of-00001 differ diff --git a/app/static/cells/ckpt-3.index b/app/static/cells/ckpt-3.index new file mode 100644 index 0000000..b5a7a07 Binary files /dev/null and b/app/static/cells/ckpt-3.index differ diff --git a/app/static/cells/label_map.pbtxt b/app/static/cells/label_map.pbtxt new file mode 100644 index 0000000..d050273 --- /dev/null +++ b/app/static/cells/label_map.pbtxt @@ -0,0 +1,4 @@ +item { + name:'cell' + id:1 +} diff --git a/app/static/cells/pipeline.config b/app/static/cells/pipeline.config new file mode 100644 index 0000000..679857b --- /dev/null +++ b/app/static/cells/pipeline.config @@ -0,0 +1,191 @@ +model { + ssd { + num_classes: 1 + image_resizer { + fixed_shape_resizer { + height: 640 + width: 640 + } + } + feature_extractor { + type: "ssd_mobilenet_v2_fpn_keras" + depth_multiplier: 1.0 + min_depth: 16 + conv_hyperparams { + regularizer { + l2_regularizer { + weight: 4e-05 + } + } + initializer { + random_normal_initializer { + mean: 0.0 + stddev: 0.01 + } + } + activation: RELU_6 + batch_norm { + decay: 0.997 + scale: true + epsilon: 0.001 + } + } + use_depthwise: true + override_base_feature_extractor_hyperparams: true + fpn { + min_level: 3 + max_level: 7 + additional_layer_depth: 128 + } + } + box_coder { + faster_rcnn_box_coder { + y_scale: 10.0 + x_scale: 10.0 + height_scale: 5.0 + width_scale: 5.0 + } + } + matcher { + argmax_matcher { + matched_threshold: 0.5 + unmatched_threshold: 0.5 + ignore_thresholds: false + negatives_lower_than_unmatched: true + force_match_for_each_row: true + use_matmul_gather: true + } + } + similarity_calculator { + iou_similarity { + } + } + box_predictor { + weight_shared_convolutional_box_predictor { + conv_hyperparams { + regularizer { + l2_regularizer { + weight: 4e-05 + } + } + initializer { + random_normal_initializer { + mean: 0.0 + stddev: 0.01 + } + } + activation: RELU_6 + batch_norm { + decay: 0.997 + scale: true + epsilon: 0.001 + } + } + depth: 128 + num_layers_before_predictor: 4 + kernel_size: 3 + class_prediction_bias_init: -4.6 + share_prediction_tower: true + use_depthwise: true + } + } + anchor_generator { + multiscale_anchor_generator { + min_level: 3 + max_level: 7 + anchor_scale: 4.0 + aspect_ratios: 1.0 + aspect_ratios: 2.0 + aspect_ratios: 0.5 + scales_per_octave: 2 + } + } + post_processing { + batch_non_max_suppression { + score_threshold: 1e-08 + iou_threshold: 0.6 + max_detections_per_class: 100 + max_total_detections: 100 + use_static_shapes: false + } + score_converter: SIGMOID + } + normalize_loss_by_num_matches: true + loss { + localization_loss { + weighted_smooth_l1 { + } + } + classification_loss { + weighted_sigmoid_focal { + gamma: 2.0 + alpha: 0.25 + } + } + classification_weight: 1.0 + localization_weight: 1.0 + } + encode_background_as_zeros: true + normalize_loc_loss_by_codesize: true + inplace_batchnorm_update: true + freeze_batchnorm: false + } +} +train_config { + batch_size: 4 + data_augmentation_options { + random_horizontal_flip { + } + } + data_augmentation_options { + random_crop_image { + min_object_covered: 0.0 + min_aspect_ratio: 0.75 + max_aspect_ratio: 3.0 + min_area: 0.75 + max_area: 1.0 + overlap_thresh: 0.0 + } + } + sync_replicas: true + optimizer { + momentum_optimizer { + learning_rate { + cosine_decay_learning_rate { + learning_rate_base: 0.08 + total_steps: 50000 + warmup_learning_rate: 0.026666 + warmup_steps: 1000 + } + } + momentum_optimizer_value: 0.9 + } + use_moving_average: false + } + fine_tune_checkpoint: "drive/MyDrive/PilotTFODCourse/Tensorflow/workspace/pre-trained-models/ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8/checkpoint/ckpt-0" + num_steps: 50000 + startup_delay_steps: 0.0 + replicas_to_aggregate: 8 + max_number_of_boxes: 100 + unpad_groundtruth_tensors: false + fine_tune_checkpoint_type: "detection" + fine_tune_checkpoint_version: V2 +} +train_input_reader { + label_map_path: "drive/MyDrive/PilotTFODCourse/Tensorflow/workspace/annotations/label_map.pbtxt" + tf_record_input_reader { + input_path: "drive/MyDrive/PilotTFODCourse/Tensorflow/workspace/annotations/train.record" + } +} +eval_config { + metrics_set: "coco_detection_metrics" + use_moving_averages: false +} +eval_input_reader { + label_map_path: "drive/MyDrive/PilotTFODCourse/Tensorflow/workspace/annotations/label_map.pbtxt" + shuffle: false + num_epochs: 1 + tf_record_input_reader { + input_path: "drive/MyDrive/PilotTFODCourse/Tensorflow/workspace/annotations/test.record" + } +} diff --git a/app/static/chlorella/checkpoint b/app/static/chlorella/checkpoint new file mode 100644 index 0000000..d2a6d99 --- /dev/null +++ b/app/static/chlorella/checkpoint @@ -0,0 +1,8 @@ +model_checkpoint_path: "ckpt-3" +all_model_checkpoint_paths: "ckpt-1" +all_model_checkpoint_paths: "ckpt-2" +all_model_checkpoint_paths: "ckpt-3" +all_model_checkpoint_timestamps: 1694381974.5197287 +all_model_checkpoint_timestamps: 1694382154.5008404 +all_model_checkpoint_timestamps: 1694382301.5258217 +last_preserved_timestamp: 1694381972.7760105 diff --git a/app/static/chlorella/ckpt-3.data-00000-of-00001 b/app/static/chlorella/ckpt-3.data-00000-of-00001 new file mode 100644 index 0000000..412ba0b Binary files /dev/null and b/app/static/chlorella/ckpt-3.data-00000-of-00001 differ diff --git a/app/static/chlorella/ckpt-3.index b/app/static/chlorella/ckpt-3.index new file mode 100644 index 0000000..cf57146 Binary files /dev/null and b/app/static/chlorella/ckpt-3.index differ diff --git a/app/static/chlorella/label_map.pbtxt b/app/static/chlorella/label_map.pbtxt new file mode 100644 index 0000000..d050273 --- /dev/null +++ b/app/static/chlorella/label_map.pbtxt @@ -0,0 +1,4 @@ +item { + name:'cell' + id:1 +} diff --git a/app/static/chlorella/pipeline.config b/app/static/chlorella/pipeline.config new file mode 100644 index 0000000..5f7fd6e --- /dev/null +++ b/app/static/chlorella/pipeline.config @@ -0,0 +1,191 @@ +model { + ssd { + num_classes: 1 + image_resizer { + fixed_shape_resizer { + height: 640 + width: 640 + } + } + feature_extractor { + type: "ssd_mobilenet_v2_fpn_keras" + depth_multiplier: 1.0 + min_depth: 16 + conv_hyperparams { + regularizer { + l2_regularizer { + weight: 4e-05 + } + } + initializer { + random_normal_initializer { + mean: 0.0 + stddev: 0.01 + } + } + activation: RELU_6 + batch_norm { + decay: 0.997 + scale: true + epsilon: 0.001 + } + } + use_depthwise: true + override_base_feature_extractor_hyperparams: true + fpn { + min_level: 3 + max_level: 7 + additional_layer_depth: 128 + } + } + box_coder { + faster_rcnn_box_coder { + y_scale: 10.0 + x_scale: 10.0 + height_scale: 5.0 + width_scale: 5.0 + } + } + matcher { + argmax_matcher { + matched_threshold: 0.5 + unmatched_threshold: 0.5 + ignore_thresholds: false + negatives_lower_than_unmatched: true + force_match_for_each_row: true + use_matmul_gather: true + } + } + similarity_calculator { + iou_similarity { + } + } + box_predictor { + weight_shared_convolutional_box_predictor { + conv_hyperparams { + regularizer { + l2_regularizer { + weight: 4e-05 + } + } + initializer { + random_normal_initializer { + mean: 0.0 + stddev: 0.01 + } + } + activation: RELU_6 + batch_norm { + decay: 0.997 + scale: true + epsilon: 0.001 + } + } + depth: 128 + num_layers_before_predictor: 4 + kernel_size: 3 + class_prediction_bias_init: -4.6 + share_prediction_tower: true + use_depthwise: true + } + } + anchor_generator { + multiscale_anchor_generator { + min_level: 3 + max_level: 7 + anchor_scale: 4.0 + aspect_ratios: 1.0 + aspect_ratios: 2.0 + aspect_ratios: 0.5 + scales_per_octave: 2 + } + } + post_processing { + batch_non_max_suppression { + score_threshold: 1e-08 + iou_threshold: 0.6 + max_detections_per_class: 100 + max_total_detections: 100 + use_static_shapes: false + } + score_converter: SIGMOID + } + normalize_loss_by_num_matches: true + loss { + localization_loss { + weighted_smooth_l1 { + } + } + classification_loss { + weighted_sigmoid_focal { + gamma: 2.0 + alpha: 0.25 + } + } + classification_weight: 1.0 + localization_weight: 1.0 + } + encode_background_as_zeros: true + normalize_loc_loss_by_codesize: true + inplace_batchnorm_update: true + freeze_batchnorm: false + } +} +train_config { + batch_size: 2 + data_augmentation_options { + random_horizontal_flip { + } + } + data_augmentation_options { + random_crop_image { + min_object_covered: 0.0 + min_aspect_ratio: 0.75 + max_aspect_ratio: 3.0 + min_area: 0.75 + max_area: 1.0 + overlap_thresh: 0.0 + } + } + sync_replicas: true + optimizer { + momentum_optimizer { + learning_rate { + cosine_decay_learning_rate { + learning_rate_base: 0.08 + total_steps: 50000 + warmup_learning_rate: 0.026666 + warmup_steps: 1000 + } + } + momentum_optimizer_value: 0.9 + } + use_moving_average: false + } + fine_tune_checkpoint: "drive/MyDrive/ClearChlorella/Tensorflow/workspace/pre-trained-models/ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8/checkpoint/ckpt-0" + num_steps: 50000 + startup_delay_steps: 0.0 + replicas_to_aggregate: 8 + max_number_of_boxes: 100 + unpad_groundtruth_tensors: false + fine_tune_checkpoint_type: "detection" + fine_tune_checkpoint_version: V2 +} +train_input_reader { + label_map_path: "drive/MyDrive/ClearChlorella/Tensorflow/workspace/annotations/label_map.pbtxt" + tf_record_input_reader { + input_path: "drive/MyDrive/ClearChlorella/Tensorflow/workspace/annotations/train.record" + } +} +eval_config { + metrics_set: "coco_detection_metrics" + use_moving_averages: false +} +eval_input_reader { + label_map_path: "drive/MyDrive/ClearChlorella/Tensorflow/workspace/annotations/label_map.pbtxt" + shuffle: false + num_epochs: 1 + tf_record_input_reader { + input_path: "drive/MyDrive/ClearChlorella/Tensorflow/workspace/annotations/test.record" + } +} diff --git a/app/static/crop/checkpoint b/app/static/crop/checkpoint new file mode 100644 index 0000000..1e55680 --- /dev/null +++ b/app/static/crop/checkpoint @@ -0,0 +1,8 @@ +model_checkpoint_path: "ckpt-3" +all_model_checkpoint_paths: "ckpt-1" +all_model_checkpoint_paths: "ckpt-2" +all_model_checkpoint_paths: "ckpt-3" +all_model_checkpoint_timestamps: 1639188029.2147803 +all_model_checkpoint_timestamps: 1639189027.9790695 +all_model_checkpoint_timestamps: 1639189982.5161285 +last_preserved_timestamp: 1639188027.3312948 diff --git a/app/static/crop/ckpt-3.data-00000-of-00001 b/app/static/crop/ckpt-3.data-00000-of-00001 new file mode 100644 index 0000000..629ac17 Binary files /dev/null and b/app/static/crop/ckpt-3.data-00000-of-00001 differ diff --git a/app/static/crop/ckpt-3.index b/app/static/crop/ckpt-3.index new file mode 100644 index 0000000..80acdbc Binary files /dev/null and b/app/static/crop/ckpt-3.index differ diff --git a/app/static/crop/pipeline.config b/app/static/crop/pipeline.config new file mode 100644 index 0000000..72fb907 --- /dev/null +++ b/app/static/crop/pipeline.config @@ -0,0 +1,191 @@ +model { + ssd { + num_classes: 1 + image_resizer { + fixed_shape_resizer { + height: 640 + width: 640 + } + } + feature_extractor { + type: "ssd_mobilenet_v2_fpn_keras" + depth_multiplier: 1.0 + min_depth: 16 + conv_hyperparams { + regularizer { + l2_regularizer { + weight: 4e-05 + } + } + initializer { + random_normal_initializer { + mean: 0.0 + stddev: 0.01 + } + } + activation: RELU_6 + batch_norm { + decay: 0.997 + scale: true + epsilon: 0.001 + } + } + use_depthwise: true + override_base_feature_extractor_hyperparams: true + fpn { + min_level: 3 + max_level: 7 + additional_layer_depth: 128 + } + } + box_coder { + faster_rcnn_box_coder { + y_scale: 10.0 + x_scale: 10.0 + height_scale: 5.0 + width_scale: 5.0 + } + } + matcher { + argmax_matcher { + matched_threshold: 0.5 + unmatched_threshold: 0.5 + ignore_thresholds: false + negatives_lower_than_unmatched: true + force_match_for_each_row: true + use_matmul_gather: true + } + } + similarity_calculator { + iou_similarity { + } + } + box_predictor { + weight_shared_convolutional_box_predictor { + conv_hyperparams { + regularizer { + l2_regularizer { + weight: 4e-05 + } + } + initializer { + random_normal_initializer { + mean: 0.0 + stddev: 0.01 + } + } + activation: RELU_6 + batch_norm { + decay: 0.997 + scale: true + epsilon: 0.001 + } + } + depth: 128 + num_layers_before_predictor: 4 + kernel_size: 3 + class_prediction_bias_init: -4.6 + share_prediction_tower: true + use_depthwise: true + } + } + anchor_generator { + multiscale_anchor_generator { + min_level: 3 + max_level: 7 + anchor_scale: 4.0 + aspect_ratios: 1.0 + aspect_ratios: 2.0 + aspect_ratios: 0.5 + scales_per_octave: 2 + } + } + post_processing { + batch_non_max_suppression { + score_threshold: 1e-08 + iou_threshold: 0.6 + max_detections_per_class: 100 + max_total_detections: 100 + use_static_shapes: false + } + score_converter: SIGMOID + } + normalize_loss_by_num_matches: true + loss { + localization_loss { + weighted_smooth_l1 { + } + } + classification_loss { + weighted_sigmoid_focal { + gamma: 2.0 + alpha: 0.25 + } + } + classification_weight: 1.0 + localization_weight: 1.0 + } + encode_background_as_zeros: true + normalize_loc_loss_by_codesize: true + inplace_batchnorm_update: true + freeze_batchnorm: false + } +} +train_config { + batch_size: 4 + data_augmentation_options { + random_horizontal_flip { + } + } + data_augmentation_options { + random_crop_image { + min_object_covered: 0.0 + min_aspect_ratio: 0.75 + max_aspect_ratio: 3.0 + min_area: 0.75 + max_area: 1.0 + overlap_thresh: 0.0 + } + } + sync_replicas: true + optimizer { + momentum_optimizer { + learning_rate { + cosine_decay_learning_rate { + learning_rate_base: 0.08 + total_steps: 50000 + warmup_learning_rate: 0.026666 + warmup_steps: 1000 + } + } + momentum_optimizer_value: 0.9 + } + use_moving_average: false + } + fine_tune_checkpoint: "drive/MyDrive/TFODCourse/Tensorflow/workspace/pre-trained-models/ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8/checkpoint/ckpt-0" + num_steps: 50000 + startup_delay_steps: 0.0 + replicas_to_aggregate: 8 + max_number_of_boxes: 100 + unpad_groundtruth_tensors: false + fine_tune_checkpoint_type: "detection" + fine_tune_checkpoint_version: V2 +} +train_input_reader { + label_map_path: "drive/MyDrive/TFODCourse/Tensorflow/workspace/annotations/label_map.pbtxt" + tf_record_input_reader { + input_path: "drive/MyDrive/TFODCourse/Tensorflow/workspace/annotations/train.record" + } +} +eval_config { + metrics_set: "coco_detection_metrics" + use_moving_averages: false +} +eval_input_reader { + label_map_path: "drive/MyDrive/TFODCourse/Tensorflow/workspace/annotations/label_map.pbtxt" + shuffle: false + num_epochs: 1 + tf_record_input_reader { + input_path: "drive/MyDrive/TFODCourse/Tensorflow/workspace/annotations/test.record" + } +} diff --git a/app/static/css/about.css b/app/static/css/about.css new file mode 100644 index 0000000..8d8d521 --- /dev/null +++ b/app/static/css/about.css @@ -0,0 +1,54 @@ +.about { + display: flex; + max-width: 1800px; + align-items: center; + justify-content: center; + flex-wrap: wrap; + padding: 1vh 1vw 1vh 1vw; + } + + .profile { + display: flex; + flex-flow: column; + font-weight: 700; + font-size: max(1.5vh, 18px); + margin: 0 1vw 2vh 1vw; + color: #333; + } + + .profile-pic { + width: 25vw; + border-radius: 10px; + margin: 0 0 1vh 0; + max-width: 400px; + min-width: 250px; + } + + .illustration { + width: max(35vw, 350px); + margin: 3vh 1vw 1vh 1vw; + } + + .approach { + font-weight: 400; + font-size: min(2vh, 30px); + width: min(80vw, 700px); + } + + .dark { + display: flex; + align-items: center; + justify-content: center; + background-color: #333; + width: 100%; + } + + .partner { + height: min(75px, 5vh); + margin: 10px 10px 10px 10px; + } + + .white { + color: white; + width: 90vw; + } \ No newline at end of file diff --git a/app/static/css/bad_index.css b/app/static/css/bad_index.css new file mode 100644 index 0000000..a0f2bcb --- /dev/null +++ b/app/static/css/bad_index.css @@ -0,0 +1,722 @@ +/* universal elements */ +.hidden { + display: none; + } + + a, button { + text-decoration: none; + cursor: pointer; + } + .body { + display: flex; + font-family: DM Sans; + flex-flow: column nowrap; + align-items: center; + height: 100vh; + margin: 0; + background-repeat: no-repeat; + background-attachment: fixed; + background-image: linear-gradient(to bottom, #124d5a, hsla(190.83333333333331, 66.67%, 21.18%, 0.25)); + background-clip: border-box; + } + + .header { + width: 86vw; + height: 5vh; + display: flex; + margin: 1vh 0vh 1.5vh 0vh; + background: transparent; + justify-content: space-between; + align-items: center; + } + + .logo { + width: 5vh; + height: 5vh; + background-image: url("algaeorithm\ logo.svg"); + background-position: center; + background-size: 4.5vh; + background-repeat: no-repeat; + z-index: 2; + } + + nav { + position: absolute; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + background: url("menu\ algae.jpg") no-repeat center center fixed; + background-size: cover; + height: 100%; + background-color: #333; + z-index: 1; + visibility: hidden; + opacity: 0; + transition: all 300ms ease; + } + + nav ul { + display: flex; + float: right; + flex-flow: column; + align-items: centder; + justify-content: center; + text-align: right; + width: fit-content; + height: 35vh; + margin: 0 7vw 0 0; + padding: 0; + list-style: none; + } + + nav li { + text-decoration: none; + cursor: pointer; + } + + nav a { + font-size: 4vh; + font-weight: 500; + color: rgba(255, 255, 255, 0.96); + opacity: 0; + transition: opacity 200ms ease 300ms; + } + + nav h5 { + position: absolute; + bottom: 2vh; + left: 7vw; + color: rgba(255, 255, 255, 0.96); + text-align: left; + font-size: 0.83em; + font-weight: 400; + opacity: 0; + transition: all 200ms ease 300ms; + } + + .nav-info { + color: rgba(255, 255, 255, 0.96); + text-align: left; + font-size: 1em; + font-weight: 400; + } + + nav a:hover { + color: #d66d50; + } + + .nav-toggle { + display: none; +} + + .nav-toggle-label { + width: 4vh; + height: 4vh; + border-radius: 2vh; + background-image: url("hamburger.svg"); + background-position: center; + background-size: 3vh; + background-repeat: no-repeat; + transition: all 100ms ease; + z-index: 2; + cursor: pointer; + } + + .nav-toggle:checked ~ .nav-toggle-label { + background-image: url("delete\ \(white\).svg"); + } + + .nav-toggle-label:hover { + background-color: rgba(0, 0, 0, 0.164); + } + + .nav-toggle:checked ~ nav { + visibility: visible; + opacity: 1; + } + + .nav-toggle:checked ~ nav a { + opacity: 1; + } + + .nav-toggle:checked ~ nav h5 { + opacity: 1; + } + + .palette { + display: flex; + flex-flow: column nowrap; + width: 90vw; + height: 80vh; + background-color: hsla(0, 0.00%, 100.00%, 0.80); + border-radius: 2vw; + box-shadow: .1vw .1vw 1.5vw .1vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); + text-align: center; + margin-bottom: 2.5vh; + } + + .page { + height: 90vh; + align-items: center; + overflow-y:auto; + } + + .page::-webkit-scrollbar { + display: none; + } + +/* all: unset; resets all atributes for media queries */ + @media only screen and (min-width: 120px) and (max-width: 639px) { /* for mobile - still need to incorporate other pages */ + .palette { + width: 100vw; + } + + .lab { + flex-flow: column; + height: 80vh; + } + + .photos { + width: 100vw !important; + height: 50vh !important; + } + + .add-photo { + height: 30vw !important; + width: 30vw !important; + } + + .preview-image { + height: 30vw !important; + } + + .settings { + flex-flow: row !important; + width: 100vw !important; + height: 30vh !important; + } + + .setting { + height: 30vh !important; + justify-content: space-around; + flex-grow: 1; + } + + .settings-label { + display: flex !important; + font-size: 15px !important; + } + + .types { + flex-flow: column; + } + + .photo-type { + border-radius: 30px !important; + height: 9vh; + width: auto !important; + } + + .photo-type-label { + max-height: 12vh; + color: transparent; + } + + .row { + flex-direction: column; + justify-content: left; + } + + .analyze-button { + width: 90vw !important; + } + } + + /* algae INPUT */ + .lab { + display: flex; + align-items: center; + } + + .instruction { + font-family: DM Sans; + font-size: min(3vw, 36px); + font-weight: 500; + text-align: center; + width: min(54vw, 1080px); + color: hsla(0, 0.00%, 20.00%, 1.00); + } + + .photos { + display: flex; + flex-direction: column; + flex-wrap: wrap; + flex-grow: 1; + align-items: center; + height: 80vh; + width: min(54vw, 1080px); + justify-content: center; + } + + .buttons { + display: flex; + margin-top: 5vh; + justify-content: space-around; + width: min(45vw, 600px); + } + + .input-values { + height: 6vh; + border-style: solid; + border-width: 0.5px; + border-radius: 2vh; + background-color: transparent; + font-family: DM Sans; + color: hsla(0, 0.00%, 20.00%, 1.00); + font-size: min(2vh, 30px); + font-weight: 400; + width: min(17vw, 250px); + background-clip: border-box; + object-fit: fill; + text-align: center; + flex-shrink: 1; + transition: all 300ms ease; + } + + .input-values:focus { + border-color: skyblue; + outline: none; + } + + .upload-photo { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + } + + .upload-photo:hover { + background-color: hsla(0, 0.00%, 20.00%, 1.00); + color: hsla(0, 0.00%, 100.00%, 1.00); + } + + .add-photo { + display: block; + height: 13vh; + width: 18vh; + border-radius: 30px; + margin: 1vh 1vw 1vh 1vw; + background-image: url("plus.svg"); + background-position: 50% 50%; + background-size: 8vh; + background-repeat: no-repeat; + background-color: #333; + cursor: pointer; + transition: all 200ms ease; + } + +.add-photo:hover { + background-size: 10vh; +} + + .previews { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + width: calc(100% - 2vw); + height: 78vh; + } + + .preview { + flex-shrink: 1; + height: 13vh; + width: auto; + margin: 1vh 1vw 1vh 1vw; + background-clip: border-box; + object-fit: contain; + position: relative; + transition: all 200ms ease; + } + + .preview-image { + height: 13vh; + width: auto; + border-radius: 30px; + } + +.delete-photo { + display: none; + height: 4vh; + width: 4vh; + border-radius: 1vh; + position: absolute; + top: 1.5vh; + right: 1.5vh; + background-image: url("delete.svg"); + background-position: center; + background-size: 2.5vh; + background-repeat: no-repeat; + background-color: rgba(255, 255, 255, 0.9); + border: none; + transition: all 200ms ease; + } + + .delete-photo:hover { + border-radius: 2vh; + } + +.preview:hover .delete-photo { + display: block; +} + + .settings { + display: flex; + flex-direction: column; + width: min(36vw, 500px); + height: 80vh; + justify-content: space-between; + border-radius: 2vw; + background-color: hsla(0, 0.00%, 100.00%, 0.50); + } + + .setting { + display: flex; + flex-direction: column; + align-items: center; + height: 25vh; + } + + .setting-name { + font-family: DM Sans; + font-size: 2vh; + } + + .title { + justify-content: center; + } + + .types { + display: flex; + width: 90%; + justify-content: center; + } + +input[type='radio'] { + -webkit-appearance: none; + -moz-appearance: none; +} + + .radio { + width: 2vh; + height: 2vh; + border: 2px solid #333; + border-radius: 50%; + box-sizing: border-box; + padding: 2px; + margin-right: 10px; + transition: all 300ms ease; + } + + .radio::after { + content: ""; + width: 100%; + height: 100%; + display: block; + background: #333; + border-radius: 50%; + transform: scale(0); + transition: transform 0.15s; + } + + input[type='radio']:checked + .radio::after { + transform: scale(1); + } + + .row { + display: flex; + align-items: center; + height: 6vh; + width: 80%; + } + + .photo-type { + width: 80%; + max-height: 20vh; + border: 10px solid transparent; + border-radius: max(2vw, 30px); + transition: all 300ms ease; + } + + .photo-type:hover, .selected-photo-type { + border: 10px solid #d66d50; + } + + .settings-label { + font-family: DM Sans; + font-size: 1.5vh; + display: inline-flex; + align-items: center; + cursor: pointer; + } + + .photo-type-label { + cursor: pointer; + } + + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + .settings-input { + color: white; + background-color: #333; + font-family: DM Sans; + font-weight: 500; + font-size: 1.5vh; + text-align: center; + width: min(5vw, 200px); + height: 3vh; + margin: 0 min(1vw, 10px) 0 1vw; + border: 2px solid transparent; + border-radius: 1vw; + transition: all 300ms ease; + } + + .settings-input:hover { + color: #333; + background-color: transparent; + border-color: #333; + } + + .settings-input:focus { + color: #333; + background-color: transparent; + border-color: #333; + outline: none; + } + + .switch { + --width: calc(var(--height) * 2); + --height: 2vh; + --border-radius: calc(var(--height) / 2); + display: inline-block; + cursor: pointer; + margin-left: 20px; + } + + .switch_input { + display: none; + } + + .switch_fill { + position: relative; + width: var(--width); + height: var(--height); + border-radius: var(--border-radius); + background: #333; + transition: background 0.2s; + } + + .switch_fill::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: var(--height); + height: var(--height); + background: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.25); + border-radius: var(--border-radius); + transition: transform 0.2s; + } + + .switch_input:checked ~ .switch_fill::after { + transform: translateX(var(--height)); + } + + .switch_input:checked ~ .switch_fill { + background: #d66d50; + } + + .analyze { + display: flex; + justify-content: center; + align-items: center; + width: 10vw; + } + + .analyze-button { + height: 5vh; + width: min(40vw, 500px); + display: flex; + font-family: DM Sans; + font-size: 2vh; + align-items: center; + justify-content: center; + border-radius: 2vh; + background-image: url("analyze\ button.svg"); + background-position: 90% 50%; + background-size: 2.5vh; + background-repeat: no-repeat; + background-color: rgba(255, 255, 255, 0.9); + box-shadow: .1vw .1vw 1.5vw .1vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); + cursor: pointer; + transition: all 200ms ease; + } + + .analyze-button:hover { + background-position: 92% 50%; + box-shadow: .1vw 1vw 1.5vw .5vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); + } + + .page-name { + font-family: 'Amiri', serif; + color: rgba(255, 255, 255, 0.96); + } + + /* CONTENT page */ + .content { + display: flex; + padding: 0 5vw 0 5vw; + align-items: center; + justify-content: center; + margin: 2vh 0 2vh 0; + height: 10vh; + width: 75vw; + color: #333; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 2vh; + box-shadow: .1vw .1vw 1.5vw .1vw rgba(18, 77, 90, 0.15); + transition: all 300ms ease; + } + + .content:hover { + box-shadow: .1vw .1vw 1.5vw .1vw rgba(18, 77, 90, 0.459); + } + + .video-title { + margin: 0 4vw 0 4vw; + display: flex; + justify-content: center; + width: 20vw; + } + + .date { + font-weight: 400; + display: flex; + align-items: left; + flex-wrap: nowrap; + flex-grow: 1; + flex-basis: 0; + } + + .thumbnail { + height: 8vh; + border-radius: 1vh; + margin-left: auto; + } + + .thumbnail-container { + display: flex; + height: 8vh; + flex-grow: 1; + flex-basis: 0; + } + + /* PRESS page */ + .press { + display: flex; + flex-flow: column; + align-items: center; + padding: 15px 0 15px 0; + max-width: 550px; + width: 100%; + margin-top: 10px; + margin-bottom: 10px; + border-radius: 10px; + background-color: white; + color: #333; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + transition: background-color 100ms ease; + } + + .press:hover { + background-color: rgba(247, 249, 249); + } + + .press-title { + max-width: 468px; + flex-wrap: wrap; + } + + .press-logo { + max-width: 468px; + max-height: 50px; + flex-grow: 1; + flex-basis: 0; + } + + .press-image { + max-width: 468px; + border-radius: 10px; + margin: 1vh 0 1vh 0; + } + + /* ABOUT page */ + .about { + display: flex; + max-width: 1800px; + align-items: center; + justify-content: center; + flex-wrap: wrap; + padding: 1vh 1vw 1vh 1vw; + } + + .profile { + display: flex; + flex-flow: column; + font-weight: 700; + font-size: max(1.5vh, 18px); + margin: 0 1vw 2vh 1vw; + color: #333; + } + + .profile-pic { + width: 25vw; + border-radius: 10px; + margin: 0 0 1vh 0; + max-width: 400px; + min-width: 250px; + } + + .illustration { + width: max(35vw, 350px); + margin: 3vh 1vw 1vh 1vw; + } + + .approach { + font-weight: 400; + font-size: min(2vh, 30px); + width: min(80vw, 700px); + } + + .dark { + display: flex; + align-items: center; + justify-content: center; + background-color: #333; + width: 100%; + } + + .partner { + height: min(75px, 5vh); + margin: 10px 10px 10px 10px; + } + + .white { + color: white; + width: 90vw; + } \ No newline at end of file diff --git a/app/static/css/content.css b/app/static/css/content.css new file mode 100644 index 0000000..e547c96 --- /dev/null +++ b/app/static/css/content.css @@ -0,0 +1,47 @@ +.content { + display: flex; + padding: 0 5vw 0 5vw; + align-items: center; + justify-content: center; + margin: 2vh 0 2vh 0; + height: 10vh; + width: 75vw; + color: #333; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 2vh; + box-shadow: .1vw .1vw 1.5vw .1vw rgba(18, 77, 90, 0.15); + transition: all 300ms ease; + } + + .content:hover { + box-shadow: .1vw .1vw 1.5vw .1vw rgba(18, 77, 90, 0.459); + } + + .video-title { + margin: 0 4vw 0 4vw; + display: flex; + justify-content: center; + width: 20vw; + } + + .date { + font-weight: 400; + display: flex; + align-items: left; + flex-wrap: nowrap; + flex-grow: 1; + flex-basis: 0; + } + + .thumbnail { + height: 8vh; + border-radius: 1vh; + margin-left: auto; + } + + .thumbnail-container { + display: flex; + height: 8vh; + flex-grow: 1; + flex-basis: 0; + } \ No newline at end of file diff --git a/app/static/css/index.css b/app/static/css/index.css new file mode 100644 index 0000000..0d4237c --- /dev/null +++ b/app/static/css/index.css @@ -0,0 +1,1578 @@ +/* universal elements */ +.hidden { + display: none; + } + + a, button { + text-decoration: none; + cursor: pointer; + } + .body { + display: flex; + font-family: DM Sans; + flex-flow: column nowrap; + align-items: center; + height: 100vh; + margin: 0; + background-repeat: no-repeat; + background-attachment: fixed; + background-image: linear-gradient(to bottom, #124d5a, hsla(190.83333333333331, 66.67%, 21.18%, 0.25)); + background-clip: border-box; + } + + .header { + width: 86vw; + height: 5vh; + display: flex; + margin: 1vh 0vh 1.5vh 0vh; + background: transparent; + justify-content: space-between; + align-items: center; + } + + .logo { + width: 5vh; + height: 5vh; + background-image: url("/static/logos/algaeorithm_logo.svg"); + background-position: center; + background-size: 4.5vh; + background-repeat: no-repeat; + z-index: 2; + } + + nav { + position: absolute; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + background: url("/static/logos/algae_menu.jpg") no-repeat center center fixed; + background-size: cover; + height: 100%; + background-color: #333; + z-index: 1; + visibility: hidden; + opacity: 0; + transition: all 300ms ease; + } + + nav ul { + display: flex; + float: right; + flex-flow: column; + align-items: centder; + justify-content: center; + text-align: right; + width: fit-content; + height: 35vh; + margin: 0 7vw 0 0; + padding: 0; + list-style: none; + } + + nav li { + text-decoration: none; + cursor: pointer; + } + + nav a { + font-size: 4vh; + font-weight: 500; + color: rgba(255, 255, 255, 0.96); + opacity: 0; + transition: opacity 200ms ease 300ms; + } + + nav h5 { + position: absolute; + bottom: 2vh; + left: 7vw; + color: rgba(255, 255, 255, 0.96); + text-align: left; + font-size: 0.83em; + font-weight: 400; + opacity: 0; + transition: all 200ms ease 300ms; + } + + .nav-info { + color: rgba(255, 255, 255, 0.96); + text-align: left; + font-size: 1em; + font-weight: 400; + } + + nav a:hover { + color: #d66d50; + } + + .nav-toggle { + display: none; +} + + .nav-toggle-label { + width: 4vh; + height: 4vh; + border-radius: 2vh; + background-image: url("/static/logos/hamburger.svg"); + background-position: center; + background-size: 3vh; + background-repeat: no-repeat; + transition: all 100ms ease; + z-index: 2; + cursor: pointer; + } + + .nav-toggle:checked ~ .nav-toggle-label { + background-image: url("/static/logos/delete_white.svg"); + } + + .nav-toggle-label:hover { + background-color: rgba(0, 0, 0, 0.164); + } + + .nav-toggle:checked ~ nav { + visibility: visible; + opacity: 1; + } + + .nav-toggle:checked ~ nav a { + opacity: 1; + } + + .nav-toggle:checked ~ nav h5 { + opacity: 1; + } + + .palette { + display: flex; + flex-flow: column nowrap; + width: 90vw; + height: 80vh; + background-color: hsla(0, 0.00%, 100.00%, 0.80); + border-radius: 2vw; + box-shadow: .1vw .1vw 1.5vw .1vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); + text-align: center; + margin-bottom: 2.5vh; + align-items: center; + } + + .page { + height: 90vh; + align-items: center; + overflow-y:auto; + } + + .page::-webkit-scrollbar { + display: none; + } + + .tab-nav { + --overview: 0; + --images: 0; + display: flex; + justify-content: center; + align-items: center; + height: 5vh; + margin-top: 2.5vh; + margin-bottom: 2.5vh; + border-radius: 2.5vh; + box-shadow: 0.1vw 0.1vw 1.5vw 0.1vw hsl(191deg 67% 21% / 48%); + background-color: hsla(0, 0.00%, 100.00%, 0.80); + } + + .tab, .selected-tab { + position: relative; + font-family: DM Sans; + font-size: min(2vh, 30px); + font-weight: 500; + color: #333; + margin-left: 5vw; + margin-right: 5vw; + padding: 0 10px; + } + + .tab::after, .selected-tab::after { + content: ""; + position: absolute; + background-color: #333; + height: 0.3vh; + left: 0; + bottom: -1vh; + transition: 300ms; + } + + .tab::after { + width: 0px; + } + + .tab:hover::after, .selected-tab::after { + width: 100%; + } + + .see-images, .see-overview { + display: flex; + align-items: flex-end; + justify-content: center; + height: 5%; + } + + .next-page, .show-overview { + font-family: DM Sans; + font-size: 3vh; + display: flex; + justify-content: center; + align-items: center; + } + + .invisible { + visibility: hidden; + } + + .margin-five { + margin: 0 2.5%; + } + + ::-webkit-scrollbar { + display: none; + } + +/* all: unset; resets all atributes for media queries */ + @media only screen and (min-width: 120px) and (max-width: 639px) { /* for mobile - still need to incorporate other pages */ + .palette { + width: 100vw; + } + + .lab { + flex-flow: column; + height: 80vh; + } + + .photos { + width: 100vw !important; + height: 50vh !important; + } + + .add-photo { + height: 30vw !important; + width: 30vw !important; + } + + .preview-image { + height: 30vw !important; + } + + .preview { + height: 30vw !important; + } + + .preview-input { + font-size: 10px !important; + } + + .preview-input::placeholder { + color: transparent !important; + } + + .settings { + flex-flow: row !important; + width: 100vw !important; + height: 30vh !important; + } + + .setting { + height: 30vh !important; + justify-content: flex-start; + flex-grow: 1; + width: 33% !important; + } + + .settings-label { + display: flex !important; + font-size: 15px !important; + } + .types { + flex-flow: column; + } + + .photo-type { + border-radius: 30px !important; + height: 9vh; + width: auto !important; + } + + .photo-type-label { + max-height: 12vh; + color: transparent; + } + + .photo-type:hover, .selected-photo-type { + border: 5px solid #333 !important; + } + + .photo-type::selection { + border: 5px solid #333 !important; + } + + .row { + flex-direction: column; + justify-content: left; + justify-content: space-between !important; + margin: 0 0 4vh 0 !important; + } + + .switch { + margin-left: 0px !important; + } + + .analyze-button { + width: 90vw !important; + } + + #loader-wrapper { + left: 0 !important; + width: 100vw !important; + height: 80vh; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 2vw; + z-index: 1000; + } + + .visual { + height: 80% !important; + width: 100% !important; + } + + .info { + height: 20% !important; + width: 100% !important; + } + + .image-container { + --results-image-height: 70% !important; + --results-image-width: auto !important; + } + + .img-nav { + margin: 0 0 2% 0 !important; + } + + .show-pictures { + margin: 2% 0 0 0 !important; + } + + .image-type { + font-size: unset !important; + } + + .we-found { + font-size: unset !important; + } + + } + + /* algae INPUT */ + .lab { + display: flex; + align-items: center; + } + + .instruction { + font-family: DM Sans; + font-size: min(3vw, 36px); + font-weight: 500; + text-align: center; + width: min(54vw, 1080px); + color: hsla(0, 0.00%, 20.00%, 1.00); + } + + .photos { + display: flex; + flex-direction: column; + flex-wrap: wrap; + flex-grow: 1; + align-items: center; + height: 80vh; + width: min(54vw, 1080px); + justify-content: center; + } + + .buttons { + display: flex; + margin-top: 5vh; + justify-content: space-around; + width: min(45vw, 600px); + } + + .input-values { + height: 6vh; + border-style: solid; + border-width: 0.5px; + border-radius: 2vh; + background-color: transparent; + font-family: DM Sans; + color: hsla(0, 0.00%, 20.00%, 1.00); + font-size: min(2vh, 30px); + font-weight: 400; + width: min(17vw, 250px); + background-clip: border-box; + object-fit: fill; + text-align: center; + flex-shrink: 1; + transition: all 300ms ease; + } + + .input-values:focus { + border-color: skyblue; + outline: none; + } + + .upload-photo { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + } + + .upload-photo:hover { + background-color: hsla(0, 0.00%, 20.00%, 1.00); + color: hsla(0, 0.00%, 100.00%, 1.00); + } + + .add-photo { + display: block; + height: 13vh; + width: 18vh; + border-radius: 30px; + margin: 1vh 1vw 1vh 1vw; + background-image: url("/static/logos/plus.svg"); + background-position: 50% 50%; + background-size: 8vh; + background-repeat: no-repeat; + background-color: #333; + cursor: pointer; + transition: all 200ms ease; + } + + .add-photo:hover { + background-size: 10vh; + } + + .previews { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + width: calc(100% - 2vw); + height: 78vh; + overflow-y: auto; + } + + .preview { + flex-shrink: 1; + height: 13vh; + width: auto; + margin: 1vh 1vw 1vh 1vw; + background-clip: border-box; + object-fit: contain; + position: relative; + transition: all 200ms ease; + } + + .preview-image { + height: 13vh; + width: auto; + border-radius: 30px; + } + +.delete-photo { + display: none; + height: 4vh; + width: 4vh; + border-radius: 1vh; + position: absolute; + top: 1.5vh; + right: 1.5vh; + background-image: url("/static/logos/delete.svg"); + background-position: center; + background-size: 2.5vh; + background-repeat: no-repeat; + background-color: rgba(255, 255, 255, 0.9); + border: none; + transition: all 200ms ease; + } + + .delete-photo:hover { + border-radius: 2vh; + } + +.preview:hover .delete-photo { + display: block; +} + +.specific-inputs { + position: absolute; + bottom: 0; + left: 0; + height: 25%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.full { + border-radius: 0 0 30px 30px; +} + +.half-left { + border-radius: 0 0 0 30px; +} + +.half-right { + border-radius: 0 0 30px 0; +} + +.preview-input { +width: 100%; +height: 100%; +flex-grow: 1; +font-size: min(1.5vh, 1.5vw); +font-weight: 500; +text-align: center; +white-space: nowrap; +overflow: hidden; +text-overflow: clip; +border: none; +font-family: DM Sans; +color: white; +transition: all 300ms ease; +} + +.preview-input:focus { +outline: none; +} + +.preview-input::placeholder { +color: white; +} + + .settings { + display: flex; + flex-direction: column; + width: min(36vw, 500px); + height: 80vh; + justify-content: space-between; + border-radius: 2vw; + background-color: hsla(0, 0.00%, 100.00%, 0.50); + } + + .setting { + display: flex; + flex-direction: column; + align-items: center; + height: 25vh; + width: auto; + } + + #time-setting { + width: 80%; + } + + .setting-name { + padding: 3px 1vw 3px 1vw; + border-radius: min(1.5vh, 10px); + font-size: 2vh; + } + + .advanced { + color: white; + padding: 3px 1vw 3px 1vw; + border-radius: min(1.5vh, 10px); + font-size: 2vh; + } + + .orange { + background-color: #d66d50; + } + + .green { + background-color: #226371; + } + + .title { + justify-content: center; + } + + .types { + display: flex; + width: 90%; + justify-content: space-evenly; + } + +input[type='radio'] { + -webkit-appearance: none; + -moz-appearance: none; +} + + .radio { + width: 2vh; + height: 2vh; + border: 2px solid #333; + border-radius: 50%; + box-sizing: border-box; + padding: 2px; + margin-right: 10px; + transition: all 300ms ease; + } + + .radio::after { + content: ""; + width: 100%; + height: 100%; + display: block; + background: #333; + border-radius: 50%; + transform: scale(0); + transition: transform 0.15s; + } + + input[type='radio']:checked + .radio::after { + transform: scale(1); + } + + .row { + display: flex; + align-items: center; + height: 6vh; + width: 80%; + } + + .photo-type { + width: 80%; + max-height: 20vh; + border: 10px solid transparent; + border-radius: max(2vw, 30px); + transition: all 300ms ease; + } + + .photo-type:hover, .selected-photo-type { + border: 10px solid #333; + } + + .photo-type::selection { + border: 10px solid #333; + } + + .settings-label { + font-family: DM Sans; + font-size: max(1.5vh, 18px); + display: inline-flex; + align-items: center; + cursor: pointer; + } + + .photo-type-label { + cursor: pointer; + } + + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + .settings-input { + color: white; + background-color: #333; + font-family: DM Sans; + font-weight: 500; + font-size: 1.5vh; + text-align: center; + width: min(5vw, 200px); + height: 3vh; + margin: 0 min(1vw, 10px) 0 1vw; + border: 2px solid transparent; + border-radius: 1vw; + transition: all 300ms ease; + } + + .settings-input:hover { + color: #333; + background-color: transparent; + border-color: #333; + } + + .settings-input:focus { + color: #333; + background-color: transparent; + border-color: #333; + outline: none; + } + + .switch { + --width: calc(var(--height) * 2); + --height: 2vh; + --border-radius: calc(var(--height) / 2); + display: inline-block; + cursor: pointer; + margin-left: 20px; + } + + .switch_input { + display: none; + } + + .switch_fill { + position: relative; + width: var(--width); + height: var(--height); + border-radius: var(--border-radius); + background: #333; + transition: background 0.2s; + } + + .switch_fill::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: var(--height); + height: var(--height); + background: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.25); + border-radius: var(--border-radius); + transition: transform 0.2s; + } + + .switch_input:checked ~ .switch_fill::after { + transform: translateX(var(--height)); + } + + .switch_input:checked ~ .switch_fill { + background: #d66d50; + } + + .analyze { + display: flex; + justify-content: center; + align-items: center; + width: 10vw; + } + + .analyze-button { + height: 5vh; + width: min(40vw, 500px); + display: flex; + font-family: DM Sans; + font-size: 2vh; + align-items: center; + justify-content: center; + border-radius: 2vh; + background-image: url("/static/logos/analyze_button.svg"); + background-position: 90% 50%; + background-size: 2.5vh; + background-repeat: no-repeat; + background-color: rgba(255, 255, 255, 0.9); + box-shadow: .1vw .1vw 1.5vw .1vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); + cursor: pointer; + transition: all 200ms ease; + } + + .analyze-button:hover { + background-position: 92% 50%; + box-shadow: .1vw 1vw 1.5vw .5vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); + } + + .page-name { + font-family: 'Amiri', serif; + color: rgba(255, 255, 255, 0.96); + } + + /* LOADING */ + #loader-wrapper { + position: absolute; + top: 7.5vh; + left: 5vw; + width: 90vw; + height: 80vh; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 2vw; + z-index: 1000; + } + + .loading-box { + display: flex; + flex-flow: column nowrap; + position: absolute; + justify-content: space-between; + align-items: center; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + height: 20vw; + width: 18vw; + } + + .loading-text, .cancel-button { + justify-content: center; + align-items: center; + display: flex; + font-family: DM Sans; + text-align: center; + font-size: min(3vh, 3vw); + width: 100%; + } + + .cancel-button { + border: .1vh solid; + border-radius: 1vh; + background-color: transparent; + padding: 15px 15px 15px 15px; + height: 10%; + width: 50%; + font-size: min(2.5vh, 2.5vw); + color: #333; + transition: all 200ms ease; + } + + .cancel-button:hover { + background-color: #333; + color: white; + } + + #loader { + width: 6vw; + height: 6vw; + margin: 0 25%; + border-radius: 50%; + border: .75vh solid #333; + border-top-color: transparent; + -webkit-animation: spin 1.5s linear infinite; + animation: spin 1.5s linear infinite; + } + + + @-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(0deg); /* IE 9 */ + transform: rotate(0deg); /* Firefox 16+, IE 10+, Opera */ + } + 100% { + -webkit-transform: rotate(360deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(360deg); /* IE 9 */ + transform: rotate(360deg); /* Firefox 16+, IE 10+, Opera */ + } + } + @keyframes spin { + 0% { + -webkit-transform: rotate(0deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(0deg); /* IE 9 */ + transform: rotate(0deg); /* Firefox 16+, IE 10+, Opera */ + } + 100% { + -webkit-transform: rotate(360deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(360deg); /* IE 9 */ + transform: rotate(360deg); /* Firefox 16+, IE 10+, Opera */ + } + } + + /* STATISTICAL OVERVIEW */ + .top, .bottom { + height: 50%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + } + + .counts, .concentrations { + width: 50%; + height: 100%; + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + } + + .stats-title { + display: flex; + justify-content: center; + align-items: center; + font-size: min(2.5vh, 2.5vw); + font-weight: bold; + font-family: DM Sans; + height: 15%; + width: 80%; + margin: 1vh 10%; + text-align: center; + } + + .stats-info { + display: flex; + justify-content: center; + flex-flow: column nowrap; + height: 70%; + width: 80%; + margin: 2vh 10%; + } + + .stats-section { + height: 5vh; + width: 80%; + margin: .3vh 10%; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + font-family: DM Sans; + font-size: min(2vh, 2vw); + } + + .graphs { + height: 100%; + width: 50%; + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + } + + .graph { + object-fit: contain; + border: 0.2vh solid #333; + flex-shrink: 1; + margin: 2.5vh 2.5vw; + cursor: crosshair; + width: 50vw; + max-width: fit-content; + max-height: 50vh; + } + + + .change-graph { + width: 95%; + height: 7.5%; + margin: .5vh 2.5%; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + flex-grow: 1; + } + + .graph-button { + height: 33%; + padding-top: .25vh; + padding-bottom: .25vh; + flex-grow: 1; + margin-left: 2.5%; + margin-right: 2.5%; + border-radius: 5vh; + border: .1vh solid transparent; + max-width: 20%; + flex-shrink: 1; + font-size: min(1.75vh, 1.75vw); + } + + /* VISUAL ANALYSIS */ + .lab { + display: flex; + align-items: center; + width: 100%; + height: 100%; + } + + .visual { + height: 100%; + display: flex; + width: 75%; + flex-flow: column nowrap; + align-items: center; + justify-content: center; + } + + .img-nav { + margin-top: 2.5vh; + --img-nav-height: 5vh; + --inner-nav-height: calc(var(--img-nav-height) * 4/5); + display: flex; + justify-content: center; + align-items: center; + height: var(--img-nav-height); + } + + .image-number { + display: flex; + align-items: center; + height: var(--inner-nav-height); + font-family: DM Sans; + font-size: min(max(2vw, calc(var(--img-nav-height)*3/5)), 36px); + text-align: center; + } + + .last, .next { + z-index: 69; + display: flex; + align-items: center; + height: var(--inner-nav-height); + width: var(--inner-nav-height); + border-style: solid; + border-width: 1px; + border-color: transparent; + border-radius: 3vw; + background-color: transparent; + background-size: var(--inner-nav-height) var(--inner-nav-height); + transition-property: all; + transition-duration: 300ms; + transition-timing-function: ease; + } + + .next:hover { + background-color: hsla(0, 0.00%, 20.00%, 1.00); + background-image: url("/static/logos/next_button(white).svg"); + color: hsla(0, 0.00%, 20.00%, 1.00); + } + + .last:hover { + background-color: hsla(0, 0.00%, 20.00%, 1.00); + background-image: url("/static/logos/last_button(white).svg"); + color: hsla(0, 0.00%, 100.00%, 1.00); + } + + .last { + margin-right: 2vw; + background-image: url("/static/logos/last_button.svg"); + } + + .next { + margin-left: 2vw; + background-image: url("/static/logos/next_button.svg"); + } + + .image-container { + --results-image-height: 65vh; + --results-image-width: 67.5vw; + height: var(--results-image-height); + width: var(--results-image-width); + display: flex; + align-items: center; + justify-content: center; + } + + .algae { + height: 60vh; + object-fit: contain; + flex-shrink: 1; + margin: 2.5vh 2.5vw; + cursor: crosshair; + width: 62.5vw; + } + + .show-pictures { + display: flex; + justify-content: center; + align-items: center; + height: 5vh; + margin-bottom: 2.5vh; + width: 67.5vw; + flex-shrink: 1; + } + + .image-type, .graph-button { + display: flex; + justify-content: center; + align-items: center; + text-align: center; + font-family: DM Sans; + color: #333; + white-space: nowrap; + transition-property: all; + transition-duration: 300ms; + transition-timing-function: ease; + } + + .image-type { + height: 5vh; + margin-left: 3vw; + margin-right: 3vw; + border-radius: 20vh; + border: .3vh solid transparent; + font-size: min(2vw, 30px); + padding: 9px 15px; + } + + .image-type:hover, .graph-button:hover { + color: hsla(0, 0.00%, 100.00%, 1.00); + background-color: hsla(0, 0.00%, 20.00%, 1.00); + } + + .info { + display: flex; + flex-direction: column; + width: 25%; + height: 80vh; + align-items: center; + justify-content: space-evenly; + border-radius: 2vw; + background-color: hsla(0, 0.00%, 100.00%, 0.50); + } + + .we-found { + width: 100%; + font-family: DM Sans; + font-size: min(5vh, 3vw); + } + + .results-unit { + font-size: min(3vh, 2vw); + } + + .download-photo { + height: 5vh; + width: 5vh; + border-radius: 2vh; + background-image: url("/static/logos/download_white.svg"); + background-position: center; + background-size: 3vh; + background-repeat: no-repeat; + background-color: #333; + transition: all 200ms ease; + } + + .ghost { + visibility: hidden; + } + + .download-photo:hover { + border-radius: 1vh; + } + + /* TABLE */ + .csv-table { + width: 90%; + height: 85%; + margin-top: 4vh; + overflow-y: auto; + table-layout: fixed; + border-collapse: collapse; + } + + thead, tfoot { + font-weight: bold; + } + + .csv-table, td { + border: .1vh solid; + font-family: DM Sans; + font-weight: inherit; + font-size: min(3vh, 2vw); + } + + td { + max-height: 5vh; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .data-0 { + max-width: 26vw; + } + + .data-1, .data-2, .data-3, .data-4 { + max-width: 13vw; + } + + tr { + cursor: pointer; + width: 100%; + } + +.download-csv { + width: 25%; + height: 5%; + margin: 1.5% 0; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(255, 255, 255, 0.8); + border: none; + border-radius: 2vw; + font-size: min(2vh, 18px); + font-family: DM Sans; + font-weight: 500; + color: #333; + box-shadow: .1vw .1vw 1.5vw .1vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); + transition: all 300ms ease; +} + +.download-csv:hover { + background-color: #333; + color: white; +} + + /* CONTENT page */ + .content { + display: flex; + padding: 0 5vw 0 5vw; + align-items: center; + justify-content: center; + margin: 2vh 0 2vh 0; + height: 10vh; + width: 75vw; + color: #333; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 2vh; + box-shadow: .1vw .1vw 1.5vw .1vw rgba(18, 77, 90, 0.15); + transition: all 300ms ease; + } + + .content:hover { + box-shadow: .1vw .1vw 1.5vw .1vw rgba(18, 77, 90, 0.459); + } + + .video-title { + margin: 0 4vw 0 4vw; + display: flex; + justify-content: center; + width: 20vw; + font-size: min(5vw, 2vh); + } + + .date { + font-weight: 400; + display: flex; + align-items: left; + flex-wrap: nowrap; + flex-grow: 1; + flex-basis: 0; + font-size: min(5vw, 1.6vh); + } + + .thumbnail { + height: 8vh; + border-radius: 1vh; + margin-left: auto; + } + + .thumbnail-container { + display: flex; + height: 8vh; + flex-grow: 1; + flex-basis: 0; + } + + /* PRESS page */ + .press { + display: flex; + flex-flow: column; + align-items: center; + padding: 15px 0 15px 0; + max-width: 550px; + width: 100%; + margin-top: 10px; + margin-bottom: 10px; + border-radius: 10px; + background-color: white; + color: #333; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + transition: background-color 100ms ease; + } + + .press:hover { + background-color: rgba(247, 249, 249); + } + + .press-title { + max-width: 468px; + flex-wrap: wrap; + } + + .press-logo { + max-width: 468px; + max-height: 50px; + flex-grow: 1; + flex-basis: 0; + } + + .press-image { + max-width: 468px; + border-radius: 10px; + margin: 1vh 0 1vh 0; + } + + /* ABOUT page */ + .about { + display: flex; + max-width: 1800px; + align-items: center; + justify-content: center; + flex-wrap: wrap; + padding: 1vh 1vw 1vh 1vw; + } + + .profile { + display: flex; + flex-flow: column; + font-weight: 700; + font-size: max(1.5vh, 18px); + margin: 0 1vw 2vh 1vw; + color: #333; + } + + .profile-pic { + width: 25vw; + border-radius: 10px; + margin: 0 0 1vh 0; + max-width: 400px; + min-width: 250px; + } + + .illustration { + width: max(35vw, 350px); + margin: 3vh 3vw 1vh 1vw; + border-radius: 30px; + } + + .approach { + font-weight: 400; + font-size: min(2vh, 30px); + width: min(80vw, 700px); + } + + .dark { + display: flex; + align-items: center; + justify-content: center; + background-color: #333; + width: 100%; + } + + .partner { + height: min(75px, 5vh); + margin: 10px 10px 10px 10px; + } + + .white { + color: white; + width: 90vw; + } + + .clicked { + border-color: #333; + } + + .main-overview { + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: center; + height: 80vh; + width: 100%; + } + + .stat-summary { + display: flex; + flex-flow: column nowrap; + justify-content: space-between; + width: 15vw; + max-width: 1800px; + height: 70vh; + background-color: hsla(0, 0.00%, 100.00%, 0.80); + border-radius: 2vw; + box-shadow: .1vw .1vw 1.5vw .1vw #2061702f; + text-align: center; + } + + .stat-container { + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + } + + .stat-graphs { + display: flex; + flex-flow: column nowrap; + justify-content: space-between; + align-items: center; + width: 60vw; + max-width: 1800px; + height: 70vh; + background-color: hsla(0, 0.00%, 100.00%, 0.80); + border-radius: 2vw; + box-shadow: .1vw .1vw 1.5vw .1vw #2061702f; + text-align: center; + } + + .stat-block { + display: flex; + flex-flow: column; + margin: 2vh 0 2vh 0; + } + + .stat { + font-size: min(2.5vh, 36px); + margin: 0; + } + + .stat-label { + font-weight: 400; + margin: 0; + color: #333; + } + + .stat-header { + font-weight: 400; + color: #333; + } + + .graph { + object-fit: contain; + border: 0.2vh solid #333; + flex-shrink: 1; + margin: 2.5vh 2.5vw; + cursor: crosshair; + width: 50vw; + max-width: fit-content; + max-height: 50vh; + } + + .graph-options { + display: flex; + justify-content: space-between; + align-items: center; + width: min(58vw, 900px); + } + + .graph-type, .graph-type-clicked { + border-radius: 1vh; + border: 0.3vh solid transparent; + padding: 5px 5px 5px 5px; + color: #333; + font-size: min(1.5vh, 30px); + transition: all 300ms ease; + } + + .graph-type:hover, .graph-type-clicked { + color: white; + background-color: #333; + } + + .stat-units { + display: flex; + justify-content: center; + } + + .count-concentration { + position: relative; + display: flex; + width: 200px; + height: 50px; + border-radius: 25px; + margin: 1.5vh 0 -0.5vh 0; + } + + .count-concentration input { + appearance: none; + width: 200px; + height: 50px; + border-radius: 25px; + background: hsla(0, 0.00%, 100.00%, 0.80); + outline: none; + cursor: pointer; + } + + .count-concentration input::before, + .count-concentration input::after { + z-index: 2; + position: absolute; + top: 56%; + transform: translateY(-50%); + font-weight: bolder; + } + + .count-concentration input::before { + content: "cells/mL"; + left: 20px; + } + + .count-concentration input::after { + content: "count"; + right: 20px; + } + + .count-concentration label{ + z-index: 1; + position: absolute; + top: 10px; + bottom: 4px; + border-radius: 20px; + } + + .count-concentration input { + transition: 0.25s; + } + + .count-concentration input:checked::before { + color: #ffffff; + transition: color 0.3s; + } + + .count-concentration input:checked::after { + color: #333; + } + + .count-concentration input:checked +label { + left: 10px; + right: 100px; + background: #333; + transition: left 0.5s, right 0.4s 0.2s; + } + + .count-concentration input:not(:checked) { + background: hsla(0, 0.00%, 100.00%, 0.80); + transition: background 0.4s; + } + + .count-concentration input:not(:checked)::before { + color: #333; + transition: color 0.5s; + } + + .count-concentration input:not(:checked)::after { + color: #ffffff; + transition: color 0.5s 0.2s; + } + + .count-concentration input:not(:checked) +label { + left: 100px; + right: 10px; + background: #333; + transition: left 0.4s 0.2s, right 0.5s, background 0.35s; + } + + .photo-type-label, .selected-photo-type { + font-style: italic; + font-size: max(1.5vh, 18px); + cursor: pointer; + justify-content: center; + margin: 1vh 1vw 1vh 1vw; + border: 2px solid #333; + border-radius: 1vw; + transition: all 300ms ease; + } + + + .photo-type-label:hover, .selected-photo-type { + background-color: #333; + color: #ffffff; + } + + + .photo-type-label::selection { + background-color: #333; + color: #ffffff; + } + \ No newline at end of file diff --git a/app/static/loading.css b/app/static/css/loading.css similarity index 87% rename from app/static/loading.css rename to app/static/css/loading.css index 9afc969..d252c55 100644 --- a/app/static/loading.css +++ b/app/static/css/loading.css @@ -4,6 +4,8 @@ left: 5vw; width: 90vw; height: 80vh; + background-color: hsla(0, 0.00%, 100.00%, 0.50); + border-radius: 2vw; z-index: 1000; } @@ -13,10 +15,11 @@ position: absolute; justify-content: space-between; align-items: center; - left: 81vw; - top: 52vh; - height: 10vw; - width: 9vw; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + height: 20vw; + width: 18vw; } .loading-text, .cancel-button { @@ -25,7 +28,7 @@ display: flex; font-family: DM Sans; text-align: center; - font-size: min(1.5vh, 1.5vw); + font-size: min(3vh, 3vw); width: 100%; } @@ -35,7 +38,7 @@ background-color: rgb(200, 200, 200); height: 10%; width: 50%; - font-size: min(1.25vh, 1.25vw); + font-size: min(2.5vh, 2.5vw); color: black; transition-property: all; transition-duration: 100ms; @@ -47,8 +50,8 @@ } #loader { - width: 3vw; - height: 3vw; + width: 6vw; + height: 6vw; margin: 0 25%; border-radius: 50%; border: .75vh solid #000000; diff --git a/app/static/css/misc.css b/app/static/css/misc.css new file mode 100644 index 0000000..f57b090 --- /dev/null +++ b/app/static/css/misc.css @@ -0,0 +1,247 @@ +a, button { + text-decoration: none; + cursor: pointer; +} +.body { + display: flex; + flex-flow: column nowrap; + height: 100vh; + width: 100vw; + margin: 0; + background-repeat: no-repeat; + background-attachment: fixed; + background-image: linear-gradient(to bottom, #124d5a, hsla(190.83333333333331, 66.67%, 21.18%, 0.25)); + background-clip: border-box; + font-family: DM Sans; + align-items: center; +} + +.header { + width: 86vw; + height: 5vh; + display: flex; + margin: 1vh 0vh 1.5vh 0vh; + background: transparent; + justify-content: space-between; + align-items: center; + } + + .logo { + width: 5vh; + height: 5vh; + background-image: url("/static/logos/algae"); + background-position: center; + background-size: 4.5vh; + background-repeat: no-repeat; + z-index: 2; + } + + nav { + position: absolute; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + background: url("menu\ algae.jpg") no-repeat center center fixed; + background-size: cover; + height: 100%; + background-color: #333; + z-index: 1; + visibility: hidden; + opacity: 0; + transition: all 300ms ease; + } + + nav ul { + display: flex; + float: right; + flex-flow: column; + justify-content: center; + text-align: right; + width: fit-content; + height: 35vh; + margin: 0 7vw 0 0; + padding: 0; + list-style: none; + } + + nav li { + text-decoration: none; + cursor: pointer; + } + + nav a { + font-size: 4vh; + font-weight: 500; + color: rgba(255, 255, 255, 0.96); + opacity: 0; + transition: opacity 200ms ease 300ms; + } + + nav h5 { + position: absolute; + bottom: 2vh; + left: 7vw; + color: rgba(255, 255, 255, 0.96); + text-align: left; + font-size: 0.83em; + font-weight: 400; + opacity: 0; + transition: all 200ms ease 300ms; + } + + .nav-info { + color: rgba(255, 255, 255, 0.96); + text-align: left; + font-size: 1em; + font-weight: 400; + } + + nav a:hover { + color: #d66d50; + } + + .nav-toggle { + display: none; +} + + .nav-toggle-label { + width: 4vh; + height: 4vh; + border-radius: 2vh; + background-image: url("hamburger.svg"); + background-position: center; + background-size: 3vh; + background-repeat: no-repeat; + transition: all 100ms ease; + z-index: 2; + cursor: pointer; + } + + .nav-toggle:checked ~ .nav-toggle-label { + background-image: url("delete\ \(white\).svg"); + } + + .nav-toggle-label:hover { + background-color: rgba(0, 0, 0, 0.164); + } + + .nav-toggle:checked ~ nav { + visibility: visible; + opacity: 1; + } + + .nav-toggle:checked ~ nav a { + opacity: 1; + } + + .nav-toggle:checked ~ nav h5 { + opacity: 1; + } + + .page { + height: 90vh; + align-items: center; + overflow-y:auto; + } + + .page::-webkit-scrollbar { + display: none; + } + + .page-name { + font-family: 'Amiri', serif; + color: rgba(255, 255, 255, 0.96); + } + +#algaeorithm { + color: black; +} + +.palette { + display: flex; + flex-flow: column nowrap; + justify-content: space-around; + align-items: center; + width: 90%; + height: 80%; + background-color: hsla(0, 0.00%, 100.00%, 0.80); + border-radius: 2vw; + box-shadow: .1vw .1vw 1.5vw .1vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); + text-align: center; +} + +.tab-nav { + --overview: 0; + --images: 0; + display: flex; + justify-content: center; + align-items: center; + height: 5vh; + margin-top: 2.5vh; + margin-bottom: 2.5vh; + border-radius: 2.5vh; + box-shadow: 0.1vw 0.1vw 1.5vw 0.1vw hsl(191deg 67% 21% / 48%); + background-color: hsla(0, 0.00%, 100.00%, 0.80); +} + +.tab, .selected-tab { + position: relative; + font-family: DM Sans; + font-size: min(2vh, 30px); + font-weight: 500; + color: #333; + margin-left: 5vw; + margin-right: 5vw; + padding: 0 10px; +} + +.tab::after, .selected-tab::after { + content: ""; + position: absolute; + background-color: #333; + height: 0.3vh; + left: 0; + bottom: -1vh; + transition: 300ms; +} + +.tab::after { + width: 0px; +} + +.tab:hover::after, .selected-tab::after { + width: 100%; +} + + +.see-images, .see-overview { + display: flex; + align-items: flex-end; + justify-content: center; + height: 5%; +} + +.next-page, .show-overview { + font-family: DM Sans; + font-size: 3vh; + display: flex; + justify-content: center; + align-items: center; +} + +.hidden { + display: none; +} + +.invisible { + visibility: hidden; +} + +.margin-five { + margin: 0 2.5%; +} + +::-webkit-scrollbar { + display: none; +} \ No newline at end of file diff --git a/app/static/css/mobile.css b/app/static/css/mobile.css new file mode 100644 index 0000000..8e81403 --- /dev/null +++ b/app/static/css/mobile.css @@ -0,0 +1,66 @@ +@media only screen and (min-width : 120px) and (max-width : 640px) { + .palette { + width: 100vw; + } + + .lab { + flex-flow: column; + height: 80vh; + } + + .photos { + width: 100vw !important; + height: 50vh !important; + } + + .add-photo { + height: 30vw !important; + width: 30vw !important; + } + + .preview-image { + height: 30vw !important; + } + + .settings { + flex-flow: row !important; + width: 100vw !important; + height: 30vh !important; + } + + .setting { + height: 30vh !important; + justify-content: space-around; + flex-grow: 1; + } + + .settings-label { + display: flex !important; + font-size: 15px !important; + } + + .types { + flex-flow: column; + } + + .photo-type { + border-radius: 30px !important; + height: 9vh; + width: auto !important; + color: transparent; + } + + .photo-type-label { + cursor: pointer; + color: transparent; + } + + .row { + flex-direction: column; + justify-content: left; + } + + .analyze-button { + width: 90vw !important; + } +} \ No newline at end of file diff --git a/app/static/css/old_index.css b/app/static/css/old_index.css new file mode 100644 index 0000000..f26a52a --- /dev/null +++ b/app/static/css/old_index.css @@ -0,0 +1,372 @@ +#time-setting { + width: 100%; +} + +.lab { + display: flex; + align-items: center; +} + +.instruction { + font-family: DM Sans; + font-size: min(3vw, 36px); + font-weight: 500; + text-align: center; + width: min(54vw, 1080px); + color: hsla(0, 0.00%, 20.00%, 1.00); +} + +.photos { + display: flex; + flex-direction: column; + flex-wrap: wrap; + flex-grow: 1; + align-items: center; + height: 80vh; + width: min(54vw, 1080px); + justify-content: flex-start; +} + +.buttons { + display: flex; + margin-top: 5vh; + justify-content: space-around; + width: min(45vw, 600px); +} + +.input-values { + height: 6vh; + border-style: solid; + border-width: 0.5px; + border-radius: 2vh; + background-color: transparent; + font-family: DM Sans; + color: hsla(0, 0.00%, 20.00%, 1.00); + font-size: min(2vh, 30px); + font-weight: 400; + width: min(17vw, 250px); + background-clip: border-box; + object-fit: fill; + text-align: center; + flex-shrink: 1; + transition: all 300ms ease; +} + +.input-values:focus { + border-color: skyblue; + outline: none; +} + +.upload-photo { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.upload-photo:hover { +background-color: hsla(0, 0.00%, 20.00%, 1.00); +color: hsla(0, 0.00%, 100.00%, 1.00); +} + +.add-photo { +display: block; +height: 13vh; +width: 18vh; +border-radius: 30px; +margin: 1vh 1vw 1vh 1vw; +background-image: url("/static/logos/plus.svg"); +background-position: 50% 50%; +background-size: 8vh; +background-repeat: no-repeat; +background-color: #333; +cursor: pointer; +transition: all 200ms ease; +} + +.add-photo:hover { +background-size: 10vh; +} + +.previews { +display: flex; +flex-wrap: wrap; +justify-content: flex-start; +align-items: flex-start; +width: calc(100% - 2vw); +max-height: calc(100% - 2vw); +margin: 1vw; +overflow-y: auto; +} + +.preview { +flex-shrink: 1; +height: 18vh; +width: auto; +margin: 1vh 1vw 1vh 1vw; +background-clip: border-box; +object-fit: contain; +position: relative; +transition: all 200ms ease; +} + +.preview-image { +height: 70%; +width: auto; +border-radius: 30px; +} + +.preview-input { +width: 1.5vw; +height: 65%; +font-size: min(1.5vh, 1.5vw); +background-color: transparent; +text-align: center; +border: .2vh solid; +border-radius: 1vh; +margin-right: 2.5%; +font-family: DM Sans; +} + +.specific-inputs { +height: 25%; +width: 100%; +display: flex; +justify-content: center; +align-items: center; +} + +.preview-time, .preview-depth { +font-size: 2vh; +font-family: DM SANS; +height: 85%; +margin-bottom: 2vh; +width: 6vw; +max-width: 20vw; +margin: 0 1%; +white-space: nowrap; +overflow: hidden; +text-overflow: clip; +flex-grow: 1; +} + +.delete-photo { +display: none; +height: 4vh; +width: 4vh; +border-radius: 2vh; +position: absolute; +top: 1.5vh; +right: 1.5vh; +background-image: url("/static/logos/delete.svg"); +background-position: center; +background-size: 2.5vh; +background-repeat: no-repeat; +background-color: rgba(255, 255, 255, 0.9); +border: none; +transition: all 200ms ease; +} + +.delete-photo:hover { + border-radius: 1vh; +} + +.preview:hover .delete-photo { +display: block; +} + +.settings { + display: flex; + flex-direction: column; + width: min(36vw, 500px); + height: 80vh; + justify-content: space-between; + border-radius: 2vw; + background-color: hsla(0, 0.00%, 100.00%, 0.50); + } + +.setting { + display: flex; + flex-direction: column; + align-items: center; + height: 25vh; +} + +.setting-name { + font-family: DM Sans; + font-size: 2vh; +} + +.title { + justify-content: center; +} + +.types { + display: flex; + width: 90%; + justify-content: space-evenly; +} + +input[type='radio'] { +-webkit-appearance: none; +-moz-appearance: none; +} + +.radio { + width: 2vh; + height: 2vh; + border: 2px solid #333; + border-radius: 50%; + box-sizing: border-box; + padding: 2px; + margin-right: 10px; + transition: all 300ms ease; +} + +.radio::after { + content: ""; + width: 100%; + height: 100%; + display: block; + background: #333; + border-radius: 50%; + transform: scale(0); + transition: transform 0.15s; +} + +input[type='radio']:checked + .radio::after { + transform: scale(1); +} + +.row { + display: flex; + align-items: center; + height: 6vh; + width: 80%; +} + +.photo-type { + width: 80%; + max-height: 20vh; + border: 10px solid transparent; + border-radius: max(2vw, 30px); + transition: all 300ms ease; +} + +.photo-type:hover, .selected-photo-type { + border: 10px solid #d66d50; +} + +.settings-label { + font-family: DM Sans; + font-size: 1.5vh; + display: inline-flex; + align-items: center; + cursor: pointer; +} + +.photo-type-label { + cursor: pointer; +} + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.settings-input { + color: white; + background-color: #333; + font-family: DM Sans; + font-weight: 500; + font-size: 1.5vh; + text-align: center; + width: min(5vw, 200px); + height: 3vh; + margin: 0 min(1vw, 10px) 0 1vw; + border: 2px solid transparent; + border-radius: 1vw; + transition: all 300ms ease; +} + +.settings-input:hover { + color: #333; + background-color: transparent; + border-color: #333; +} + +.settings-input:focus { + color: #333; + background-color: transparent; + border-color: #333; + outline: none; +} + +.switch { + --width: calc(var(--height) * 2); + --height: 2vh; + --border-radius: calc(var(--height) / 2); + display: inline-block; + cursor: pointer; + margin-left: 20px; +} + +.switch_input { + display: none; +} + +.switch_fill { + position: relative; + width: var(--width); + height: var(--height); + border-radius: var(--border-radius); + background: #333; + transition: background 0.2s; +} + +.switch_fill::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: var(--height); + height: var(--height); + background: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.25); + border-radius: var(--border-radius); + transition: transform 0.2s; +} + +.switch_input:checked ~ .switch_fill::after { + transform: translateX(var(--height)); +} + +.switch_input:checked ~ .switch_fill { + background: #d66d50; +} + +.analyze-button { + height: 5vh; + margin: 2.5vh 0; + width: min(30vw, 500px); + display: flex; + font-family: DM Sans; + font-size: 2vh; + align-items: center; + justify-content: center; + border-radius: 2vh; + background-image: url("/static/logos/analyze_button.svg"); + background-position: 90% 50%; + background-size: 2.5vh; + background-repeat: no-repeat; + background-color: rgba(255, 255, 255, 0.9); + box-shadow: .1vw .1vw 1.5vw .1vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); + cursor: pointer; + transition: all 200ms ease; +} + +.analyze-button:hover { +background-position: 92% 50%; +box-shadow: .1vw 1vw 1.5vw .5vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); +} \ No newline at end of file diff --git a/app/static/css/press.css b/app/static/css/press.css new file mode 100644 index 0000000..07be4ea --- /dev/null +++ b/app/static/css/press.css @@ -0,0 +1,37 @@ +.press { + display: flex; + flex-flow: column; + align-items: center; + padding: 15px 0 15px 0; + max-width: 550px; + width: 100%; + margin-top: 10px; + margin-bottom: 10px; + border-radius: 10px; + background-color: white; + color: #333; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + transition: background-color 100ms ease; + } + + .press:hover { + background-color: rgba(247, 249, 249); + } + + .press-title { + max-width: 468px; + flex-wrap: wrap; + } + + .press-logo { + max-width: 468px; + max-height: 50px; + flex-grow: 1; + flex-basis: 0; + } + + .press-image { + max-width: 468px; + border-radius: 10px; + margin: 1vh 0 1vh 0; + } \ No newline at end of file diff --git a/app/static/results.css b/app/static/css/results.css similarity index 77% rename from app/static/results.css rename to app/static/css/results.css index 39093d5..f9d0df5 100644 --- a/app/static/results.css +++ b/app/static/css/results.css @@ -51,24 +51,24 @@ justify-content: center; .next:hover { background-color: hsla(0, 0.00%, 20.00%, 1.00); -background-image: url("next button(white).svg"); +background-image: url("/static/logos/next_button(white).svg"); color: hsla(0, 0.00%, 20.00%, 1.00); } .last:hover { background-color: hsla(0, 0.00%, 20.00%, 1.00); -background-image: url("last button(white).svg"); +background-image: url("/static/logos/last_button(white).svg"); color: hsla(0, 0.00%, 100.00%, 1.00); } .last { margin-right: 2vw; -background-image: url("last button.svg"); +background-image: url("/static/logos/last_button.svg"); } .next { margin-left: 2vw; -background-image: url("next button.svg"); +background-image: url("/static/logos/next_button.svg"); } .image-container { @@ -82,12 +82,12 @@ background-image: url("next button.svg"); } .algae { - width: 62.5vw; - height: 60vh; - object-fit: contain; - flex-shrink: 1; - margin: 2.5vh 2.5vw; - cursor: pointer; + height: 60vh; + object-fit: contain; + flex-shrink: 1; + margin: 2.5vh 2.5vw; + cursor: pointer; + width: 62.5vw; } .show-pictures { @@ -147,4 +147,24 @@ background-color: hsla(0, 0.00%, 100.00%, 0.50); .results-unit { font-size: min(3vh, 2vw); +} + +.ghost { + visibility: hidden; +} +.download-photo { + height: 5vh; + width: 5vh; + border-radius: 2vh; + background-image: url("/static/logos/download.svg"); + background-position: center; + background-size: 2vh; + background-repeat: no-repeat; + background-color: rgba(255, 255, 255, 0.9); + border: none; + transition: all 200ms ease; +} + +.download-photo:hover { + border-radius: 1vh; } \ No newline at end of file diff --git a/app/static/stats.css b/app/static/css/stats.css similarity index 100% rename from app/static/stats.css rename to app/static/css/stats.css diff --git a/app/static/table.css b/app/static/css/table.css similarity index 100% rename from app/static/table.css rename to app/static/css/table.css diff --git a/app/static/css/tmp.css b/app/static/css/tmp.css new file mode 100644 index 0000000..e69de29 diff --git a/app/static/diatom/checkpoint b/app/static/diatom/checkpoint new file mode 100644 index 0000000..b1c8d55 --- /dev/null +++ b/app/static/diatom/checkpoint @@ -0,0 +1,8 @@ +model_checkpoint_path: "ckpt-3" +all_model_checkpoint_paths: "ckpt-1" +all_model_checkpoint_paths: "ckpt-2" +all_model_checkpoint_paths: "ckpt-3" +all_model_checkpoint_timestamps: 1644016833.8991776 +all_model_checkpoint_timestamps: 1644017846.9485729 +all_model_checkpoint_timestamps: 1644018817.8313348 +last_preserved_timestamp: 1644016831.972022 diff --git a/app/static/diatom/ckpt-3.data-00000-of-00001 b/app/static/diatom/ckpt-3.data-00000-of-00001 new file mode 100644 index 0000000..2d2a536 Binary files /dev/null and b/app/static/diatom/ckpt-3.data-00000-of-00001 differ diff --git a/app/static/diatom/ckpt-3.index b/app/static/diatom/ckpt-3.index new file mode 100644 index 0000000..fec5c6a Binary files /dev/null and b/app/static/diatom/ckpt-3.index differ diff --git a/app/static/diatom/label_map.pbtxt b/app/static/diatom/label_map.pbtxt new file mode 100644 index 0000000..d050273 --- /dev/null +++ b/app/static/diatom/label_map.pbtxt @@ -0,0 +1,4 @@ +item { + name:'cell' + id:1 +} diff --git a/app/static/diatom/pipeline.config b/app/static/diatom/pipeline.config new file mode 100644 index 0000000..b55049f --- /dev/null +++ b/app/static/diatom/pipeline.config @@ -0,0 +1,191 @@ +model { + ssd { + num_classes: 1 + image_resizer { + fixed_shape_resizer { + height: 640 + width: 640 + } + } + feature_extractor { + type: "ssd_mobilenet_v2_fpn_keras" + depth_multiplier: 1.0 + min_depth: 16 + conv_hyperparams { + regularizer { + l2_regularizer { + weight: 4e-05 + } + } + initializer { + random_normal_initializer { + mean: 0.0 + stddev: 0.01 + } + } + activation: RELU_6 + batch_norm { + decay: 0.997 + scale: true + epsilon: 0.001 + } + } + use_depthwise: true + override_base_feature_extractor_hyperparams: true + fpn { + min_level: 3 + max_level: 7 + additional_layer_depth: 128 + } + } + box_coder { + faster_rcnn_box_coder { + y_scale: 10.0 + x_scale: 10.0 + height_scale: 5.0 + width_scale: 5.0 + } + } + matcher { + argmax_matcher { + matched_threshold: 0.5 + unmatched_threshold: 0.5 + ignore_thresholds: false + negatives_lower_than_unmatched: true + force_match_for_each_row: true + use_matmul_gather: true + } + } + similarity_calculator { + iou_similarity { + } + } + box_predictor { + weight_shared_convolutional_box_predictor { + conv_hyperparams { + regularizer { + l2_regularizer { + weight: 4e-05 + } + } + initializer { + random_normal_initializer { + mean: 0.0 + stddev: 0.01 + } + } + activation: RELU_6 + batch_norm { + decay: 0.997 + scale: true + epsilon: 0.001 + } + } + depth: 128 + num_layers_before_predictor: 4 + kernel_size: 3 + class_prediction_bias_init: -4.6 + share_prediction_tower: true + use_depthwise: true + } + } + anchor_generator { + multiscale_anchor_generator { + min_level: 3 + max_level: 7 + anchor_scale: 4.0 + aspect_ratios: 1.0 + aspect_ratios: 2.0 + aspect_ratios: 0.5 + scales_per_octave: 2 + } + } + post_processing { + batch_non_max_suppression { + score_threshold: 1e-08 + iou_threshold: 0.6 + max_detections_per_class: 100 + max_total_detections: 100 + use_static_shapes: false + } + score_converter: SIGMOID + } + normalize_loss_by_num_matches: true + loss { + localization_loss { + weighted_smooth_l1 { + } + } + classification_loss { + weighted_sigmoid_focal { + gamma: 2.0 + alpha: 0.25 + } + } + classification_weight: 1.0 + localization_weight: 1.0 + } + encode_background_as_zeros: true + normalize_loc_loss_by_codesize: true + inplace_batchnorm_update: true + freeze_batchnorm: false + } +} +train_config { + batch_size: 4 + data_augmentation_options { + random_horizontal_flip { + } + } + data_augmentation_options { + random_crop_image { + min_object_covered: 0.0 + min_aspect_ratio: 0.75 + max_aspect_ratio: 3.0 + min_area: 0.75 + max_area: 1.0 + overlap_thresh: 0.0 + } + } + sync_replicas: true + optimizer { + momentum_optimizer { + learning_rate { + cosine_decay_learning_rate { + learning_rate_base: 0.08 + total_steps: 50000 + warmup_learning_rate: 0.026666 + warmup_steps: 1000 + } + } + momentum_optimizer_value: 0.9 + } + use_moving_average: false + } + fine_tune_checkpoint: "drive/MyDrive/OriginalTFODCourse/Tensorflow/workspace/pre-trained-models/ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8/checkpoint/ckpt-0" + num_steps: 50000 + startup_delay_steps: 0.0 + replicas_to_aggregate: 8 + max_number_of_boxes: 100 + unpad_groundtruth_tensors: false + fine_tune_checkpoint_type: "detection" + fine_tune_checkpoint_version: V2 +} +train_input_reader { + label_map_path: "drive/MyDrive/OriginalTFODCourse/Tensorflow/workspace/annotations/label_map.pbtxt" + tf_record_input_reader { + input_path: "drive/MyDrive/OriginalTFODCourse/Tensorflow/workspace/annotations/train.record" + } +} +eval_config { + metrics_set: "coco_detection_metrics" + use_moving_averages: false +} +eval_input_reader { + label_map_path: "drive/MyDrive/OriginalTFODCourse/Tensorflow/workspace/annotations/label_map.pbtxt" + shuffle: false + num_epochs: 1 + tf_record_input_reader { + input_path: "drive/MyDrive/OriginalTFODCourse/Tensorflow/workspace/annotations/test.record" + } +} diff --git a/app/static/files/synopsis.pdf b/app/static/files/synopsis.pdf new file mode 100644 index 0000000..68de197 Binary files /dev/null and b/app/static/files/synopsis.pdf differ diff --git a/app/static/input.css b/app/static/input.css deleted file mode 100644 index c0d1b5b..0000000 --- a/app/static/input.css +++ /dev/null @@ -1,276 +0,0 @@ -.top-instruction { - margin-top: 2vh; - height: 18vh; - width: 100%; - flex-grow: 1; - display: flex; - flex-flow: column nowrap; - justify-content: space-evenly; - align-items: center; -} - -.initially-visible { - width: 100%; - height: 50%; - display: flex; - flex-flow: column nowrap; - justify-content: center; - align-items: center; -} - -.instruction { - font-family: DM Sans; - font-weight: 300; - height: 75%; - width: 100%; - text-align: center; - flex-grow: 1; - font-size: min(5vh, 5vw); - margin: 0; - display: flex; - justify-content: center; - align-items: center; -} - -.advanced-options { - margin-top: .5vh; - height: 24%; - width: 10vw; - font-family: DM Sans; - color: hsla(0, 0.00%, 20.00%, 1.00); - font-size: min(2vh, 1.2vw); - cursor: pointer; - background-color: transparent; - transition-property: all; - transition-duration: 300ms; - transition-timing-function: ease; - text-align: center; - padding: 0; -} - -.advanced-options:hover { - text-decoration: underline; -} - -.advanced-input { - display: flex; - flex-direction: column; - justify-content: space-evenly; - align-items: center; - height: 40%; - width: 100%; -} - -.advanced-box { - display: flex; - justify-content: center; - align-items: center; - height: 40%; - width: min(65%, 80vh); - margin: 0 2.5%; - font-family: DM Sans; -} - -.inner-advanced-box { - display: flex; - justify-content: flex-start; - align-items: center; - height: 100%; - width: 55%; - margin: 0 2.5%; -} - -#all-depth-box, #time-unit-box { - justify-content: flex-end; - width: 25%; -} - -.advanced-label { - font-family: DM Sans; - font-size: min(2vh, 2vw); - margin-right: 5%; -} - -.optional-time-input, .optional-depth-input { - height: 50%; - text-align: center; - background-color: transparent; - border-radius: 1vh; - border: .1vh solid black; - font-family: DM SANS; - margin-right: 5%; - font-size: min(1.5vh, 1.5vw); -} - -.optional-time-input { - width: 50%; -} - -.optional-depth-input { - width: 25%; -} - -.upper { - display: flex; - height: 25%; - width: 100%; - flex-wrap: nowrap; - justify-content: space-around; - align-items: center; - flex-grow: 2; - margin-bottom: 20vh; -} - -.right, .left { - display: flex; - height: 100%; - justify-content: center; - align-items: center; - width: 50%; -} - -.input-values { - height: 40%; - width: 70%; - border-style: solid; - border-width: 0.5px; - border-radius: 10vh; - background-color: hsla(208, 75.69%, 7.97%, 0.00); - font-family: DM Sans; - color: hsla(0, 0.00%, 20.00%, 1.00); - font-size: min(3.5vw, 3.5vh); - font-weight: 400; - background-clip: border-box; - object-fit: fill; - text-align: center; - flex-shrink: 1; - transition-property: all; - transition-duration: 300ms; - transition-timing-function: ease; -} - -.input-values:hover { - background-color: hsla(0, 0.00%, 20.00%, 1.00); - color: hsla(0, 0.00%, 100.00%, 1.00); -} - -.upload-photo { - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; -} - - -.enter-url { - display: block; -} - -.lower { - display: flex; - flex-grow: 5; - height: 50%; - width: 100%; - align-items: center; -} - -.left-margin { - width: 10%; -} - -.previews { - display: flex; - flex-wrap: wrap; - flex-grow: 8; - justify-content: center; - align-items: center; - flex-shrink: 1; - height: 100%; - width: 80%; - overflow-y: auto; -} - -.preview { - flex-shrink: 1; - display: flex; - flex-flow: column nowrap; - height: 45%; - width: auto; - max-width: 50%; - margin: 1vw 1vh 1vw 1vh; - object-fit: contain; -} - -.preview-image-container { - width: auto; - height: 80%; -} - -.preview-image { - height: 90%; - width: auto; - border-radius: 30px; - border: .5vh solid transparent; - cursor: pointer; -} - -.preview-image:hover { - border: .5vh solid red; -} - -.specific-inputs { - height: 20%; - width: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.preview-time, .preview-depth { - font-size: 2vh; - font-family: DM SANS; - height: 100%; - width: 6vw; - max-width: 20vw; - margin: 0 1%; - white-space: nowrap; - overflow: hidden; - text-overflow: clip; - flex-grow: 1; -} - -.preview-input { - width: 2.5vw; - height: 60%; - font-size: min(1.5vh, 1.5vw); - background-color: transparent; - text-align: center; - border: .2vh solid; - border-radius: 1vh; - margin-right: 2.5%; - font-family: DM Sans; -} - -.analyze { - display: flex; - justify-content: center; - align-items: center; - width: 5%; - height: 4.5vw; - margin-right: 2.5%; - margin-left: 2.5%; -} - -.analyze-button { - width: 100%; - height: 100%; - transition-property: all; - transition-duration: 300ms; - transition-timing-function: ease; - cursor: pointer; -} - -.analyze-button:hover { - transform: translate(10%, 0px); -} - diff --git a/app/static/logos/DOEB&W.png b/app/static/logos/DOEB&W.png new file mode 100644 index 0000000..c95c7d7 Binary files /dev/null and b/app/static/logos/DOEB&W.png differ diff --git a/app/static/logos/ISBB&W.png b/app/static/logos/ISBB&W.png new file mode 100644 index 0000000..a7f891a Binary files /dev/null and b/app/static/logos/ISBB&W.png differ diff --git a/app/static/logos/NRELB&W.png b/app/static/logos/NRELB&W.png new file mode 100644 index 0000000..05a3641 Binary files /dev/null and b/app/static/logos/NRELB&W.png differ diff --git a/app/static/logos/SVCEB&W.png b/app/static/logos/SVCEB&W.png new file mode 100644 index 0000000..6a6ae56 Binary files /dev/null and b/app/static/logos/SVCEB&W.png differ diff --git a/app/static/logos/algae_menu.jpg b/app/static/logos/algae_menu.jpg new file mode 100644 index 0000000..981e1fb Binary files /dev/null and b/app/static/logos/algae_menu.jpg differ diff --git a/app/static/logos/algaefoundationB&W.png b/app/static/logos/algaefoundationB&W.png new file mode 100644 index 0000000..c3435da Binary files /dev/null and b/app/static/logos/algaefoundationB&W.png differ diff --git a/app/static/logos/algaeorithm_logo.svg b/app/static/logos/algaeorithm_logo.svg new file mode 100644 index 0000000..cbd73b2 --- /dev/null +++ b/app/static/logos/algaeorithm_logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/static/analyze button.svg b/app/static/logos/analyze_button.svg similarity index 100% rename from app/static/analyze button.svg rename to app/static/logos/analyze_button.svg diff --git a/app/static/logos/ashwin_profile.jpg b/app/static/logos/ashwin_profile.jpg new file mode 100644 index 0000000..d63e2b1 Binary files /dev/null and b/app/static/logos/ashwin_profile.jpg differ diff --git a/app/static/logos/cac_thumbnail.webp b/app/static/logos/cac_thumbnail.webp new file mode 100644 index 0000000..c939205 Binary files /dev/null and b/app/static/logos/cac_thumbnail.webp differ diff --git a/app/static/logos/chlamy_square.jpg b/app/static/logos/chlamy_square.jpg new file mode 100644 index 0000000..918b13f Binary files /dev/null and b/app/static/logos/chlamy_square.jpg differ diff --git a/app/static/logos/chlorella_square.jpg b/app/static/logos/chlorella_square.jpg new file mode 100644 index 0000000..c9dd9db Binary files /dev/null and b/app/static/logos/chlorella_square.jpg differ diff --git a/app/static/logos/delete.svg b/app/static/logos/delete.svg new file mode 100644 index 0000000..1a434ba --- /dev/null +++ b/app/static/logos/delete.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/static/logos/delete_white.svg b/app/static/logos/delete_white.svg new file mode 100644 index 0000000..d798caf --- /dev/null +++ b/app/static/logos/delete_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/static/logos/download.svg b/app/static/logos/download.svg new file mode 100644 index 0000000..cdff7bc --- /dev/null +++ b/app/static/logos/download.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/static/logos/download_white.svg b/app/static/logos/download_white.svg new file mode 100644 index 0000000..24f198c --- /dev/null +++ b/app/static/logos/download_white.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/static/logos/favicon.ico b/app/static/logos/favicon.ico new file mode 100644 index 0000000..9271af8 Binary files /dev/null and b/app/static/logos/favicon.ico differ diff --git a/app/static/logos/green_earth.svg b/app/static/logos/green_earth.svg new file mode 100644 index 0000000..aa9c3ed --- /dev/null +++ b/app/static/logos/green_earth.svg @@ -0,0 +1 @@ +Asset 3av \ No newline at end of file diff --git a/app/static/logos/hamburger.svg b/app/static/logos/hamburger.svg new file mode 100644 index 0000000..07d81ae --- /dev/null +++ b/app/static/logos/hamburger.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/static/logos/jake_profile.jpg b/app/static/logos/jake_profile.jpg new file mode 100644 index 0000000..2b5c9ce Binary files /dev/null and b/app/static/logos/jake_profile.jpg differ diff --git a/app/static/last button(white).svg b/app/static/logos/last_button(white).svg similarity index 100% rename from app/static/last button(white).svg rename to app/static/logos/last_button(white).svg diff --git a/app/static/last button.svg b/app/static/logos/last_button.svg similarity index 100% rename from app/static/last button.svg rename to app/static/logos/last_button.svg diff --git a/app/static/next button(white).svg b/app/static/logos/next_button(white).svg similarity index 100% rename from app/static/next button(white).svg rename to app/static/logos/next_button(white).svg diff --git a/app/static/next button.svg b/app/static/logos/next_button.svg similarity index 100% rename from app/static/next button.svg rename to app/static/logos/next_button.svg diff --git a/app/static/logos/plus.svg b/app/static/logos/plus.svg new file mode 100644 index 0000000..cb22fda --- /dev/null +++ b/app/static/logos/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/static/logos/portfolio-showcase.gif b/app/static/logos/portfolio-showcase.gif new file mode 100644 index 0000000..c4c48af Binary files /dev/null and b/app/static/logos/portfolio-showcase.gif differ diff --git a/app/static/logos/rohan_profile.jpg b/app/static/logos/rohan_profile.jpg new file mode 100644 index 0000000..58d3df7 Binary files /dev/null and b/app/static/logos/rohan_profile.jpg differ diff --git a/app/static/logos/tricornutum_square.jpg b/app/static/logos/tricornutum_square.jpg new file mode 100644 index 0000000..1232aec Binary files /dev/null and b/app/static/logos/tricornutum_square.jpg differ diff --git a/app/static/logos/tutorial_thumbnail.webp b/app/static/logos/tutorial_thumbnail.webp new file mode 100644 index 0000000..9892645 Binary files /dev/null and b/app/static/logos/tutorial_thumbnail.webp differ diff --git a/app/static/misc.css b/app/static/misc.css deleted file mode 100644 index 74c6022..0000000 --- a/app/static/misc.css +++ /dev/null @@ -1,124 +0,0 @@ -a, button { - text-decoration: none; - cursor: pointer; -} -.body { - display: flex; - flex-flow: column nowrap; - align-items: center; - height: 100vh; - width: 100vw; - margin: 0; - background-repeat: no-repeat; - background-attachment: fixed; - background-image: linear-gradient(to bottom, #124d5a, hsla(190.83333333333331, 66.67%, 21.18%, 0.25)); - background-clip: border-box; -} -.header { - height: 8%; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - margin-bottom: 2vh; - background-color: white; -} -.logo { - display: block; - height: 100%; - font-family: DM Sans; - font-size: 6vh; - text-align: center; -} - -#algaeorithm { - color: black; -} - -.palette { - display: flex; - flex-flow: column nowrap; - justify-content: space-around; - align-items: center; - width: 90%; - height: 80%; - background-color: hsla(0, 0.00%, 100.00%, 0.80); - border-radius: 2vw; - box-shadow: .1vw .1vw 1.5vw .1vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); - text-align: center; -} - -.tab-nav { - --overview: 0; - --images: 0; - display: flex; - justify-content: center; - align-items: center; - height: 5vh; - margin-top: 2.5vh; - margin-bottom: 2.5vh; - border-radius: 2.5vh; - box-shadow: .1vw .1vw 1.5vw .1vw hsla(190.83333333333331, 66.67%, 21.18%, 0.48); - background-color: hsla(0, 0.00%, 100.00%, 0.80); - } - -.tab, .selected-tab { - position: relative; - font-family: DM Sans; - font-size: min(2vh, 30px); - font-weight: 500; - color: #333; - margin-left: 5vw; - margin-right: 5vw; - padding: 0 10px; -} - -.tab::after, .selected-tab::after { - content: ""; - position: absolute; - background-color: #333; - height: 0.3vh; - left: 0; - bottom: -1vh; - transition: 300ms; -} - -.tab::after { - width: 0px; -} - -.tab:hover::after, .selected-tab::after { - width: 100%; -} - - -.see-images, .see-overview { - display: flex; - align-items: flex-end; - justify-content: center; - height: 5%; -} - -.next-page, .show-overview { - font-family: DM Sans; - font-size: 3vh; - display: flex; - justify-content: center; - align-items: center; -} - -.hidden { - display: none; -} - -.invisible { - visibility: hidden; -} - -.margin-five { - margin: 0 2.5%; -} - -::-webkit-scrollbar { - display: none; -} \ No newline at end of file diff --git a/app/static/script.js b/app/static/script.js index d621806..7a5511b 100644 --- a/app/static/script.js +++ b/app/static/script.js @@ -1,127 +1,155 @@ let imageIndex = 0; let imagesList = []; let fileList = document.getElementById("fileList"); -let urlList = document.getElementById("urlList"); let fileInput = new FormData(); let urlToSubmit = []; let parsedResponse = {}; -const changeAdvanced = () => { - if (document.getElementById("advanced-options").innerHTML == "Advanced") { - document.getElementById("advanced-input").setAttribute("class", "advanced-input"); - document.getElementById("advanced-options").innerHTML = "Hide Advanced"; +const checkBox = (boxId) => { + document.getElementById(boxId).checked = !document.getElementById(boxId).checked; +} + +const checkRadio = (radioId) => { + document.getElementById(radioId).checked = true; + if (radioId == "consistent" || radioId == "varied") { + changeDepth(); + } +} + +const changeImageType = (imageType) => { + checkRadio(imageType); + document.getElementById("img-"+imageType).setAttribute("class", "selected-photo-type photo-type-label row"); + if (imageType == "clear") { + document.getElementById("img-"+"hemocytometer").setAttribute("class", "photo-type-label row"); + document.getElementById("img-"+"chlorella").setAttribute("class", "photo-type-label row"); + } else if (imageType=="chlorella") { + document.getElementById("img-"+"clear").setAttribute("class", "photo-type-label row"); + document.getElementById("img-"+"hemocytometer").setAttribute("class", "photo-type-label row"); } else { - document.getElementById("advanced-input").setAttribute("class", "hidden"); - document.getElementById("advanced-options").innerHTML = "Advanced"; + document.getElementById("img-"+"clear").setAttribute("class", "photo-type-label row"); + document.getElementById("img-"+"chlorella").setAttribute("class", "photo-type-label row"); } } -const updateTimePreviews = () => { - for (let previewElement of document.getElementById("previews").children) { - if (previewElement.getAttribute("id") == "sample-preview") { - previewElement.children[0].setAttribute("class", "specific-inputs"); - continue; +const getRadioValue = (radioName) => { + for (let radioButton of document.getElementsByName(radioName)) { + if (radioButton.checked) { + return radioButton.value; } - previewElement.children[1].setAttribute("class", "specific-inputs"); - previewElement.children[1].children[0].setAttribute("class", "preview-time"); - let currentVal = previewElement.children[1].children[0].children[0].value; - previewElement.children[1].children[0].innerHTML = previewElement.children[1].children[0].children[0].outerHTML + document.getElementById("time-unit").value; - previewElement.children[1].children[0].children[0].value = currentVal; } } +const isConsistentDepth = () => { + let depthSensitive = getRadioValue("depth-sensitive") == "consistent"; + return depthSensitive; +} + +const changeInterval = () => { + if (document.getElementById("interval").checked) { + document.getElementById("interval-label").setAttribute("class", "settings-label"); + } else { + document.getElementById("interval-label").setAttribute("class", "hidden"); + } + changeTime(); +} + +const changeIntervalValue = () => { + labelElement = document.getElementById("interval-label") + labelElement.innerHTML = labelElement.children[0].outerHTML + " " + document.getElementById("time-units").value; +} + +const updateDepthTimeClass = () => { + changeTime(); + +} + const changeTime = () => { - if (document.getElementById("time-sensitive").checked) { - document.getElementById("time-unit-box").setAttribute("class", "inner-advanced-box"); - updateTimePreviews(); + document.getElementById("time-setting").setAttribute("class", "setting"); + if (document.getElementById("time-sensitive").checked && !document.getElementById("interval").checked) { + document.getElementById("time-heading").setAttribute("class", "advanced orange") + for (let previewElement of document.getElementById("previews").children) { + if (previewElement.tagName.toLowerCase() != "div") { + continue; + } + previewElement.children[1].setAttribute("class", "specific-inputs"); + if (isConsistentDepth()) { + previewElement.children[1].children[0].setAttribute("class", "preview-input orange full") + } else { + previewElement.children[1].children[0].setAttribute("class", "preview-input orange half-left") + } + + } } else { - document.getElementById("time-unit-box").setAttribute("class", "invisible margin-five"); + document.getElementById("time-heading").setAttribute("class", "setting-name") + if (!document.getElementById("time-sensitive").checked) { + document.getElementById("time-setting").setAttribute("class", "hidden"); + } for (let previewElement of document.getElementById("previews").children) { - if (previewElement.getAttribute("id") == "sample-preview") { - if (document.getElementById("depth-sensitive").checked) { - previewElement.children[0].setAttribute("class", "hidden"); - } + if (previewElement.tagName.toLowerCase() != "div") { continue; } - if (document.getElementById("depth-sensitive").checked) { + previewElement.children[1].children[0].setAttribute("class", "hidden"); + if (isConsistentDepth()) { previewElement.children[1].setAttribute("class", "hidden"); } - previewElement.children[1].children[0].setAttribute("class", "hidden"); - let currentVal = previewElement.children[1].children[0].children[0].value; - previewElement.children[1].children[0].innerHTML = previewElement.children[1].children[0].children[0].outerHTML; - previewElement.children[1].children[0].children[0].value = currentVal; + else { + previewElement.children[1].children[1].setAttribute("class", "preview-input green full"); + } } } + changeDepth(); } const changeDepth = () => { - if (document.getElementById("depth-sensitive").checked) { + if (!isConsistentDepth()) { + document.getElementById("depth-heading").setAttribute("class", "advanced green") + document.getElementById("default-label").setAttribute("class", "settings-label"); + document.getElementById("all-depth-label").setAttribute("class", "hidden"); for (let previewElement of document.getElementById("previews").children) { - if (previewElement.getAttribute("id") == "sample-preview") { - if (!document.getElementById("time-sensitive").checked) { - previewElement.children[0].setAttribute("class", "hidden"); - } + if (previewElement.tagName.toLowerCase() != "div") { continue; } - if (!document.getElementById("time-sensitive").checked) { - previewElement.children[1].setAttribute("class", "hidden"); + previewElement.children[1].setAttribute("class", "specific-inputs"); + if (document.getElementById("time-sensitive").checked && !document.getElementById("interval").checked) { + previewElement.children[1].children[1].setAttribute("class", "preview-input green half-right"); + } else { + previewElement.children[1].children[1].setAttribute("class", "preview-input green full"); } - previewElement.children[1].children[1].setAttribute("class", "hidden"); } } else { + document.getElementById("depth-heading").setAttribute("class", "setting-name") + document.getElementById("default-label").setAttribute("class", "hidden"); + document.getElementById("all-depth-label").setAttribute("class", "settings-label"); for (let previewElement of document.getElementById("previews").children) { - if (previewElement.getAttribute("id") == "sample-preview") { - previewElement.children[0].setAttribute("class", "specific-inputs"); + if (previewElement.tagName.toLowerCase() != "div") { continue; } - previewElement.children[1].setAttribute("class", "specific-inputs"); - previewElement.children[1].children[1].setAttribute("class", "preview-depth"); + previewElement.children[1].children[1].setAttribute("class", "hidden"); + if (!document.getElementById("time-sensitive").checked || document.getElementById("interval").checked) { + previewElement.children[1].setAttribute("class", "hidden"); + } else { + previewElement.children[1].children[0].setAttribute("class", "preview-input orange full"); + } } } + changeTime(); } -const createPreview = (imageName, imageType, imageSrc) => { +const createPreview = (imageName, imageSrc, imageType="file") => { let previewDiv = document.createElement("div"); previewDiv.setAttribute("class", "preview"); previewDiv.setAttribute("id", "img-"+imageName); - let imageContainer = document.createElement("div"); - imageContainer.setAttribute("class", "preview-image-container"); let previewImage = document.createElement("img"); previewImage.setAttribute("class", "preview-image"); previewImage.src = imageSrc; previewImage.title = imageName; - previewImage.setAttribute("onclick", "deleteImage('" + imageType + "', '" + imageName + "')"); - imageContainer.appendChild(previewImage); - previewDiv.appendChild(imageContainer); - previewDiv.innerHTML += document.getElementById("sample-preview").innerHTML; - if (document.getElementById("time-sensitive").checked) { - previewDiv.children[1].children[0].setAttribute("class", "preview-time"); - previewDiv.children[1].children[0].innerHTML += " " + document.getElementById("time-unit").value; - } - if (!document.getElementById("depth-sensitive").checked) { - previewDiv.children[1].children[1].setAttribute("class", "preview-depth"); - } + previewDiv.appendChild(previewImage); + previewDiv.innerHTML += document.getElementById("sample-preview").children[1].outerHTML; + let deleteButton = document.createElement("button"); + deleteButton.setAttribute("class", "delete-photo"); + deleteButton.setAttribute("onclick", "deleteImage('" + imageName + "', '" + imageType + "')"); + previewDiv.appendChild(deleteButton); document.getElementById("previews").appendChild(previewDiv); - document.getElementById("lower").setAttribute("class", "lower"); - document.getElementById("upper").style.setProperty("margin-bottom", "0vh"); -} - - -const addURL = () => { - if (!document.getElementById("urlInput").value){ - alert("Please enter a URL"); - } - else { - let givenURL = document.getElementById("urlInput").value; - if (urlToSubmit.includes(givenURL)) { - alert("Please enter a unique URL"); - document.getElementById("urlInput").value = ""; - return; - } - urlToSubmit.push(givenURL); - createPreview(givenURL, 'url', givenURL); - document.getElementById("urlInput").value = ""; - } } const addFileToList = () => { @@ -136,12 +164,13 @@ const addFileToList = () => { continue; } fileInput.append(newFile.name, newFile); - createPreview(newFile.name, "file", URL.createObjectURL(newFile)); + createPreview(newFile.name, URL.createObjectURL(newFile)); } } + document.getElementById("analyze-button").setAttribute("class", "analyze-button"); } -const deleteImage = (imageType, imageName) => { +const deleteImage = (imageName, imageType="file") => { let imageElement = document.getElementById("img-"+imageName); imageElement.parentElement.removeChild(imageElement); if (imageType=="file") { @@ -150,20 +179,17 @@ const deleteImage = (imageType, imageName) => { else { urlToSubmit.splice(urlToSubmit.indexOf(imageName), 1); } - if (document.getElementById("previews").children.length == 1) { - document.getElementById("lower").setAttribute("class", "hidden"); - document.getElementById("upper").style.setProperty("margin-bottom", "20vh"); + if (document.getElementById("previews").children.length == 3) { + document.getElementById("analyze-button").setAttribute("class", "hidden"); } } -const getFilename = (fullName, method, nameType) => { - let filename = ""; +const getFilename = (fullName, nameType) => { if (nameType=="url") { - filename = fullName.slice(fullName.lastIndexOf("/")+1); + return fullName.slice(fullName.lastIndexOf("/")+1); } else { - filename = fullName; + return fullName; } - return filename.slice(0, filename.lastIndexOf(".")) + method + filename.slice(filename.lastIndexOf(".")); } const changePalette = (elementId) => { @@ -179,63 +205,58 @@ const changePalette = (elementId) => { } const changeImage = (change) => { - displayImage(imageIndex + change, "Outlines"); + displayImage(imageIndex + change, "output"); } const clickDownload = () => { document.getElementById("download-link").click(); } -const setImage = (index, method, type) => { +const setImage = (index, method, dataType) => { let resultsImage = document.getElementById("resultsImage"); - resultsImage.setAttribute("src", "data:image/jpeg;base64,"+imagesList[index][0][method.toLowerCase()]); - resultsImage.setAttribute("title", "Download "+imagesList[index][1]); + [imageName, imageOutput, dataType] = imagesList[index]; + resultsImage.setAttribute("src", imageOutput[method]); + resultsImage.setAttribute("class", "algae"); let download_link = document.getElementById("download-link"); - download_link.setAttribute("href", "data:image/jpeg;base64,"+imagesList[index][0][method.toLowerCase()]); - download_link.setAttribute("download", getFilename(imagesList[index][1], method, type)); + download_link.setAttribute("href", imageOutput[method]); + let imageFilename = getFilename(imageName, dataType); + let downloadName = imageFilename.slice(0, imageFilename.lastIndexOf(".")) + "_" + method + imageFilename.slice(imageFilename.lastIndexOf(".")); + download_link.setAttribute("download", downloadName); + document.getElementById("download-anchor").setAttribute("title", "Download "+ downloadName); + resultsImage.setAttribute("title", downloadName); } -const displayImage = (imagesIndex, method) => { +const displayImage = (newIndex, method) => { document.getElementById("show-images").setAttribute("class", "selected-tab"); document.getElementById("show-overview").setAttribute("class", "tab"); document.getElementById("show-table").setAttribute("class", "tab"); - imageIndex = imagesIndex; + imageIndex = newIndex; changePalette("resultsInfo"); - let count = imagesList[imagesIndex][0]["count"]; - let location = imagesList[imagesIndex][1]; - let type = imagesList[imagesIndex][2]; - let resultsElement = document.getElementById("visual"); - let i = 0; - while (i < resultsElement.childNodes.length) { - if (resultsElement.childNodes[i].nodeName.toLowerCase() == "a") { - resultsElement.childNodes[i].remove(); - } - else { - i++; - } - } + let [imageName, imageOutput, dataType] = imagesList[imageIndex]; document.getElementById("img-nav").setAttribute("class", "hidden"); document.getElementById("lastImage").setAttribute("class", "invisible"); document.getElementById("nextImage").setAttribute("class", "invisible"); document.getElementById("show-pictures").innerHTML = ""; document.getElementById("image-number").innerHTML = ""; - document.getElementById("resultsCount").innerHTML = count; - document.getElementById("resultsConcentration").innerHTML = imagesList[imagesIndex][0]["concentration"]; + document.getElementById("resultsCount").innerHTML = imageOutput["count"]; + document.getElementById("resultsConcentration").innerHTML = imageOutput["concentration"] - if (count.includes("N/A")) { + if (imageOutput["count"].includes("N/A")) { document.getElementById("resultsImage").removeAttribute("src"); - if (Object.keys(imagesList[imagesIndex][0]).length > 1) { - setImage(imagesIndex, "Image", type); + if (Object.keys(imageOutput).length > 2) { + setImage(newIndex, "image", dataType); } } else { document.getElementById("resultsCount").innerHTML += " cells"; document.getElementById("resultsConcentration").innerHTML += " cells / mL"; - let methods = ["Image", "Outlines", "Circles"]; - setImage(imagesIndex, method, type); - for (let i = 0; i < 3; i++) { + let methods = ["image", "output"]; + let silentAnchor = document.createElement("a"); + silentAnchor.setAttribute("class", "download-photo ghost"); + document.getElementById("show-pictures").appendChild(silentAnchor); + for (let i = 0; i < 2; i++) { let anchor = document.createElement("a"); - anchor.setAttribute("onclick", `displayImage(${imagesIndex}, '${methods[i]}')`); + anchor.setAttribute("onclick", `displayImage(${newIndex}, '${methods[i]}')`); anchor.innerHTML = methods[i]; if (methods[i]==method) { anchor.setAttribute("class", "image-type clicked"); @@ -244,38 +265,66 @@ const displayImage = (imagesIndex, method) => { } document.getElementById("show-pictures").appendChild(anchor); } + let downloadAnchor = document.createElement("a"); + downloadAnchor.setAttribute("class", "download-photo"); + downloadAnchor.setAttribute("onclick", "clickDownload()"); + downloadAnchor.setAttribute("id", "download-anchor"); + document.getElementById("show-pictures").appendChild(downloadAnchor); + setImage(newIndex, method, dataType); } let navbar = document.getElementById("img-nav"); navbar.setAttribute("class", "img-nav"); - if (imagesIndex) { + if (newIndex) { let anchor = document.getElementById("lastImage"); anchor.setAttribute("onclick", "changeImage(-1)"); anchor.setAttribute("class", "last"); } - document.getElementById("image-number").innerHTML = (imagesIndex + 1).toString() + " of " + imagesList.length.toString(); - if (imagesIndex < imagesList.length - 1) { + document.getElementById("image-number").innerHTML = (newIndex + 1).toString() + " of " + imagesList.length.toString(); + if (newIndex < imagesList.length - 1) { let anchor = document.getElementById("nextImage"); anchor.setAttribute("onclick", "changeImage(1)"); anchor.setAttribute("class", "next"); } } +//const setGraph = (selectedGraph, metric) => { +// let graphList = Object.keys(parsedResponse["graphs"][metric]); +// document.getElementById(metric.toLowerCase()+"s-graph").src = parsedResponse["graphs"][metric][selectedGraph]; +// let changeGraph = document.getElementById(metric.toLowerCase()+"s-change"); +// changeGraph.innerHTML = ""; +// for (let graphType of graphList) { +// let graphAnchor = document.createElement("a"); +// graphAnchor.innerHTML = graphType; +// graphAnchor.setAttribute("onclick", "setGraph('" + graphType + "', '" + metric + "')"); +// if (graphType==selectedGraph) { +// graphAnchor.setAttribute("class", "graph-button clicked"); +// } else { +// graphAnchor.setAttribute("class", "graph-button"); +// } +// changeGraph.appendChild(graphAnchor); +// } +//} + const setGraph = (selectedGraph, metric) => { let graphList = Object.keys(parsedResponse["graphs"][metric]); - document.getElementById(metric.toLowerCase()+"s-graph").src = "data:image/jpeg;base64,"+parsedResponse["graphs"][metric][selectedGraph]; + document.getElementById(metric.toLowerCase()+"s-graph").src = parsedResponse["graphs"][metric][selectedGraph]; let changeGraph = document.getElementById(metric.toLowerCase()+"s-change"); - changeGraph.innerHTML = ""; + changeGraph.innerHTML = ""; for (let graphType of graphList) { let graphAnchor = document.createElement("a"); graphAnchor.innerHTML = graphType; graphAnchor.setAttribute("onclick", "setGraph('" + graphType + "', '" + metric + "')"); if (graphType==selectedGraph) { - graphAnchor.setAttribute("class", "graph-button clicked"); + graphAnchor.setAttribute("class", "graph-type-clicked"); } else { - graphAnchor.setAttribute("class", "graph-button"); + graphAnchor.setAttribute("class", "graph-type"); } changeGraph.appendChild(graphAnchor); } + changeGraph.innerHTML += ""; + let download_link = document.getElementById(metric+"s-graph-download"); + download_link.setAttribute("href", parsedResponse["graphs"][metric][selectedGraph]); + download_link.setAttribute("download", selectedGraph+".jpg"); } const setTable = () => { @@ -292,55 +341,84 @@ const setOverview = () => { document.getElementById("show-images").setAttribute("class", "tab"); document.getElementById("show-table").setAttribute("class", "tab"); if (parsedResponse["stats"] == "No data available") { - displayImage(0, "Outlines"); + displayImage(0, "output"); document.getElementById("tab-nav").setAttribute("class", "hidden"); } else { - document.getElementById("counts-stats").innerHTML = ""; - document.getElementById("concentrations-stats").innerHTML = ""; let units = {"Count": " cells", "Concentration": " cells / mL"}; + let shorthands = {"Mean":"mean", "Range":"range", "Standard Deviation":"stddev"}; for (let metric of ["Count", "Concentration"]) { - let sectionDiv = document.getElementById(metric.toLowerCase()+"s-stats"); for (let stat of ["Mean", "Range", "Standard Deviation"]) { - let statSection = document.createElement("div"); - statSection.setAttribute("class", "stats-section"); - statSection.innerHTML = stat + ": " + parsedResponse["stats"][metric][stat]; - sectionDiv.appendChild(statSection); + let statSection = document.getElementById(metric.toLowerCase()+"s-"+shorthands[stat]); + statSection.innerHTML = parsedResponse["stats"][metric][stat]; } - let medianBox = document.createElement("div"); - medianBox.setAttribute("class", "stats-section"); - let iqrBox = document.createElement("div"); - iqrBox.setAttribute("class", "stats-section"); + let medianBox = document.getElementById(metric.toLowerCase()+"s-median"); + let iqrBox = document.getElementById(metric.toLowerCase()+"s-iqr"); iqrList = parsedResponse["stats"][metric]["iqr"] - medianBox.innerHTML = "Median: " + iqrList[1]; - iqrBox.innerHTML = "Interquartile Range: " + iqrList[2] + " - " + iqrList[0]; - sectionDiv.appendChild(medianBox); - sectionDiv.appendChild(iqrBox); + medianBox.innerHTML = iqrList[1]; + iqrBox.innerHTML = iqrList[2] + " - " + iqrList[0]; let graphs = Object.keys(parsedResponse["graphs"]["Count"]); setGraph(graphs[0], metric); } } } -const addInformation = (csv_rows) => { +//const setOverview = () => { +// changePalette("overview"); +// document.getElementById("tab-nav").setAttribute("class", "tab-nav"); +// document.getElementById("show-overview").setAttribute("class", "selected-tab"); +// document.getElementById("show-images").setAttribute("class", "tab"); +// document.getElementById("show-table").setAttribute("class", "tab"); +// if (parsedResponse["stats"] == "No data available") { +// displayImage(0, "output"); +// document.getElementById("tab-nav").setAttribute("class", "hidden"); +// } else { +// document.getElementById("counts-stats").innerHTML = ""; +// document.getElementById("concentrations-stats").innerHTML = ""; +// let units = {"Count": " cells", "Concentration": " cells / mL"}; +// for (let metric of ["Count", "Concentration"]) { +// let sectionDiv = document.getElementById(metric.toLowerCase()+"s-stats"); +// for (let stat of ["Mean", "Range", "Standard Deviation"]) { +// let statSection = document.createElement("div"); +// statSection.setAttribute("class", "stats-section"); +// statSection.innerHTML = stat + ": " + parsedResponse["stats"][metric][stat]; +// sectionDiv.appendChild(statSection); +// } +// let medianBox = document.createElement("div"); +// medianBox.setAttribute("class", "stats-section"); +// let iqrBox = document.createElement("div"); +// iqrBox.setAttribute("class", "stats-section"); +// iqrList = parsedResponse["stats"][metric]["iqr"] +// medianBox.innerHTML = "Median: " + iqrList[1]; +// iqrBox.innerHTML = "Interquartile Range: " + iqrList[2] + " - " + iqrList[0]; +// sectionDiv.appendChild(medianBox); +// sectionDiv.appendChild(iqrBox); +// let graphs = Object.keys(parsedResponse["graphs"]["Count"]); +// setGraph(graphs[0], metric); +// } +// } +//} + +const addInformation = () => { + let csvRows = parsedResponse["csv"] let table = document.getElementById("csv-table"); let thead = document.createElement("thead"); - for (let header of csv_rows["header"]) { + for (let header of csvRows["header"]) { let td = document.createElement("td"); td.innerHTML = header; thead.appendChild(td) } table.appendChild(thead); - for (let object of [[parsedResponse["file_counts"], "file"], [parsedResponse["url_counts"], "url"]]) { - for (let key of Object.keys(object[0])) { - imagesList.push([object[0][key], key, object[1]]); + for (let [data, dataType] of [[parsedResponse["file_counts"], "file"], [parsedResponse["url_counts"], "url"]]) { + for (const [imageName, imageOutput] of Object.entries(data)) { + imagesList.push([imageName, imageOutput, dataType]); let linkIndex = imagesList.length - 1; let tr = document.createElement("tr"); - tr.setAttribute("onclick", "displayImage("+linkIndex+", 'Outlines')"); - tr.setAttribute("title", key); - for (let index in csv_rows[key]) { + tr.setAttribute("onclick", "displayImage("+linkIndex+", 'output')"); + tr.setAttribute("title", imageName); + for (let columnNum in csvRows[imageName]) { let td = document.createElement("td"); - td.setAttribute("class", "data-"+index.toString()); - td.innerHTML = csv_rows[key][index]; + td.setAttribute("class", "data-"+columnNum.toString()); + td.innerHTML = csvRows[imageName][columnNum]; tr.appendChild(td); } table.appendChild(tr); @@ -354,7 +432,7 @@ const checkIfNumber = (value) => { return false; } try { - parseInt(value); + parseFloat(value); } catch (error) { return false; } @@ -362,36 +440,57 @@ const checkIfNumber = (value) => { } const checkSensitives = () => { + imageColor = 2; + if (document.getElementById("hemocytometer").checked) { + imageColor = 0; + } + fileInput.append("color", imageColor); if (document.getElementById("time-sensitive").checked) { if (!document.getElementById("time-unit").value) { alert("Please enter a valid unit for time sensitive data"); return false; } - for (let previewElement of document.getElementById("previews").children) { - if (previewElement.id == "sample-preview") { - continue; + if (document.getElementById("interval").checked) { + if (!checkIfNumber(document.getElementById("time-interval").value)) { + alert("Please enter a valid interval"); + return false; + } else { + let intervalDay = 0; + for (let previewElement of document.getElementById("previews").children) { + if (previewElement.id == "sample-preview" || previewElement.tagName.toLowerCase() != "div") { + continue; + } + fileInput.append("time-"+previewElement.children[0].title, intervalDay.toString()); + intervalDay += parseFloat(document.getElementById("time-interval").value); + } } - if (checkIfNumber(previewElement.children[1].children[0].children[0].value)) { - fileInput.append("time-"+previewElement.children[0].children[0].title, previewElement.children[1].children[0].children[0].value); + } else { + for (let previewElement of document.getElementById("previews").children) { + if (previewElement.id == "sample-preview" || previewElement.tagName.toLowerCase() != "div") { + continue; + } + if (checkIfNumber(previewElement.children[1].children[0].value)) { + fileInput.append("time-"+previewElement.children[0].title, previewElement.children[1].children[0].value); + } } } fileInput.append("time-unit", document.getElementById("time-unit").value); } - if (!document.getElementById("depth-sensitive").checked) { + if (!isConsistentDepth()) { for (let previewElement of document.getElementById("previews").children) { - if (previewElement.id == "sample-preview") { + if (previewElement.id == "sample-preview" || previewElement.tagName.toLowerCase() != "div") { continue; } - if (checkIfNumber(previewElement.children[1].children[1].children[0].value)) { - fileInput.append("depth-"+previewElement.children[0].children[0].title, previewElement.children[1].children[1].children[0].value); + if (checkIfNumber(previewElement.children[1].children[1].value)) { + fileInput.append("depth-"+previewElement.children[0].title, previewElement.children[1].children[1].value); } else { - if (!checkIfNumber(document.getElementById("all-depth").value)) { + if (!checkIfNumber(document.getElementById("default-depth").value)) { alert("Please enter a depth for each picture or a valid default depth"); return false; } else { - fileInput.append("depth-"+previewElement.children[0].children[0].title, document.getElementById("all-depth").value); + fileInput.append("depth-"+previewElement.children[0].title, document.getElementById("default-depth").value); } } } @@ -409,13 +508,24 @@ const checkSensitives = () => { } const cancelRequest = () => { - document.getElementById("analyze-link").setAttribute("class", "analyze-button"); + document.getElementById("analyze-button").setAttribute("class", "analyze-button"); document.getElementById("loader-wrapper").setAttribute("class", "hidden"); } +const updateOverview = () => { + if (document.getElementById("switcher").checked) { + document.getElementById("counts-overview").setAttribute("class", "hidden"); + document.getElementById("concentrations-overview").setAttribute("class", "main-overview"); + } else { + document.getElementById("concentrations-overview").setAttribute("class", "hidden"); + document.getElementById("counts-overview").setAttribute("class", "main-overview"); + } + +} + const loadInformation = () => { if (!document.getElementById("previews").children.length) { - alert("Please add a file or url"); + alert("No images added"); return; } let request = new XMLHttpRequest(); @@ -424,30 +534,34 @@ const loadInformation = () => { imagesIndex = 0; imagesList = []; parsedResponse = JSON.parse(request.response); - addInformation(parsedResponse["csv"]); + addInformation(); document.getElementById("loader-wrapper").setAttribute("class", "hidden"); - setOverview(); + setOverview(parsedResponse); } } if (!checkSensitives()) { return; } fileInput.append("url", JSON.stringify(urlToSubmit)); + if (document.getElementById("clear").checked) { + fileInput.append("cell_type", "chlamy"); + } else if (document.getElementById("hemocytometer").checked) { + fileInput.append("cell_type", "diatom"); + } else { + fileInput.append("cell_type", "chlorella"); + } request.open("POST", "/"); request.send(fileInput); - document.getElementById("analyze-link").setAttribute("class", "hidden"); + document.getElementById("analyze-button").setAttribute("class", "hidden"); document.getElementById("loader-wrapper").removeAttribute("class"); } document.addEventListener("keydown", function(event) { - if (event.key === "Enter") { - if (event.ctrlKey) { - loadInformation(); - } else { - addURL(); - } + if (event.key === "Enter" && event.ctrlKey) { + loadInformation(); } }); document.getElementById("staging").addEventListener("change", addFileToList); document.getElementById("time-sensitive").addEventListener("change", changeTime); -document.getElementById("depth-sensitive").addEventListener("change", changeDepth); -document.getElementById("time-unit").addEventListener("keyup", updateTimePreviews); \ No newline at end of file +document.getElementById("interval").addEventListener("change", changeInterval); +document.getElementById("time-unit").addEventListener("keyup", changeIntervalValue); +document.getElementById("switcher").addEventListener("change", updateOverview); \ No newline at end of file diff --git a/app/static/temp.js b/app/static/temp.js new file mode 100644 index 0000000..686a968 --- /dev/null +++ b/app/static/temp.js @@ -0,0 +1,50 @@ +const setOverview = () => { + changePalette("overview"); + document.getElementById("tab-nav").setAttribute("class", "tab-nav"); + document.getElementById("show-overview").setAttribute("class", "selected-tab"); + document.getElementById("show-images").setAttribute("class", "tab"); + document.getElementById("show-table").setAttribute("class", "tab"); + if (parsedResponse["stats"] == "No data available") { + displayImage(0, "output"); + document.getElementById("tab-nav").setAttribute("class", "hidden"); + } else { + document.getElementById("counts-stats").innerHTML = ""; + document.getElementById("concentrations-stats").innerHTML = ""; + let units = {"Count": " cells", "Concentration": " cells / mL"}; + let shorthands = {"Mean":"mean", "Range":"range", "Standard Deviation":"stddev"}; + for (let metric of ["Count", "Concentration"]) { + for (let stat of ["Mean", "Range", "Standard Deviation"]) { + let statSection = document.getElementById(metric.toLowerCase()+"s-"+shorthands[stat]); + statSection.innerHTML = parsedResponse["stats"][metric][stat]; + } + let medianBox = document.getElementById(metric.toLowerCase()+"s-median"); + let iqrBox = document.getElementById(metric.toLowerCase()+"s-iqr"); + iqrList = parsedResponse["stats"][metric]["iqr"] + medianBox.innerHTML = iqrList[1]; + iqrBox.innerHTML = iqrList[2] + " - " + iqrList[0]; + let graphs = Object.keys(parsedResponse["graphs"]["Count"]); + setGraph(graphs[0], metric); + } + } +} + +const setGraph = (selectedGraph, metric) => { + let graphList = Object.keys(parsedResponse["graphs"][metric]); + document.getElementById(metric.toLowerCase()+"s-graph").src = parsedResponse["graphs"][metric][selectedGraph]; + let changeGraph = document.getElementById(metric.toLowerCase()+"s-change"); + changeGraph.innerHTML = ""; + for (let graphType of graphList) { + let graphAnchor = document.createElement("a"); + graphAnchor.innerHTML = graphType; + graphAnchor.setAttribute("onclick", "setGraph('" + graphType + "', '" + metric + "')"); + if (graphType==selectedGraph) { + graphAnchor.setAttribute("class", "graph-type-clicked"); + } else { + graphAnchor.setAttribute("class", "graph-type"); + } + changeGraph.appendChild(graphAnchor); + } + let download_link = document.getElementById(metric+"s-graph-download"); + download_link.setAttribute("href", parsedResponse["graphs"][metric][selectedGraph]); + download_link.setAttribute("download", selectedGraph+".jpg"); +} \ No newline at end of file diff --git a/app/templates/about.html b/app/templates/about.html new file mode 100644 index 0000000..d5d1ff2 --- /dev/null +++ b/app/templates/about.html @@ -0,0 +1,84 @@ + + + + + + + About + + + + + + + + +
+ +

about

+ + + +
+
+
+ +

Algaeorithm is accelerating
the transition to a
bioeconomy

+
+

Our Approach

+

+ The challenge of shifting the US to a bio-based economy is a generational problem. The catalyst for this movement will be the next generation of students, who can turn promises of a green future full of sustainable biopower, biofuels, and other bioproducts into their reality. Using a machine-learning-based web application and a diverse offering of educational content, Algaeorithm aims to support this movement by engaging current high school students in the world of bioenergy. +

+

Team

+
+ + + Ashwin Mukherjee + + + + Rohan Chanani + + + + Jake Valenzuela, PhD + +
+
+
+

Funded By

+
+ + + + + + + + + + + + + + + +
+
+ + + + \ No newline at end of file diff --git a/app/templates/analyzed.html b/app/templates/analyzed.html new file mode 100644 index 0000000..b5e0b97 --- /dev/null +++ b/app/templates/analyzed.html @@ -0,0 +1,8 @@ +{% extends "layout.html" %} + +{% block title %} + {{ business.name }} +{% endblock %} + +{% block main %} +{% endblock %} \ No newline at end of file diff --git a/app/templates/bad_index.html b/app/templates/bad_index.html new file mode 100644 index 0000000..3733952 --- /dev/null +++ b/app/templates/bad_index.html @@ -0,0 +1,251 @@ + + + + + + + Algaeorithm + + + + + + +
+ +

algaeorithm

+ + + +
+
+
+
+
+ + + +
+
+
+ +
+

algae type

+ + + +
+
+

depth

+
+ + +
+
+ + +
+
+
+
+

time

+ +
+ +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/app/templates/content.html b/app/templates/content.html new file mode 100644 index 0000000..1be978f --- /dev/null +++ b/app/templates/content.html @@ -0,0 +1,55 @@ + + + + + + + Content + + + + + + + + +
+ +

content

+ + + +
+
+ +
+

Algaeorithm Tutorial

+

Feb 27, 2022

+
+ +
+

2021 Congressional App Challenge

+

Oct 31, 2021

+
+ +
+

Big data and its applications

+

Sep 28, 2021

+
+
+ + + + \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html index d8ed68c..6f9968b 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,136 +1,121 @@ - - - - - Algaeorithm - - - - - - - - - - + + + + + Algaeorithm + + + + + + +
-

Algaeorithm

+ +

algaeorithm

+ + +
-
-
-

To begin, choose an option below

- Advanced -
-