diff --git a/.env.example b/.env.example deleted file mode 100644 index ee03bba0..00000000 --- a/.env.example +++ /dev/null @@ -1,10 +0,0 @@ -LOG_LEVEL=INFO -EXEC_MODE=prod -CLOUDKARAFKA_BROKERS=your-cloud-karafka-brokers-urls -CLOUDKARAFKA_USERNAME=your-cloud-karafka-username -CLOUDKARAFKA_PASSWORD=your-cloud-karafka-password -CLOUDKARAFKA_TOPIC_PREFIX=your-cloud-karafka-topic-prefix -FLASK_SECRET_KET=your-secret-flask-key -KAGGLE_USERNAME=your-kaggle-username -KAGGLE_KEY=your-kaggle-key -NEXT_URL=http://localhost:3000 \ No newline at end of file diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..2a64173e --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +worker: python classification_app.py \ No newline at end of file diff --git a/classification/grid_search.py b/classification/grid_search.py deleted file mode 100644 index 01d7f33d..00000000 --- a/classification/grid_search.py +++ /dev/null @@ -1,57 +0,0 @@ -import pathlib -import json -from typing import Union, Iterable - -import numpy as np -import matplotlib.pyplot as plt -from keras.wrappers.scikit_learn import KerasClassifier -from sklearn.model_selection import GridSearchCV - -from lenet import LeNet - -file_path = pathlib.Path(__file__).absolute() -data_dir = file_path.parents[1] / 'dataset' -data_file = sorted(file for file in data_dir.iterdir() if 'classification_set' in file.name)[-1] -label_file = sorted(file for file in data_dir.iterdir() if 'label_dict' in file.name)[-1] -with open(label_file, 'r') as f: - label_dict = json.loads(f.read()) - -labels_values = label_dict.values() - - -def create_model(lr): - lenet = LeNet(labels_values, lr=lr) - return lenet.model - - -# TODO: Allow other NeuralNetwork subclasses -def grid_search(*, epochs, lr, **kwargs): - model = KerasClassifier(build_fn=create_model) - print(kwargs) - param_grid = dict(epochs=epochs, lr=lr, **kwargs) - grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3) - # TODO: get sets without class instantiation (classmethod?) - lenet = LeNet(labels_values) - lenet.get_train_test_sets(data_file=data_file) - x, y = lenet.train_set - # x_test, y_test = lenet.test_set - # x = (*x_train, *x_test) - # y = (*y_train, *y_test) - return grid.fit(x, y) - - -if __name__ == '__main__': - lrs = [0.001, 0.0001, 0.00001] - batch_sizes = [64, 128] - grid_result = grid_search(epochs=[10], lr=lrs, batch_size=batch_sizes) - print(grid_result.best_params_) - print(grid_result.best_score_) - scores = [x[1] for x in grid_result.grid_scores_] - scores = np.array(scores).reshape(len(lrs), len(batch_sizes)) - - for ind, i in enumerate(lrs): - plt.plot(batch_sizes, scores[ind], label='lr: ' + str(i)) - plt.legend() - plt.xlabel('Batch Size') - plt.ylabel('Mean score') - plt.show() diff --git a/classification/lenet.py b/classification/lenet.py index 755ce675..1ce798c1 100644 --- a/classification/lenet.py +++ b/classification/lenet.py @@ -1,4 +1,3 @@ -from abc import ABC from typing import Iterable import keras diff --git a/classification/neural_network.py b/classification/neural_network.py index c4265aa2..be6062bb 100644 --- a/classification/neural_network.py +++ b/classification/neural_network.py @@ -1,16 +1,10 @@ -import json -from datetime import datetime -from typing import Iterable, Union, Optional +from typing import Iterable, Union from pathlib import Path from operator import itemgetter from abc import ABC, abstractmethod -from keras.wrappers.scikit_learn import KerasClassifier + from tensorflow.keras import Model -from tensorflow.keras.utils import to_categorical -from sklearn.model_selection import train_test_split -from sklearn.metrics import classification_report, cohen_kappa_score, confusion_matrix -import matplotlib.pyplot as plt import numpy as np from preprocessing.image_edition import image_to_square, resize_threshold @@ -38,52 +32,20 @@ def model(self) -> Model: def labels(self) -> Iterable[str]: pass - def get_train_test_sets(self, *, data_file: Union[str, Path]): - database = np.load(data_file) - dataset, labels = database['database'], database['label'] - labels = to_categorical(labels) - # split dataset in train and test - x_train, x_test, y_train, y_test = train_test_split(dataset, labels, test_size=0.1, stratify=labels) - x_train = x_train.reshape(x_train.shape[0], *self.input_shape) - x_test = x_test.reshape(x_test.shape[0], *self.input_shape) - self.train_set = (x_train, y_train) - self.test_set = (x_test, y_test) - - def save_weights(self, file_path: Union[str, Path], *, save_format: str = 'h5'): - self.model.save_weights(file_path, save_format=save_format) - def load_weights(self, file: Union[str, Path]): self.model.load_weights(file) - def train(self, *, data_file: Union[str, Path], batch_size: int = 32, epochs: int = 10, - logs_dir: Optional[Union[str, Path]] = None, weights_dir=None): - # load data - self.get_train_test_sets(data_file=data_file) - history = self.model.fit(*self.train_set, epochs=epochs, batch_size=batch_size, validation_data=self.test_set) - self.history = history.history - if logs_dir or weights_dir: - now = datetime.now() - date_string = now.strftime('%Y_%m_%d_%H_%M_%S') - data_string = f'd={date_string}_e={epochs}_b={batch_size}' - if logs_dir: - with open(f'{logs_dir}/loss_history_{self.__class__.__name__.lower()}_{data_string}.log', 'w') as file: - file.write(json.dumps(self.history, indent=4, sort_keys=True)) - accuracy, cohen_kappa = self.accuracy_metrics() - with open(f'{logs_dir}/accuracy_metrics_{self.__class__.__name__.lower()}_{data_string}.log', 'w') as file: - file.write(accuracy) - file.write(f'Cohen Kappa score : {cohen_kappa}') - if weights_dir: - self.save_weights(f'{weights_dir}/{self.__class__.__name__.lower()}_weights_{data_string}.h5', - save_format='h5') - def predict(self, img: np.ndarray): return self.model.predict(img) def predict_array(self, array: Iterable[np.ndarray]): predictions_results = [None] * len(array) for img_idx, img in enumerate(array): - - squared_img = image_to_square(img) + try: + squared_img = image_to_square(img) + except ValueError: + predictions_results[img_idx] = "" + continue resized_img = resize_threshold(squared_img, self.input_shape[:2]) input_img = resized_img[np.newaxis, :, :, np.newaxis] prediction_array = self.predict(input_img) @@ -94,39 +56,3 @@ def predict_array(self, array: Iterable[np.ndarray]): prediction = sorted(prediction_list, key=itemgetter(1))[-1] predictions_results[img_idx] = prediction[0] return predictions_results - - def accuracy_metrics(self): - y_pred = self.model.predict(self.test_set[0], batch_size=32, verbose=1) - y_pred_max = np.argmax(y_pred, axis=1) - - y_true = [] - for i in range(len(self.test_set[1])): - y_true.append(np.argmax(self.test_set[1][i])) - - accuracy = classification_report(y_true, y_pred_max) - cohen_kappa = cohen_kappa_score(y_true, y_pred_max) - conf_matrix = confusion_matrix(y_true, y_pred_max) - return accuracy, cohen_kappa, conf_matrix - - def train_test_plt(self): - fig = plt.figure(figsize=(15, 5)) - fig.add_subplot(1, 2, 1) - - plt.plot(self.history['val_loss']) - plt.plot(self.history['loss']) - plt.title('Model Loss') - plt.xlabel('Epoch') - plt.ylabel('Loss') - plt.legend(['Test', 'Train'], loc='upper left') - - fig.add_subplot(1, 2, 2) - plt.plot(self.history['val_accuracy']) - plt.plot(self.history['accuracy']) - plt.title('Model Accuracy') - plt.xlabel('Epoch') - plt.ylabel('Accuracy') - plt.legend(['Test', 'Train'], loc='upper left') - - fig.subplots_adjust(wspace=0.2, hspace=1) - - diff --git a/classification/train.py b/classification/train.py deleted file mode 100644 index 753e16b5..00000000 --- a/classification/train.py +++ /dev/null @@ -1,25 +0,0 @@ -import pathlib -import json - -from lenet import LeNet - - -if __name__ == '__main__': - file_path = pathlib.Path(__file__).absolute() - data_dir = file_path.parents[1] / 'dataset' - data_file = sorted(file for file in data_dir.iterdir() if 'classification_set' in file.name)[-1] - label_file = sorted(file for file in data_dir.iterdir() if 'label_dict' in file.name)[-1] - with open(label_file, 'r') as f: - label_dict = json.loads(f.read()) - - labels = label_dict.values() - - # Model instantiation - lenet = LeNet(labels) - - # Model training - fitted_lenet = lenet.train(data_file=data_file) - - # Training metrics - # train_test_plt(fitted_lenet) - # accuracy_metrics(lenet) diff --git a/classification/weights/lenet_weights_d=2021_02_21_11_51_18_e=50_b=64.h5 b/classification/weights/lenet_weights_d=2021_02_21_11_51_18_e=50_b=64.h5 deleted file mode 100644 index 1e808a7c..00000000 Binary files a/classification/weights/lenet_weights_d=2021_02_21_11_51_18_e=50_b=64.h5 and /dev/null differ diff --git a/dataset/classification_set_2021_01_11_20_40_00.npz b/dataset/classification_set_2021_01_11_20_40_00.npz deleted file mode 100644 index 10dd0ea3..00000000 Binary files a/dataset/classification_set_2021_01_11_20_40_00.npz and /dev/null differ diff --git a/dataset/classification_set_2021_02_25_23_39_37.npz b/dataset/classification_set_2021_02_25_23_39_37.npz deleted file mode 100644 index 02eb5a01..00000000 Binary files a/dataset/classification_set_2021_02_25_23_39_37.npz and /dev/null differ diff --git a/dataset/classification_set_2021_02_26_23_49_38.npz b/dataset/classification_set_2021_02_26_23_49_38.npz deleted file mode 100644 index 032f1e10..00000000 Binary files a/dataset/classification_set_2021_02_26_23_49_38.npz and /dev/null differ diff --git a/dataset/classification_set_2021_02_27_01_08_49.npz b/dataset/classification_set_2021_02_27_01_08_49.npz deleted file mode 100644 index 032f1e10..00000000 Binary files a/dataset/classification_set_2021_02_27_01_08_49.npz and /dev/null differ diff --git a/dataset/label_dict_2021_02_27_01_08_49.txt b/dataset/label_dict_2021_02_27_01_08_49.txt index 0b6a5297..ec4029a3 100644 --- a/dataset/label_dict_2021_02_27_01_08_49.txt +++ b/dataset/label_dict_2021_02_27_01_08_49.txt @@ -15,41 +15,41 @@ "13": "8", "14": "9", "15": "a", - "16": "alpha", + "16": "\\alpha", "17": "b", - "18": "beta", + "18": "\\beta", "19": "c", - "20": "cos", + "20": "\\cos", "21": "d", - "22": "delta", + "22": "\\delta", "23": "e", "24": "f", - "25": "forward_slash", + "25": "/", "26": "g", - "27": "gamma", + "27": "\\gamma", "28": "h", "29": "i", "30": "j", "31": "k", "32": "l", - "33": "lambda", - "34": "log", + "33": "\\lambda", + "34": "\\log", "35": "m", - "36": "mu", + "36": "\\mu", "37": "n", "38": "o", "39": "p", - "40": "phi", - "41": "pi", + "40": "\\phi", + "41": "\\pi", "42": "q", "43": "r", "44": "s", - "45": "sigma", - "46": "sin", - "47": "sqrt", + "45": "\\sigma", + "46": "\\sin", + "47": "\\sqrt", "48": "t", - "49": "tan", - "50": "theta", + "49": "\\tan", + "50": "\\theta", "51": "u", "52": "v", "53": "w", diff --git a/dataset/seg_test.png b/dataset/seg_test.png deleted file mode 100644 index 387eabf3..00000000 Binary files a/dataset/seg_test.png and /dev/null differ diff --git a/doc/Memoria.docx b/doc/Memoria.docx deleted file mode 100644 index 43443510..00000000 Binary files a/doc/Memoria.docx and /dev/null differ diff --git a/parse_app.py b/parse_app.py deleted file mode 100644 index 75587b75..00000000 --- a/parse_app.py +++ /dev/null @@ -1,70 +0,0 @@ -import concurrent.futures -import json -import logging - -import flask -from flask_cors import CORS, cross_origin -from kafka import kafka -from parsing.parser import XYParser -from segmentation.xy_segmentation import dict_to_xy_segmentation_results -from settings import LOG_LEVEL, FLASK_SECRET_KEY - -app = flask.Flask(__name__) -app.config['SECRET_KEY'] = FLASK_SECRET_KEY -CORS(app, supports_credentials=True) - -logging.basicConfig(level=LOG_LEVEL) - -consumer = kafka.init_consumer('classification') - -logging.info("Listening for new messages") - -parsed_equations = dict() - - -def message_processor(): - while True: - input_message = kafka.consumer_cycle(consumer) - if input_message: - input_json = json.loads(input_message.value()) - predictions_results = input_json['predictions_results'] - segmentation_dict = input_json['segmentation_structure'] - session_id = input_json['session_id'] - segmentation_structure = dict_to_xy_segmentation_results(segmentation_dict) - latex_expression = XYParser(predictions_results, segmentation_structure).last_level.parsed_groups[0] - global parsed_equations - parsed_equations[session_id] = latex_expression - logging.info(f'parsed latex: {latex_expression}') - - -@app.route("/parsed/", methods=('GET',)) -@cross_origin(supports_credentials=True) -def parsed(session_id): - logging.debug('req received') - - def event_stream(): - logging.debug('event stream') - global parsed_equations - while True: - latex_equation = parsed_equations.get(session_id) - if latex_equation: - logging.debug('got it') - parsed_equations.pop(session_id) - return f'data: {latex_equation}\n\n' - # else: - # print('waiting') - # return 'listening...' - # yield 'data: 1\n\n' - return flask.Response(event_stream(), mimetype="text/event-stream") - - -@app.route("/test") -def test(): - return 'ok', 200 - - -if __name__ == '__main__': - with concurrent.futures.ThreadPoolExecutor() as executor: - executor.submit(message_processor) - app.run(debug=(LOG_LEVEL == 'DEBUG'), port=5003, threaded=True) - diff --git a/parsing/__init__.py b/parsing/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/parsing/parser.py b/parsing/parser.py deleted file mode 100644 index c04a408a..00000000 --- a/parsing/parser.py +++ /dev/null @@ -1,172 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Mar 22 21:32:35 2021 - -@author: Carlos Espa Torres -""" - -import logging -from typing import List, Iterator, Union - -from segmentation.xy_segmentation import XYSegmentationResults, SegmentationOperation, SegmentationGroup -from utils.utils import check_list_types - - -class ParsedLevel: - def __init__(self, parsed_groups: Union[List[str], str, None] = None): - if check_list_types(parsed_groups, str): - self.__parsed_groups = parsed_groups - elif isinstance(parsed_groups, str): - self.__parsed_groups = [parsed_groups] - elif parsed_groups is None: - self.__parsed_groups = list() - else: - raise TypeError(''.join(('parsed_groups argument must be of type List[str], str, or NoneType', - f' instead found {parsed_groups} of type {type(parsed_groups)}'))) - - @property - def parsed_groups(self) -> List[str]: - return self.__parsed_groups - - def add_group(self, new_group: str) -> None: - if not isinstance(new_group, str): - raise TypeError(''.join(('new_group arg must be of type str,', - f'instead found {new_group} of type {type(new_group)}'))) - self.__parsed_groups.append(new_group) - - def parse_group(self, segmentation_group: SegmentationGroup, parsed_iterator: Iterator[str]) -> str: - # FIXME: Greek letters and operators should start with \ maybe strings with length > 1 - operation = segmentation_group.segmentation_operation - if operation == SegmentationOperation.NONE or operation == SegmentationOperation.BEGINNING: - logging.debug('Operation none or beginning detected') - parsed_group = next(parsed_iterator) - elif operation == SegmentationOperation.ROOT_REMOVAL: - logging.debug('Operation root removal detected') - parsed_group = ''.join(("{\\", next(parsed_iterator), "{", next(parsed_iterator), "}}")) - - elif operation == SegmentationOperation.X_SEGMENTATION: - logging.debug('Operation x segmentation detected') - previous_level = 0 - parsed_group = '' - for symbol_level in segmentation_group.segmentation_levels: - if symbol_level == previous_level: - parsed_group += next(parsed_iterator) - elif symbol_level < previous_level: - if previous_level > 0: - parsed_group = ''.join((parsed_group, "}", next(parsed_iterator))) - else: - parsed_group = ''.join((parsed_group, "_{", next(parsed_iterator))) - else: - if previous_level >= 0: - parsed_group = ''.join((parsed_group, "^{", next(parsed_iterator))) - else: - parsed_group = ''.join((parsed_group, "}", next(parsed_iterator))) - previous_level = symbol_level - parsed_group += "}" * abs(symbol_level) - elif operation == SegmentationOperation.Y_SEGMENTATION: - logging.debug('Operation y segmentation detected') - if len(segmentation_group.segmented_images) == 3: - numerator, _, denominator = next(parsed_iterator), next(parsed_iterator), next(parsed_iterator) - parsed_group = ''.join((r"\frac{", numerator, "}{", denominator, "}")) - elif len(segmentation_group.segmented_images) == 2: - group1, group2 = next(parsed_iterator), next(parsed_iterator) - if [group1, group2] == ['-', '-']: - parsed_group = '=' - else: - parsed_group = group2 - else: - raise ValueError(f'Operation value ({segmentation_group.segmentation_operation}) out of range') - logging.debug(f"parsed symbols: {parsed_group}") - self.add_group(parsed_group) - - -class XYParser: - def __init__(self, predicted_array: List[str], xy_segmentation_results: XYSegmentationResults): - self.__parsed_levels = list() - self.add_level(ParsedLevel(predicted_array)) - for level_index, segmentation_level in enumerate(reversed(xy_segmentation_results.segmentation_levels)): - logging.debug(f"Parsing level {level_index}") - self.add_level(ParsedLevel()) - logging.debug(self.previous_level.parsed_groups) - expression_iter = iter(self.previous_level.parsed_groups) - for group_index, segmentation_group in enumerate(segmentation_level.segmentation_groups): - logging.debug(f"Parsing operation {group_index}") - self.last_level.parse_group(segmentation_group, expression_iter) - - @property - def parsed_levels(self) -> List[ParsedLevel]: - return self.__parsed_levels - - @property - def last_level(self) -> ParsedLevel: - return self.parsed_levels[-1] - - @property - def previous_level(self) -> ParsedLevel: - """Returns the previous parsed level to the last created level""" - return self.parsed_levels[-2] - - def add_level(self, new_level: ParsedLevel) -> None: - if not isinstance(new_level, ParsedLevel): - raise TypeError('new_level argument must be of type ParsedLevel') - self.__parsed_levels.append(new_level) - - -def parse(equation_structure): - """Takes a prediction of symbols and parses it to LaTeX""" - latex_string = r"" - # take array of symbols - # FIXME: fix upstream grouping - construction_array = [equation_structure[-1][1]] - for level_index, (operations, images) in enumerate(reversed(equation_structure)): - logging.debug(f"Parsing level {level_index}") - logging.debug([operations, construction_array[-1]]) - expression_iter = iter(construction_array[-1]) - construction_array.append([]) - for operation_index, operation in enumerate(operations): - logging.debug(f"Parsing operation {operation_index}") - if operation[0] == 'n': - construction_array[-1].append(next(expression_iter)) - logging.debug(f"parsed symbols: {construction_array[-1][-1]}") - elif operation[0] == 'r': - symbol_array = next(expression_iter) - construction_array[-1].append("{\\" + symbol_array[0] + "{" + symbol_array[-1] + "}}") - logging.debug(f"parsed symbols: {construction_array[-1][-1]}") - elif operation[0] == 'x': - previous_level = 0 - expression = r"{" - symbol_iter = iter(next(expression_iter)) - for symbol_index, symbol_level in enumerate(operation[1]): - if symbol_level == previous_level: - expression += next(symbol_iter) - elif symbol_level < previous_level: - if previous_level > 0: - expression += "{" + next(symbol_iter) - else: - expression += "}_{" + next(symbol_iter) - else: - if previous_level >= 0: - expression += "}^{" + next(symbol_iter) - else: - expression += "}" + next(symbol_iter) - expression += "}" * (abs(symbol_level) + 1) - construction_array[-1].append([expression]) - logging.debug(f"parsed symbols: {construction_array[-1][-1]}") - elif operation[0] == 'y': - # TODO: find a way to know number of levels, maybe add them again - symbol_array = next(expression_iter) - logging.debug(symbol_array) - if len(symbol_array) == 3: - expression = r"{\frac{" + symbol_array[0] + "}{" + symbol_array[-1] + "}}" - if len(symbol_array) == 2: - if symbol_array == ['-', '-']: - expression = '=' - else: - expression = symbol_array[-1] - construction_array[-1].append([expression]) - logging.debug(f"parsed symbols: {construction_array[-1][-1]}") - elif operation[0] == 'b': - latex_string = construction_array[-2][-1] - - logging.debug(f"Parsed expression: {latex_string}") - return latex_string diff --git a/predict.py b/predict.py deleted file mode 100644 index 15681b48..00000000 --- a/predict.py +++ /dev/null @@ -1,47 +0,0 @@ -import pathlib -import json -import logging - -import cv2 - -from segmentation.xy_segmentation import xy_segmentation -from classification.lenet import LeNet -from parsing.parser import XYParser -from solver.solver import Solver -from settings import LOG_LEVEL - - -if __name__ == '__main__': - logging.basicConfig(level=LOG_LEVEL) # select logging level - pad = [3] * 4 # [top, bottom, left, right] - - # Model instantiation and loading - base_dir = pathlib.Path(__file__).parents[0] - dataset_dir = base_dir / 'dataset' - - # TODO: allow file selection - with open([str(file) for file in dataset_dir.iterdir() if 'label_dict' in str(file)][0], "r") as f: - labels = list(json.loads(f.read()).values()) - - logging.debug(f"length of labels {len(labels)}") - lenet = LeNet(labels) - weights_dir = base_dir / 'classification' / 'weights' - weights_file = sorted([path for path in weights_dir.iterdir()])[-1] - lenet.load_weights(weights_file) - - # Input directory - img_dir = base_dir / 'dataset' / 'segmentation' - # save_dir = base_dir / 'segmentation' / 'segmented' - - # Find all images to segment - for img_path in img_dir.iterdir(): - if img_path.is_dir(): - continue - image = cv2.imread(str(img_path), 0) - segmentation_results, segmentation_structure = xy_segmentation(image) - print(segmentation_results) - predictions_results = lenet.predict_array(segmentation_results) - logging.info(f'results are: {predictions_results}') - latex_expression = XYParser(predictions_results, segmentation_structure).last_level.parsed_groups[0] - latex_solution = Solver(latex_expression, 'y').latex_solution - logging.info(f'solution latex is: {latex_solution}') diff --git a/preprocessing/image_edition.py b/preprocessing/image_edition.py index 4adf7b7e..ed0bbf80 100644 --- a/preprocessing/image_edition.py +++ b/preprocessing/image_edition.py @@ -4,9 +4,6 @@ import numpy as np from scipy import ndimage -from utils.utils import show_image - - def bounding_square(objs: Iterable[np.ndarray]) -> Tuple[int]: """Returns a single bounding rectangle that contains all input objects""" x = min([obj[1].start for obj in objs]) @@ -52,9 +49,6 @@ def image_to_square(img: np.ndarray, *, pad: Iterable[int] = [1]*4, white_value= logging.error(f"{int((side - height) / 2)}:{height + int((side - height) / 2)}") logging.error(f"{int((side - width) / 2)}:{width + int((side - width) / 2)}") raise e - if debug: - show_image(crop_img, "original") - show_image(squared_image, "squared_image") return squared_image @@ -79,7 +73,5 @@ def resize_threshold(image: np.ndarray, size: int, *, normalize=False, debug=Fal _, ncomponents = ndimage.label(img_inv, structure) if ncomponents == 1: break - if debug: - show_image(resized_img, "resized") _, resized_img = cv2.threshold(resized_img, threshold, white_value, cv2.THRESH_BINARY) return resized_img diff --git a/requirements.txt b/requirements.txt index 430fb4ee..19bd9b14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,8 @@ confluent-kafka==1.6.0 -Flask==2.0.1 -Flask-Cors==3.0.10 -Flask-SocketIO==5.1.1 -kaggle~=1.5.12 keras==2.6.0 -latex2sympy2==1.6.2 -matplotlib==3.4.2 numpy==1.19.5 -opencv==4.5.2 +opencv-python-headless~=4.5.2 scikit-learn==0.24.2 scipy==1.6.2 -sympy==1.8 -tensorflow==2.6.0 +tensorflow-cpu~=2.6.0 python-dotenv~=0.19.0 diff --git a/segmentation/Contour.py b/segmentation/Contour.py deleted file mode 100644 index 452082ee..00000000 --- a/segmentation/Contour.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Jan 12 19:38:35 2021 - -@author: Carlos -""" -import cv2 as cv -import matplotlib.pyplot as plt -import numpy as np -import pathlib -import os - - -def contour_split(img, img_name, save_dir): - """ - Find contours of an image and saves a new image for each contour found - """ - imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) - ret, thresh = cv.threshold(imgray, 80, 255, cv.THRESH_BINARY) - ret2, thresh_img = cv.threshold(img, 80, 255, cv.THRESH_BINARY) - _, contours, hier = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE) - #cv.drawContours(thresh_img, contours, -1, (0,255,0), 3) - # mask = np.ones(img.shape[:2], dtype="uint8") * 255 - segmented_symbols = [] - for index, contour in enumerate(contours): - mask = np.ones(thresh_img.shape) - mask = cv.drawContours(mask, contours, index, 0, cv.FILLED) - - # Generate output - masked_img = thresh_img.copy() - masked_img[mask.astype(np.bool)] = 255 - # Threshold to throw away noise - print(f'masked img shape {masked_img.shape}') - zero_threshold = masked_img.shape[0]*masked_img.shape[1]/10e3 - if np.count_nonzero(masked_img == 0) > zero_threshold: - segmented_symbols.append(masked_img) - #cv.imwrite(f"{save_dir}/{img_name}_Contour_{index}.png", masked_img) - return segmented_symbols - - - -if __name__ == '__main__': - - file_path = pathlib.Path(__file__) - img_dir = file_path.parents[1] / 'dataset' / 'segmentation' - saving_dir = file_path.parents[0] / 'segmented' - # Erase previous contents of the directory - for file_path in saving_dir.iterdir(): - os.unlink(file_path) - # Find all images to segment - for img_path in img_dir.iterdir(): - img = cv.imread(str(img_path.absolute())) - - symbols = contour_split(img, img_name=img_path.parts[-1], - save_dir=saving_dir) - # fig = plt.figure() - # ax = fig.add_subplot(1, 1, 1) - # ax.imshow(thresh_img) - # plt.axis("off") \ No newline at end of file diff --git a/segmentation/SLIC.py b/segmentation/SLIC.py deleted file mode 100644 index 20989eec..00000000 --- a/segmentation/SLIC.py +++ /dev/null @@ -1,61 +0,0 @@ -import cv2 as cv -from skimage.segmentation import slic -from skimage.segmentation import mark_boundaries -from skimage.util import img_as_float -from skimage import io -import matplotlib.pyplot as plt -import numpy as np -import pathlib - -def superpixel_split(thresh_img, save_dir, n_segments=100, sigma=5.0, compactness=5, order=0,): - segments = slic(thresh_img, n_segments=n_segments, compactness=compactness, sigma=sigma) - for (i, segVal) in enumerate(np.unique(segments)): - # construct a mask for the segment - # print (f"[x] inspecting segment {i}") - mask = np.ones(thresh_img.shape, dtype="uint8") * 255 - mask[segments == segVal] = 0 - # show the masked region - # cv.imwrite(f"Test_Mask_{i}.png", mask) - masked_img = cv.bitwise_or(thresh_img, mask) - zero_threshold = masked_img.shape[0]*masked_img.shape[1]/10e3 - if np.count_nonzero(masked_img == 0) > zero_threshold: - cv.imwrite(f"{save_dir}/Applied_Mask_{i*10**order}.png", masked_img) - -file_path = pathlib.Path(__file__) -img_dir = file_path.parents[1] / 'dataset' / 'segmentation' -saving_dir = file_path.parents[0] / 'segmented' - -for img_path in img_dir.iterdir(): - img = cv.imread(str(img_path.absolute())) - - ret, thresh_img = cv.threshold(img, 80, 255, cv.THRESH_BINARY) - - - # titles = ['Original Image', 'BINARY'] - # images = [img, thresh_img] - - # for i in range(2): - # plt.subplot(2,3,i+1) - # plt.imshow(images[i],'gray',vmin=0,vmax=255) - # plt.title(titles[i]) - # plt.xticks([]) - # plt.yticks([]) - - # plt.subplot(2,3,3) - # plt.imshow(mark_boundaries(thresh_img, segments)) - # plt.title(titles[i]) - # plt.xticks([]) - # plt.yticks([]) - - # fig = plt.figure() - # ax = fig.add_subplot(1, 1, 1) - # ax.imshow(mark_boundaries(thresh_img, segments, color=(255,0,0))) - # plt.axis("off") - - # loop over the unique segment values - print(f'saving directory is: {saving_dir}') - superpixel_split(thresh_img, save_dir=saving_dir) - - for idx, img_path in enumerate(saving_dir.iterdir()): - img = cv.imread(str(img_path.absolute())) - superpixel_split(img, save_dir=saving_dir, order=2 * (idx + 1)) diff --git a/segmentation/__init__.py b/segmentation/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/segmentation/segmented/seg.png b/segmentation/segmented/seg.png deleted file mode 100644 index a8d597a1..00000000 Binary files a/segmentation/segmented/seg.png and /dev/null differ diff --git a/segmentation/xy_segmentation.py b/segmentation/xy_segmentation.py deleted file mode 100644 index 3b40259f..00000000 --- a/segmentation/xy_segmentation.py +++ /dev/null @@ -1,425 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Jan 14 18:43:19 2021 - -@author: Carlos Espa Torres -""" - -import pathlib -from enum import IntEnum, unique -from typing import List, Tuple, Union -# import itertools -import logging -from copy import deepcopy - -import cv2 as cv -import matplotlib.pyplot as plt -import numpy as np -from scipy.ndimage.measurements import label - -from utils.utils import show_image, check_list_types, normalize_img - - -@unique -class SegmentationOperation(IntEnum): - BEGINNING = 0 - X_SEGMENTATION = 1 - Y_SEGMENTATION = 2 - ROOT_REMOVAL = 3 - NONE = 4 - - -class SegmentationGroup: - def __init__(self, segmentation_operation: SegmentationOperation, - segmented_images: List[np.ndarray], segmentation_levels: List[int] = None): - if not isinstance(segmentation_operation, SegmentationOperation): - raise TypeError('segmentation_operation arg must be of type SegmentationOperation') - if not check_list_types(segmented_images, np.ndarray): - raise TypeError('segmented_images arg must be of type List[np.ndarray]') - if segmentation_operation is SegmentationOperation.X_SEGMENTATION: - if not check_list_types(segmentation_levels, int): - raise TypeError('segmentation_levels arg must be of type List[int]') - if len(segmented_images) != len(segmentation_levels): - raise ValueError('segmentation_levels and segmented_images must have same size for X_SEGMENTATION') - if segmentation_levels[0] != 0: - raise ValueError('first segmentation level must be zero') - elif segmentation_levels is not None: - raise ValueError('segmentation_levels must be None for non X_SEGMENTATION operations') - self.__segmentation_operation: SegmentationOperation = segmentation_operation - self.__segmented_images: List[np.ndarray] = segmented_images - self.__segmentation_levels: List[int] = segmentation_levels - - def serialize(self): - return {'segmentation_operation': self.segmentation_operation, 'segmented_images': self.segmented_images, - 'segmentation_levels': self.segmentation_levels} - - @property - def segmentation_operation(self) -> SegmentationOperation: - return self.__segmentation_operation - - @property - def segmented_images(self) -> List[np.ndarray]: - return self.__segmented_images - - @segmented_images.setter - def segmented_images(self, a): - self.__segmented_images = [a for img in self.__segmented_images] - - @property - def segmentation_levels(self) -> List[int]: - return self.__segmentation_levels - - -class SegmentationLevel: - def __init__(self, segmentation_groups: Union[List[SegmentationGroup], SegmentationGroup] = None): - if check_list_types(segmentation_groups, SegmentationGroup): - self.__segmentation_groups: List[SegmentationGroup] = segmentation_groups - elif isinstance(segmentation_groups, SegmentationGroup): - self.__segmentation_groups = [segmentation_groups] - elif segmentation_groups is None: - self.__segmentation_groups = list() - else: - raise TypeError('segmentation_groups arg must be of type SegmentationGroup') - - def serialize(self): - return {"segmentation_groups": [group.serialize() for group in self.segmentation_groups]} - - @property - def segmentation_groups(self) -> List[SegmentationGroup]: - return self.__segmentation_groups - - @segmentation_groups.setter - def segmentation_groups(self, new_value: List[SegmentationGroup]): - if check_list_types(new_value, SegmentationGroup): - self.__segmentation_groups: List[SegmentationGroup] = new_value - elif isinstance(new_value, SegmentationGroup): - self.__segmentation_groups = [new_value] - else: - raise TypeError('new_value arg must be of type List[SegmentationGroup]') - - def add_group(self, new_group: SegmentationGroup): - if not isinstance(new_group, SegmentationGroup): - raise TypeError('new_group arg must be of type SegmentationGroup') - self.__segmentation_groups.append(new_group) - - -class XYSegmentationResults: - def __init__(self, image: np.ndarray): - if not isinstance(image, np.ndarray): - raise TypeError('image arg must be of type np.ndarray') - self.image = image - self.__continue_division = True - self.__segmentation_levels: List[SegmentationLevel] = [] - - def perform_segmentation(self): - image_array = [self.image] - segmentation_group = SegmentationGroup(SegmentationOperation.BEGINNING, image_array) - self.add_level(SegmentationLevel([segmentation_group])) - - while self.__continue_division and len(self.segmentation_levels) < 4: - # flag to determine whether the division is complete or not - self.__continue_division = False - self.__division_step() - - def serialize(self): - return {"segmentation_levels": [level.serialize() for level in self.segmentation_levels]} - - - @property - def segmentation_levels(self) -> List[SegmentationLevel]: - return self.__segmentation_levels - - # @segmentation_levels.setter - # def segmentation_levels(self, levels: List[SegmentationLevel]): - # if not check_list_types(levels, SegmentationLevel): - # raise TypeError(f"Expected levels arg of type List[SegmentationLevel] but got {type(levels)}") - # self.__segmentation_levels = levels - - @property - def last_level(self) -> SegmentationLevel: - return self.__segmentation_levels[-1] - - @property - def previous_level(self) -> SegmentationLevel: - """Returns the previous level to the last one created""" - return self.__segmentation_levels[-2] - - @property - def segmented_images(self): - return [image for group in self.last_level.segmentation_groups for image in group.segmented_images] - - def add_level(self, new_level: SegmentationLevel) -> None: - if not isinstance(new_level, SegmentationLevel): - raise TypeError('new_level arg must be of type SegmentationLevel') - self.__segmentation_levels.append(new_level) - - def __division_step(self): - """method to produce the next segmentation level""" - self.add_level(SegmentationLevel()) - if self.last_level is self.previous_level: - raise ValueError('same reference in distinct levels') - logging.debug(f'there are {len(self.previous_level.segmentation_groups)} segmentation groups') - for group_index, image_group in enumerate(self.previous_level.segmentation_groups): - logging.debug(f'there are {len(image_group.segmented_images)} images in group {group_index}') - for image_index, image in enumerate(image_group.segmented_images): - logging.debug(''.join((f'starting division of order {len(self.segmentation_levels) - 1}', - f' of picture {image_index} of group {group_index}'))) - try: - # Expect different returns depending on OpenCV version - if int(cv.__version__.split('.')[0]) < 4: - _, contours, _ = cv.findContours(image, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) - else: - contours, _ = cv.findContours(image, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) - - except TypeError as error: - logging.error('Conflicting image is:') - logging.error(image) - raise TypeError(error) - # If there is more than one contour, division can still happen - if len(contours) > 2: - logging.debug(f'detected {len(contours) - 1} contours') - im2 = image.copy() - for i, _ in enumerate(contours): - cv.drawContours(im2, contours, i, (100, 100, 100), 1) - self.__continue_division = True - if logging.root.level == logging.DEBUG: - show_image(im2, - f'division of order {len(self.segmentation_levels) - 1} of picture {image_index}') - self.last_level.add_group(self.__segment_image(image)) - else: - logging.debug('just one contour detected') - self.last_level.add_group(SegmentationGroup(SegmentationOperation.NONE, [image])) - - def __segment_image(self, img: np.ndarray) -> SegmentationGroup: - _, img_inv = cv.threshold(img, 80, 255, cv.THRESH_BINARY_INV) - # Get all connected components in the projection - connection_structure = np.ones((3, 3), dtype='uint8') - _, img_ncomponents = label(img_inv, connection_structure) - if img_ncomponents < 2: - logging.debug("Just one component detected") - return SegmentationGroup(SegmentationOperation.NONE, [img]) - # Get projection over x axis - x_projection = np.matrix(np.amax(img_inv, axis=0)) - - x_labeled, x_ncomponents = label(x_projection, connection_structure) - if x_ncomponents > 1: - return self.__x_division(img, x_labeled, x_ncomponents) - else: - # Get projection over y axis - y_projection = np.matrix(np.amax(img_inv, axis=1)).transpose() - - # Get all connected components in the projection - y_labeled, y_ncomponents = label(y_projection, connection_structure) - - if y_ncomponents > 1: - - return self.__y_division(img, y_labeled, y_ncomponents) - - else: - return self.__mask_removal(img, img_inv) - - @staticmethod - def __x_division(img: np.ndarray, x_labeled: np.ndarray, x_ncomponents: int) -> SegmentationGroup: - """Function that divides an image into vertical components""" - # Array of segmented images - segmented_images = [None] * x_ncomponents - # Array of level of sub/superscript - segmented_levels = [None] * x_ncomponents - segmented_levels[0] = 0 - - logging.debug('performing vertical division') - if logging.root.level == logging.DEBUG: - # Axes for cropped images - rows = int(np.sqrt(x_ncomponents)) - cols = x_ncomponents / rows - if np.isclose(int(cols), cols): - cols = int(cols) - else: - cols = int(cols) + 1 - fig, axs = plt.subplots(rows, cols) - - for component_index in range(x_ncomponents): - x, y = np.argmax(x_labeled == component_index + 1), 0 - w, h = np.count_nonzero(x_labeled == component_index + 1), img.shape[0] - cropped_image = cv.copyMakeBorder(img[y:y + h, x:x + w], 0, 0, pad[2], pad[3], - cv.BORDER_CONSTANT, value=255) - - _, img_inv = cv.threshold(cropped_image, 80, 255, cv.THRESH_BINARY_INV) - moments = cv.moments(img_inv) - y_centroid = int(moments['m01'] / moments['m00']) - logging.debug(f'centroid of element {component_index} is: {y_centroid}') - - if component_index > 0: - if (previous_y_centroid - y_centroid) * 0.9 > diff_min: - segmented_levels[component_index] = segmented_levels[component_index - 1] + 1 - logging.debug("level up") - logging.debug(f'current level: {segmented_levels[component_index]}') - elif (y_centroid - previous_y_centroid) * 0.9 > diff_max: - segmented_levels[component_index] = segmented_levels[component_index - 1] - 1 - logging.debug("level down") - logging.debug(f'current level: {segmented_levels[component_index]}') - logging.debug(f"y_max: {y_max}, prev_y = {previous_y_centroid}, diff_max: {diff_max}, diff: {y_centroid - previous_y_centroid}") - else: - segmented_levels[component_index] = segmented_levels[component_index - 1] - logging.debug('same level') - logging.debug(f'current level: {segmented_levels[component_index]}') - - # References for next component to compare levels - if component_index < x_ncomponents - 1: - previous_y_centroid = y_centroid - # Find highest black pixel - y_min = np.min(np.where(np.amax(img_inv, axis=1))) - y_max = np.max(np.where(np.amax(img_inv, axis=1))) - diff_min = previous_y_centroid - y_min - diff_max = y_max - previous_y_centroid - - segmented_images[component_index] = cropped_image - - if logging.root.level == logging.DEBUG: - if rows > 1: - axs[int(component_index / cols)][component_index % cols].imshow(segmented_images[component_index], - cmap='gray') - else: - axs[component_index].imshow(segmented_images[component_index], cmap='gray') - return SegmentationGroup(SegmentationOperation.X_SEGMENTATION, segmented_images, - segmented_levels) # ('x', segmented_levels), segmented_images - - @staticmethod - def __y_division(img: np.ndarray, y_labeled: np.ndarray, y_ncomponents: int) -> SegmentationGroup: - """Function that divides an image into horizontal components""" - logging.debug(f'y_ncomponents: {y_ncomponents}') - logging.debug('performing horizontal division') - - if logging.root.level == logging.DEBUG: - # Axes for cropped images - rows = int(np.sqrt(y_ncomponents)) - cols = y_ncomponents / rows - if np.isclose(int(cols), cols): - cols = int(cols) - else: - cols = int(cols) + 1 - fig, axs = plt.subplots(rows, cols) - - segmented_images = [None] * y_ncomponents - for idx in range(y_ncomponents): - x, y = 0, np.argmax(y_labeled == idx + 1) - w, h = img.shape[1], np.count_nonzero(y_labeled == idx + 1) - cropped_image = cv.copyMakeBorder(img[y:y + h, x:x + w], pad[0], pad[1], 0, 0, - cv.BORDER_CONSTANT, value=255) - - segmented_images[idx] = cropped_image - - if logging.root.level == logging.DEBUG: - if rows > 1: - axs[int(idx / cols)][idx % cols].imshow(segmented_images[idx], cmap='gray') - else: - axs[idx].imshow(segmented_images[idx], cmap='gray') - return SegmentationGroup(SegmentationOperation.Y_SEGMENTATION, segmented_images) # ('y',), segmented_images - - @staticmethod - def __mask_removal(img: np.ndarray, img_inv: np.ndarray) -> SegmentationGroup: - """Function that splits radical from radicand""" - logging.debug('performing radical-radicand split') - - # Expect different returns depending on OpenCV version - if int(cv.__version__.split('.')[0]) < 4: - _, contours, _ = cv.findContours(img, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) - else: - contours, _ = cv.findContours(img, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) - - bound_rects = [None] * len(contours) - - for i, c in enumerate(contours): - bound_rects[i] = cv.boundingRect(c) - - # Avoid misconnection interpretation - if len(bound_rects) > 1: - # Find biggest box - rect_areas = [w * h for (x, y, w, h) in bound_rects] - logging.debug(f"Bounding boxes {bound_rects}") - big_rect_idx = np.argsort(rect_areas)[-1] - - # Split radical and radicand - mask = np.ones(img.shape) - mask = cv.drawContours(mask, contours, big_rect_idx, 0, cv.FILLED) - radical = img.copy() - radical[mask.astype(np.bool)] = 255 - radicand = cv.drawContours(img, contours, big_rect_idx, 255, cv.FILLED) - segmented_images = [radical, radicand] - operation = SegmentationOperation.ROOT_REMOVAL # ('r',) - if logging.root.level == logging.DEBUG: - # Axes for cropped images - fig, axs = plt.subplots(2) - axs[0].imshow(radical, cmap='gray') - axs[1].imshow(radicand, cmap='gray') - else: - for i, _ in enumerate(contours): - # Connect split symbol - im2 = img.copy() - cv.drawContours(im2, contours, i, (100, 100, 100), 1) - segmented_images = [im2] - # set operation to none - operation = SegmentationOperation.NONE # ('n',) - return SegmentationGroup(operation, segmented_images) # operation, segmented_images - - -def xy_segmentation(input_image: np.ndarray) -> Tuple[List[np.ndarray], XYSegmentationResults]: - """ - Returns the segmentation results and the segmentation structure of the image provided by input - """ - # image = cv.imread(str(image_path), 0) - # Apply padding - - normalized_image = normalize_img(input_image) - padded_image = cv.copyMakeBorder(normalized_image, *segmentation_padding, cv.BORDER_CONSTANT, value=255) - - # Prepare image in grayscale - _, thresholded_image = cv.threshold(padded_image, 180, 255, cv.THRESH_BINARY) - xy_segmenter = XYSegmentationResults(thresholded_image) - - xy_segmenter.perform_segmentation() - segmentation_results = [img for group in xy_segmenter.last_level.segmentation_groups - for img in group.segmented_images] - segmentation_structure = deepcopy(xy_segmenter) - - for level in segmentation_structure.segmentation_levels: - for group in level.segmentation_groups: - group.segmented_images = 0 - - return segmentation_results, segmentation_structure - - -def dict_to_xy_segmentation_results(dict_) -> XYSegmentationResults: - """Parses an input dictionary into an XYSegmentationResults Object""" - segmentation_results = XYSegmentationResults(np.ndarray(0)) - for level in dict_['segmentation_levels']: - groups = [] - for group in level['segmentation_groups']: - segmentation_operation = SegmentationOperation(group['segmentation_operation']) - segmented_images = [np.ndarray(obj) if isinstance(obj, int) else np.array(obj) - for obj in group['segmented_images']] - segmentation_levels = group['segmentation_levels'] - groups.append(SegmentationGroup(segmentation_operation, segmented_images, segmentation_levels)) - segmentation_results.add_level(SegmentationLevel(groups)) - return segmentation_results - - -segmentation_padding = [3] * 4 -pad = segmentation_padding -if __name__ == '__main__': - - debug = True - pad = [3] * 4 # [top, bottom, left, right] - file_path = pathlib.Path(__file__) - img_dir = file_path.parents[1] / 'dataset' / 'segmentation' - - """ - saving_dir = file_path.parents[0] / 'segmented' - # Erase previous contents of the directory - - for file_path in saving_dir.iterdir(): - os.unlink(file_path) - """ - # Find all images to segment - for img_path in img_dir.iterdir(): - xy_segmentation_results = xy_segmentation(img_path) diff --git a/segmentation_app.py b/segmentation_app.py deleted file mode 100644 index 4cd50d62..00000000 --- a/segmentation_app.py +++ /dev/null @@ -1,51 +0,0 @@ -import json -import logging -import base64 -from io import BytesIO - -from PIL import Image, ImageOps -import flask -from flask_cors import CORS, cross_origin -import numpy as np - -from kafka import kafka -from segmentation.xy_segmentation import xy_segmentation -from settings import FLASK_SECRET_KEY, LOG_LEVEL, NEXT_URL - -MAX_HEIGHT = 250 -MAX_WIDTH = 500 - -app = flask.Flask(__name__) -app.config['SECRET_KEY'] = FLASK_SECRET_KEY -CORS(app, origins=NEXT_URL) - -logging.basicConfig(level=LOG_LEVEL) -producer = kafka.init_producer() - - -@app.route("/") -def index(): - return "Running!" - - -@app.route("/segmentation/", methods=('POST',)) -@cross_origin() -def segment_image(session_id): - image_str = flask.request.json['image'] - image_data_str = image_str[image_str.find(',') + 1:] - image_data = bytes(image_data_str, encoding="ascii") - image = ImageOps.exif_transpose(Image.open(BytesIO(base64.b64decode(image_data))).convert('L')) - ratio = min(MAX_HEIGHT/image.size[0], MAX_WIDTH/image.size[1], 1) - new_size = int(ratio*image.size[0]), int(ratio*image.size[1]) - resized_image = image.resize(new_size, Image.ANTIALIAS) - image_array = np.array(resized_image.getdata(), dtype='uint8').reshape((*resized_image.size[-1::-1], 1)) - segmentation_results, segmentation_structure = xy_segmentation(image_array) - message = json.dumps({'segmentation_results': [array.tolist() for array in segmentation_results], - 'segmentation_structure': segmentation_structure.serialize(), - 'session_id': session_id}) - kafka.send_message(producer, 'segmentation', message) - return {"status": "sent for processing"}, 200 - - -if __name__ == '__main__': - app.run(host='0.0.0.0', debug=(LOG_LEVEL == 'DEBUG'), port=8003) diff --git a/solver/__init__.py b/solver/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/solver/solver.py b/solver/solver.py deleted file mode 100644 index 4c2e95f7..00000000 --- a/solver/solver.py +++ /dev/null @@ -1,28 +0,0 @@ -import logging -import sympy -from latex2sympy2 import latex2sympy - - -class Solver: - def __init__(self, latex_eq: str, function: str): - if not isinstance(latex_eq, str): - raise TypeError("latex_eq arg should be of type string") - if not isinstance(function, str): - raise TypeError("function arg should be of type string") - if not latex_eq: - self.latex_solution = '' - return - self.equation = sympy.Eq(*[latex2sympy(eq_side) for eq_side in latex_eq.split('=')]) - logging.debug(self.equation) - t = sympy.Symbol('t') - - self.solution = sympy.dsolve(self.equation.subs(function, sympy.Function(function)(t))) - logging.debug(self.solution) - self.latex_solution = sympy.latex(self.solution) - logging.debug(self.latex_solution) - - -if __name__ == '__main__': # pragma: no cover - logging.basicConfig(level=logging.DEBUG) - solver = Solver(r'\frac{d}{dt}y=y', 'y') - diff --git a/solver_app.py b/solver_app.py deleted file mode 100644 index 80810f5e..00000000 --- a/solver_app.py +++ /dev/null @@ -1,29 +0,0 @@ -import flask -from flask_socketio import SocketIO - -from solver.solver import Solver -from settings import NEXT_URL -app = flask.Flask(__name__) -app.config['SECRET_KEY'] = 'secret!' - -socketio = SocketIO(app, cors_allowed_origins=NEXT_URL) - - -@app.route("/") -def index(): - return "Running!" - - -@app.route("/solver") -@app.route("/solver///") -def solve_equation(latex_eq='', function=''): - return {'equation': latex_eq, 'solution': Solver(latex_eq, function).latex_solution} - - -@socketio.on('equation') -def solve_equation(data): - socketio.emit('solution', Solver(data['equation'], data['function']).latex_solution) - - -if __name__ == '__main__': - socketio.run(app, debug=True) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/context.py b/tests/context.py deleted file mode 100644 index 8d2e6c41..00000000 --- a/tests/context.py +++ /dev/null @@ -1,8 +0,0 @@ -import os -import sys -sys.path.insert(0, os.path.abspath('..')) - -import parsing -import segmentation -import classification -from solver import solver \ No newline at end of file diff --git a/tests/test_solver.py b/tests/test_solver.py deleted file mode 100644 index 89f1f5bb..00000000 --- a/tests/test_solver.py +++ /dev/null @@ -1,25 +0,0 @@ -import unittest - -from .context import solver - - -class TestSolver(unittest.TestCase): - def test_should_raise_exception_on_non_string_inputs(self): - self.assertRaises(TypeError, solver.Solver, 1, '') - self.assertRaises(TypeError, solver.Solver, '', 1) - - def test_should_return_empty_string_on_empty_string_input(self): - latex_solution = solver.Solver('', '').latex_solution - self.assertEqual(latex_solution, '') - - def test_should_return_constant_when_derivative_is_zero(self): - latex_solution = solver.Solver(r'\frac{d}{dt}y=0', 'y').latex_solution - self.assertEqual(latex_solution, r'y{\left(t \right)} = C_{1}') - - def test_should_return_exp_when_derivative_is_the_function(self): - latex_solution = solver.Solver(r'\frac{d}{dt}y=y', 'y').latex_solution - self.assertEqual(latex_solution, r'y{\left(t \right)} = C_{1} e^{t}') - - -if __name__ == '__main__': - unittest.main() diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/utils/utils.py b/utils/utils.py deleted file mode 100644 index aeae0ac5..00000000 --- a/utils/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Any, List - -import matplotlib.pyplot as plt -import numpy as np - - -def check_list_types(checked_list: List, checked_type: Any) -> bool: - """Checks if all the elements of a list are of the specified type""" - if not isinstance(checked_list, list): - return False - return all(isinstance(checked_list_element, checked_type) for checked_list_element in checked_list) - - -def show_image(im: np.ndarray, title: str, *, cmap: str = 'gray') -> None: - """Plots the input image with pyplot""" - # axes for debugging purposes - fig, ax = plt.subplots() - ax.set_title(title) - ax.imshow(im, cmap=cmap) - plt.show() - - -def normalize_img(image: np.ndarray) -> np.ndarray: - """Transform an image to 0-255 range""" - print(f"min = {np.amin(image)}, {np.amax(image)}") - zero_image = image - np.amin(image) - print(f"max {np.amax(zero_image)}") - return (zero_image * 255.0 / np.amax(zero_image)).astype('uint8')