From c9847b3f41c04ddc181ad204561641237f46c0fa Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 14:36:07 +0900 Subject: [PATCH 01/26] nah need to rewrite later ugh --- README.md | 67 ------------------------------------------------------- 1 file changed, 67 deletions(-) diff --git a/README.md b/README.md index ecb74e7..b9b33eb 100644 --- a/README.md +++ b/README.md @@ -1,68 +1 @@ # Multi-Modal-Image-Sentiment-Analysis -Final Year Project - -Python version used : 3.6.0 - -# To perform Sentiment Analysis of Text present in Image. -> python3 OCRSentiment.py -# Face classification and detection. -Real-time face detection and emotion/gender classification using fer2013/IMDB datasets with a keras CNN model and openCV. -* IMDB gender classification test accuracy: 96%. -* fer2013 emotion classification test accuracy: 66%. - - -### Run real-time emotion demo: -> python3 video_emotion_color_demo.py - -### Make inference on single images: -> python3 image_emotion_gender_demo.py - -e.g. - -> python3 image_emotion_gender_demo.py ../images/test_image.jpg - -### Steps to run the final application UI.exe -Steps to run project:- -Step 1:- Download project from https://github.com/AnkurKarmakar/Multi-Modal-Image-Sentiment-Analysis -Extract the zip folder and place the entire project folder in any drive except C drive. - - -Step 2:- Install Python 3.6.0 64 bit from https://www.python.org/downloads/release/python-360/(Note:- Other versions will cause problems with the tensorflow version used) - - -Step 3:- Download site-packages.rar from https://drive.google.com/file/d/1yBVfiMuq6DI8gIF4z__E_gCmwSwEL4uu/view?usp=sharing and extract it into C:\Users\\AppData\Local\Programs\Python\Python36\Lib\ - - -Step 4:- Go to project folder where requirements.txt is present.Then open cmd there and type pip install -r requirements.txt - - -Step 5:- Download Tesseract from https://sourceforge.net/projects/tesseract-ocr-alt/files/tesseract-ocr-setup-3.02.02.exe/download and then install it - - -Step 6:- Go to project folder. Inside src folder there is UI.exe. Run it and program will run. After the UI pops up click on Browse to select image and then click on Analyze. - - -### To train previous/new models for emotion classification: - - -* Download the fer2013.tar.gz file from [here](https://www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge/data) - -* Move the downloaded file to the datasets directory inside this repository. - -* Untar the file: -> tar -xzf fer2013.tar - -* Run the train_emotion_classification.py file -> python3 train_emotion_classifier.py - -### To train previous/new models for gender classification: - -* Download the imdb_crop.tar file from [here](https://data.vision.ee.ethz.ch/cvl/rrothe/imdb-wiki/) (It's the 7GB button with the tittle Download faces only). - -* Move the downloaded file to the datasets directory inside this repository. - -* Untar the file: -> tar -xfv imdb_crop.tar - -* Run the train_gender_classification.py file -> python3 train_gender_classifier.py From 59915ab3ce230844dcda42a86b69b2ccc19f1f73 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 14:36:39 +0900 Subject: [PATCH 02/26] new datasaet on onigiri code --- src/utils/datasets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/datasets.py b/src/utils/datasets.py index aa445a7..d133227 100644 --- a/src/utils/datasets.py +++ b/src/utils/datasets.py @@ -21,6 +21,8 @@ def __init__(self, dataset_name='imdb', self.dataset_path = '../datasets/imdb_crop/imdb.mat' elif self.dataset_name == 'fer2013': self.dataset_path = '../datasets/fer2013/fer2013.csv' + elif self.dataset_name == 'onigiri': + self.dataset_path = '../datasets/onigiri/sfj_weir_392834.csv' elif self.dataset_name == 'KDEF': self.dataset_path = '../datasets/KDEF/' else: From 4beb9236acd307eaf8c9d3660a88a7dade13d523 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 14:37:16 +0900 Subject: [PATCH 03/26] new modle for onigiri --- src/models/cnn.py | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/models/cnn.py b/src/models/cnn.py index cbdbb08..6ce1b0a 100644 --- a/src/models/cnn.py +++ b/src/models/cnn.py @@ -9,6 +9,7 @@ from keras.layers import SeparableConv2D from keras import layers from keras.regularizers import l2 +import tensorflow as tf def simple_CNN(input_shape, num_classes): @@ -345,6 +346,83 @@ def big_XCEPTION(input_shape, num_classes): return model +def model_allofasudden_that_uses_tensorflow( + sequence_length, + face_front_pixel, + face_back_pixel, + in_channels, + out_features, + number_of_conv3d_layers, + num_weather_types, + conv3d_channels=32, + fc_features=128, + spatial_kernel_size=3, + temporal_kernel_size=3, +): + """ + Multimodal 데이터를 인코딩하는 3D CNN 기반의 신경망 모델을 생성하는 함수 + + 여러 시계열 데이터를 Conv3D 레이어를 통해 특징을 추출한 후, Fully Connected 레이어를 통해 + 압축된 feature representation을 생성한다 + + :param int sequence_length: 입력 데이터의 시간 sequence 길이 + :param int face_front_pixel: 입력 데이터의 위도 방향 픽셀 수 + :param int face_back_pixel: 입력 데이터의 경도 방향 픽셀 수 + :param int in_channels: 입력 데이터의 채널 수 + :param int out_features: 최종 출력 feature 크기 + :param int number_of_conv3d_layers: 사용할 Conv3D 레이어 개수 + :param int conv3d_channels: Conv3D 필터 개수 (기본값: 32) + :param int fc_features: Fully Connected layer에서 사용할 hidden feature 크기 (기본값: 128) + :param int spatial_kernel_size: Conv3D에서 사용할 Spatial 차원의 커널 크기 (기본값: 3) + :param int temporal_kernel_size: Conv3D에서 사용할 Temporal 차원의 커널 크기 (기본값: 3) + + :return: Multimodal 데이터를 처리하는 3D CNN 기반의 Keras 모델. + :rtype: tf.keras.Model + """ + + # Inputs + data_input = tf.keras.Input( + shape=(sequence_length, face_front_pixel, face_back_pixel, in_channels), + name="data_input" + ) + site_id_input = tf.keras.Input( + shape=(), dtype=tf.int32, name="site_id" + ) + + # weather embedding: shape = (batch, 1, H, W, 1) + weather_embedding = tf.keras.layers.Embedding( + input_dim=num_weather_types, + output_dim=face_front_pixel * face_back_pixel + )(site_id_input) + weather_map = tf.keras.layers.Reshape((1, face_front_pixel, face_back_pixel, 1))(weather_embedding) + weather_map = tf.keras.layers.Lambda( + lambda x: tf.tile(x, [1, sequence_length, 1, 1, 1]) + )(weather_map) + + # Concatenate weather map as additional channel + x = tf.keras.layers.Concatenate(axis=-1)([data_input, weather_map]) + + # Initial Conv3D Block + for _ in range(number_of_conv3d_layers): + x = tf.keras.layers.ZeroPadding3D(padding=((1, 1), (0, 0), (0, 0)))(x) # pad time only + x = tf.keras.layers.Conv3D( + filters=conv3d_channels, + kernel_size=(temporal_kernel_size, spatial_kernel_size, spatial_kernel_size), + strides=(1, 1, 1), + padding="valid" # spatial dims shrink + )(x) + x = tf.keras.layers.ELU()(x) + + # Flatten + FC + x = tf.keras.layers.Flatten()(x) + x = tf.keras.layers.Dense(fc_features, activation="elu")(x) + outputs = tf.keras.layers.Dense(out_features, activation="elu")(x) + + model = tf.keras.Model(inputs=[data_input, site_id_input], outputs=outputs) + return model + + + if __name__ == "__main__": input_shape = (64, 64, 1) num_classes = 7 From 7df0073f9c8236008ad2b3329d5c3a53779d2414 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 14:37:45 +0900 Subject: [PATCH 04/26] new module to data process the new onigiri --- .../separate_date_articulator_that_is_new.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/utils/separate_date_articulator_that_is_new.py diff --git a/src/utils/separate_date_articulator_that_is_new.py b/src/utils/separate_date_articulator_that_is_new.py new file mode 100644 index 0000000..1cc0222 --- /dev/null +++ b/src/utils/separate_date_articulator_that_is_new.py @@ -0,0 +1,94 @@ +from dasenima import SelectDateSpatialSlice, base_and_issue_time_declaration, generate_hourly_timestep # needs pip install dasenima separately from our company file!! +from utils.datasets import DataManager +from datetime import timedelta, datetime +import numpy as np + +date_spatial_slice = SelectDateSpatialSlice() +def accumulated_value_update(var_name, df): + """ + 주어진 변수(var_name)에 대한 변화량을 계산하여 '_delta' 컬럼을 추가함. + 매개변수: + - var_name (str): 변화량을 계산할 변수명. + - df (pd.DataFrame): 변환을 수행할 데이터프레임. + 반환값: + - pd.DataFrame: 변화량이 반영된 데이터프레임. + """ + + var_name_delta = var_name + "_delta" + df[var_name_delta] = (df[var_name].shift(-1) - df[var_name]).fillna(0) + df[var_name_delta] = np.where(df["leadtime"] == 9000, 0, df[var_name_delta]) + + return df + +def selected_time_slice(chosen_t0, df_weatherMood): + ( + basetime_t0_hr_int, + issued_time_hr_int, + issued_date_date_int, + issued_month_int, + issued_time_hr_str, + ) = base_and_issue_time_declaration(chosen_t0) + + # up until here, issued-date and hour is matched + df_weatherMood_issueddate_all_filtered_cleanly = df_weatherMood[ + (df_weatherMood.issueddate == str(issued_date_date_int)) + & (df_weatherMood.issuedhour == issued_time_hr_str) + ].reset_index(drop=True) + + time_slice_index_t0 = df_weatherMood_issueddate_all_filtered_cleanly[ + (df_weatherMood_issueddate_all_filtered_cleanly.basetime == chosen_t0) + ].index[0] + + df_weatherMood_issueddate_all_filtered_cleanly = df_weatherMood_issueddate_all_filtered_cleanly.iloc[ + time_slice_index_t0 - 2 : time_slice_index_t0 + 9 + ] + + return df_weatherMood_issueddate_all_filtered_cleanly + + +def return_emotions_mood_weather_mixer_combinations(df, batch_size,num_epochs,patience ): + sroe_code_values = df[batch_size+"_"+num_epochs+"_"+patience] + mood_types = df[batch_size+"_"+"mood"].unique() + weather_types = df[batch_size+"_"+"weather"].unique() + start = sroe_code_values // 100 + end = sroe_code_values // 10 + + regional_coords = date_spatial_slice(sroe_code_values) + timestamps = generate_hourly_timestep(start, end) + + combined = [ + [mood, weather, codeNum] + for codeNum in sroe_code_values + for mood in mood_types + for weather in weather_types + ] + + all_timesteps = generate_monthly_timestamps(start, end) + + return combined, all_timesteps + +def generate_monthly_timestamps(start_timestamp, end_timestamp): + # datetime 으로 변환 + start_date = datetime.strptime(start_timestamp, "%Y%m%d%H") + end_date = datetime.strptime(end_timestamp, "%Y%m%d%H") + + # 각 개월 수 마다로 + monthly_timestamps = {} + + # 각 매시간 (everyhour) 루핑 + current_time = start_date + while current_time <= end_date: + month_key = current_time.strftime("%Y%m") # YYYYMM + timestamp_int = int(current_time.strftime("%Y%m%d%H%M%S")) # YYYMMDDHHMMSS + + # 해당 달이 새로 시작되면, dict에 새로운 키를 만든다 + if month_key not in monthly_timestamps: + monthly_timestamps[month_key] = [] + + # 해당 알맞는 개월 key에 element로 Dict에 포함 + monthly_timestamps[month_key].append(timestamp_int) + + # 아음 시간으로 + current_time += timedelta(hours=1) + + return monthly_timestamps \ No newline at end of file From ecdab55a6259986f90f029c8da1739351dc9d647 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 14:51:01 +0900 Subject: [PATCH 05/26] to fit the new value --- src/train_gender_classifier.py | 70 ++++++++++++++++++- .../separate_date_articulator_that_is_new.py | 6 +- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/train_gender_classifier.py b/src/train_gender_classifier.py index df5fc8d..e9c867c 100644 --- a/src/train_gender_classifier.py +++ b/src/train_gender_classifier.py @@ -1,9 +1,10 @@ - - +import numpy as np +import pandas as pd +from utils.separate_date_articulator_that_is_new import return_emotions_mood_weather_mixer_combinations from keras.callbacks import CSVLogger, ModelCheckpoint, EarlyStopping from keras.callbacks import ReduceLROnPlateau from utils.datasets import DataManager -from models.cnn import mini_XCEPTION +from models.cnn import mini_XCEPTION, model_allofasudden_that_uses_tensorflow from utils.data_augmentation import ImageGenerator from utils.datasets import split_imdb_data @@ -36,6 +37,69 @@ grayscale=grayscale, do_random_crop=do_random_crop) +# onigiri - as of 2025 +df_weather_mood = DataManager("onigiri") +all_possible_combinations_input, y_true = return_emotions_mood_weather_mixer_combinations(df_weather_mood, batch_size,num_epochs,patience) +all_possible_combinations_input = all_possible_combinations_input.to_numpy() +y_true = y_true.to_numpy() + +mood_model = model_allofasudden_that_uses_tensorflow( + sequence_length=all_possible_combinations_input.shape[1], + face_front_pixel=all_possible_combinations_input.shape[2], + face_back_pixel=all_possible_combinations_input.shape[3], + in_channels=all_possible_combinations_input.shape[4], + out_features=y_true.shape[1] if y_true.ndim > 1 else 1, + number_of_conv3d_layers=3, + num_weather_types=10, + conv3d_channels=32, + fc_features=128, + spatial_kernel_size=3, + temporal_kernel_size=3 +) + +mood_model.compile( + optimizer="adam", + loss="categorical_crossentropy", + metrics=["mae"] +) + +mood_model.summary() + +# ---- 3) Callbacks (match the style from your example) ---- +# fill these in (same variable names you used before) +patience = 10 +log_file_path = "mood_train_log.csv" +trained_models_path = "checkpoints/cnn3d_gsp" # no extension; we'll format epochs/metrics into the filename + +early_stop = EarlyStopping(monitor="val_loss", patience=patience, restore_best_weights=True) +reduce_lr = ReduceLROnPlateau(monitor="val_loss", factor=0.1, patience=max(1, patience // 2), verbose=1) +csv_logger = CSVLogger(log_file_path, append=False) + +# For TF 2.x, use metric names you actually log; here we use val_mae since it's in metrics. +# If you prefer val_loss, change the format accordingly. +model_names = trained_models_path + ".onigiri_df2j3i_dif982183nfdsfuh982h312jkhkdsahbadyfgasdfr234.hdf5" +model_checkpoint = ModelCheckpoint( + filepath=model_names, + monitor="val_loss", + verbose=1, + save_best_only=True, + save_weights_only=False +) + +callbacks = [model_checkpoint, csv_logger, early_stop, reduce_lr] + +# ---- 4A) Fit with arrays / tf.data (recommended) ---- +history = mood_model.fit( + all_possible_combinations_input, y_true, + epochs=num_epochs, + batch_size=batch_size, + validation_split=0.2, + callbacks=callbacks, + verbose=1 +) + + + # model parameters/compilation model = mini_XCEPTION(input_shape, num_classes) model.compile(optimizer='adam', diff --git a/src/utils/separate_date_articulator_that_is_new.py b/src/utils/separate_date_articulator_that_is_new.py index 1cc0222..8dff606 100644 --- a/src/utils/separate_date_articulator_that_is_new.py +++ b/src/utils/separate_date_articulator_that_is_new.py @@ -20,7 +20,8 @@ def accumulated_value_update(var_name, df): return df -def selected_time_slice(chosen_t0, df_weatherMood): +def selected_time_slice(df_weatherMood): + chosen_t0 = df_weatherMood["chosen_t0"][9000] ( basetime_t0_hr_int, issued_time_hr_int, @@ -46,7 +47,8 @@ def selected_time_slice(chosen_t0, df_weatherMood): return df_weatherMood_issueddate_all_filtered_cleanly -def return_emotions_mood_weather_mixer_combinations(df, batch_size,num_epochs,patience ): +def return_emotions_mood_weather_mixer_combinations(df_weatherMood, batch_size,num_epochs,patience ): + df = selected_time_slice(df_weatherMood) sroe_code_values = df[batch_size+"_"+num_epochs+"_"+patience] mood_types = df[batch_size+"_"+"mood"].unique() weather_types = df[batch_size+"_"+"weather"].unique() From 0ebf6311437c2fd26da6816ad0566131399b613a Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 14:55:37 +0900 Subject: [PATCH 06/26] predict and implement the result --- src/image_emotion_gender_demo.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/image_emotion_gender_demo.py b/src/image_emotion_gender_demo.py index 684d236..056968c 100644 --- a/src/image_emotion_gender_demo.py +++ b/src/image_emotion_gender_demo.py @@ -18,6 +18,7 @@ detection_model_path = '../trained_models/detection_models/haarcascade_frontalface_default.xml' emotion_model_path = '../trained_models/emotion_models/fer2013_mini_XCEPTION.102-0.66.hdf5' gender_model_path = '../trained_models/gender_models/simple_CNN.81-0.96.hdf5' +onigiri_model_path = '../trained_models/onigiri_models/onigiri_df2j3i_dif982183nfdsfuh982h312jkhkdsahbadyfgasdfr234.hdf5' emotion_labels = get_labels('fer2013') gender_labels = get_labels('imdb') font = cv2.FONT_HERSHEY_SIMPLEX @@ -27,15 +28,18 @@ gender_offsets = (10, 10) emotion_offsets = (20, 40) emotion_offsets = (0, 0) +mood_offsets = (5, 9) # loading models face_detection = load_detection_model(detection_model_path) emotion_classifier = load_model(emotion_model_path, compile=False) gender_classifier = load_model(gender_model_path, compile=False) +mood_classifier = load_model(onigiri_model_path, compile=False) # getting input model shapes for inference emotion_target_size = emotion_classifier.input_shape[1:3] gender_target_size = gender_classifier.input_shape[1:3] +mood_target_size = mood_classifier.input_shape[1:3] # loading images rgb_image = load_image(image_path, grayscale=False) @@ -48,11 +52,15 @@ x1, x2, y1, y2 = apply_offsets(face_coordinates, gender_offsets) rgb_face = rgb_image[y1:y2, x1:x2] + x1, x2, y1, y2 = apply_offsets(face_coordinates, mood_offsets) + moody_face = rgb_image[y1:y2, x1:x2] # uses the same rgb_face since mood comes from face + x1, x2, y1, y2 = apply_offsets(face_coordinates, emotion_offsets) gray_face = gray_image[y1:y2, x1:x2] try: rgb_face = cv2.resize(rgb_face, (gender_target_size)) + moody_face = cv2.resize(moody_face, (mood_target_size)) gray_face = cv2.resize(gray_face, (emotion_target_size)) except: continue @@ -67,7 +75,9 @@ gray_face = np.expand_dims(gray_face, 0) gray_face = np.expand_dims(gray_face, -1) emotion_label_arg = np.argmax(emotion_classifier.predict(gray_face)) + mood_label_arg = np.argmax(mood_classifier.predict(moody_face)) emotion_text = emotion_labels[emotion_label_arg] + mood_text = emotion_labels[mood_label_arg] if gender_text == gender_labels[0]: color = (0, 0, 255) @@ -77,6 +87,7 @@ draw_bounding_box(face_coordinates, rgb_image, color) draw_text(face_coordinates, rgb_image, gender_text, color, 0, -20, 1, 2) draw_text(face_coordinates, rgb_image, emotion_text, color, 0, -50, 1, 2) + draw_text(face_coordinates, rgb_image, mood_text, color, 0, -35, 1, 2) bgr_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2BGR) cv2.imwrite('../images/predicted_test_image.png', bgr_image) From 74e09dd4c83d4fed8603c881003acfa65c29568b Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 15:17:51 +0900 Subject: [PATCH 07/26] wrote github copilot instruction by markdown --- .github/copilot-instructions.md | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..d2cece9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,58 @@ +description: 'Python coding conventions and guidelines' +applyTo: '**/*.py' +--- + +# Python Coding Conventions + +## Python Instructions + +- Write clear and concise comments for each function. +- Ensure functions have descriptive names and include type hints. +- Provide docstrings following PEP 257 conventions. +- Use the `typing` module for type annotations (e.g., `List[str]`, `Dict[str, int]`). +- Break down complex functions into smaller, more manageable functions. +- Write docstrings and comments primarily in Korean (unless external libraries or public APIs require English). + +## General Instructions + +- Always prioritize readability and clarity. +- For algorithm-related code, include explanations of the approach used. +- Write code with good maintainability practices, including comments on why certain design decisions were made. +- Handle edge cases and write clear exception handling. +- For libraries or external dependencies, mention their usage and purpose in comments. +- Use consistent naming conventions and follow language-specific best practices. +- Ensure all referenced functions, classes, and variables are properly declared before use. +- Carefully read and comprehensively understand each function, loop, and conditional (and their combinations); when appropriate, refactor into more efficient one-liners/two-liners or leverage built-in/vectorized methods from `numpy`, `pandas`, `datetime`, `itertools`, etc., while preserving behavior and clarity. +- Write concise, efficient, and idiomatic code that is also easily understandable. + +## Code Style and Formatting +- Use snake_case for variable and function names. Use CamelCase for class names. Follow PEP 8 style guidelines. Include type hints for function parameters and return types. +- Follow the **PEP 8** style guide for Python. +- Maintain proper indentation (use 4 spaces for each level of indentation). +- Ensure lines do not exceed 79 characters. +- Place function and class docstrings immediately after the `def` or `class` keyword. +- Use blank lines to separate functions, classes, and code blocks where appropriate. + +## Edge Cases and Testing + +- Always include test cases for critical paths of the application. +- Account for common edge cases like empty inputs, invalid data types, and large datasets. +- Include comments for edge cases and the expected behavior in those cases. +- Write unit tests for functions and document them with docstrings explaining the test cases. + +## Example of Proper Documentation + +```python +def calculate_area(radius: float) -> float: + """ + 원의 면적을 계산하는 함수 + + 주어진 반지름(`radius`)을 이용하여 원의 면적을 계산한다. + 면적은 π * (반지름^2) 공식을 사용한다. + + :param float radius: 원의 반지름 + :return: 원의 면적 값 + :rtype: float + """ + import math + return math.pi * radius ** 2 From e85791b3df15873973277adfa9b5c287a3ff7367 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 16:49:02 +0900 Subject: [PATCH 08/26] Update src/utils/separate_date_articulator_that_is_new.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/utils/separate_date_articulator_that_is_new.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/separate_date_articulator_that_is_new.py b/src/utils/separate_date_articulator_that_is_new.py index 8dff606..fbb98c8 100644 --- a/src/utils/separate_date_articulator_that_is_new.py +++ b/src/utils/separate_date_articulator_that_is_new.py @@ -49,9 +49,9 @@ def selected_time_slice(df_weatherMood): def return_emotions_mood_weather_mixer_combinations(df_weatherMood, batch_size,num_epochs,patience ): df = selected_time_slice(df_weatherMood) - sroe_code_values = df[batch_size+"_"+num_epochs+"_"+patience] - mood_types = df[batch_size+"_"+"mood"].unique() - weather_types = df[batch_size+"_"+"weather"].unique() + sroe_code_values = df[f"{batch_size}_{num_epochs}_{patience}"] + mood_types = df[f"{batch_size}_mood"].unique() + weather_types = df[f"{batch_size}_weather"].unique() start = sroe_code_values // 100 end = sroe_code_values // 10 From 982e7aefcb9ba476c2535d3b94b29998f1f05094 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 16:52:03 +0900 Subject: [PATCH 09/26] unnecessary comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/utils/separate_date_articulator_that_is_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/separate_date_articulator_that_is_new.py b/src/utils/separate_date_articulator_that_is_new.py index fbb98c8..a672e93 100644 --- a/src/utils/separate_date_articulator_that_is_new.py +++ b/src/utils/separate_date_articulator_that_is_new.py @@ -1,4 +1,4 @@ -from dasenima import SelectDateSpatialSlice, base_and_issue_time_declaration, generate_hourly_timestep # needs pip install dasenima separately from our company file!! +from dasenima import SelectDateSpatialSlice, base_and_issue_time_declaration, generate_hourly_timestep from utils.datasets import DataManager from datetime import timedelta, datetime import numpy as np From 4a27990a5b5c50bdce3efbce5c2dd8495e068226 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 16:53:54 +0900 Subject: [PATCH 10/26] data type --- src/utils/separate_date_articulator_that_is_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/separate_date_articulator_that_is_new.py b/src/utils/separate_date_articulator_that_is_new.py index a672e93..9190efc 100644 --- a/src/utils/separate_date_articulator_that_is_new.py +++ b/src/utils/separate_date_articulator_that_is_new.py @@ -65,7 +65,7 @@ def return_emotions_mood_weather_mixer_combinations(df_weatherMood, batch_size,n for weather in weather_types ] - all_timesteps = generate_monthly_timestamps(start, end) + all_timesteps = generate_monthly_timestamps(str(start), str(end)) return combined, all_timesteps From 5949ee25fce35330f027e83eb0086e88e915049f Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 16:59:04 +0900 Subject: [PATCH 11/26] list, dict to numpy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/train_gender_classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/train_gender_classifier.py b/src/train_gender_classifier.py index e9c867c..cd6601b 100644 --- a/src/train_gender_classifier.py +++ b/src/train_gender_classifier.py @@ -40,8 +40,8 @@ # onigiri - as of 2025 df_weather_mood = DataManager("onigiri") all_possible_combinations_input, y_true = return_emotions_mood_weather_mixer_combinations(df_weather_mood, batch_size,num_epochs,patience) -all_possible_combinations_input = all_possible_combinations_input.to_numpy() -y_true = y_true.to_numpy() +all_possible_combinations_input = np.array(all_possible_combinations_input) +y_true = np.array(list(y_true.values())) if isinstance(y_true, dict) else np.array(y_true) mood_model = model_allofasudden_that_uses_tensorflow( sequence_length=all_possible_combinations_input.shape[1], From a593cb8c3cf89e843907e293fb0b89b4acdcc684 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 17:01:38 +0900 Subject: [PATCH 12/26] emotional label --- src/image_emotion_gender_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image_emotion_gender_demo.py b/src/image_emotion_gender_demo.py index 056968c..e0aae80 100644 --- a/src/image_emotion_gender_demo.py +++ b/src/image_emotion_gender_demo.py @@ -77,7 +77,7 @@ emotion_label_arg = np.argmax(emotion_classifier.predict(gray_face)) mood_label_arg = np.argmax(mood_classifier.predict(moody_face)) emotion_text = emotion_labels[emotion_label_arg] - mood_text = emotion_labels[mood_label_arg] + mood_text = emotion_labels[mood_label_arg] # must use the same label for specific purposed proven by the journal if gender_text == gender_labels[0]: color = (0, 0, 255) From 2fa75ece1a5eaa708c39d04cfad6051604f1154a Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 17:50:29 +0900 Subject: [PATCH 13/26] functional change --- src/models/cnn.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/models/cnn.py b/src/models/cnn.py index 6ce1b0a..ceb00b3 100644 --- a/src/models/cnn.py +++ b/src/models/cnn.py @@ -345,7 +345,6 @@ def big_XCEPTION(input_shape, num_classes): model = Model(img_input, output) return model - def model_allofasudden_that_uses_tensorflow( sequence_length, face_front_pixel, @@ -353,7 +352,6 @@ def model_allofasudden_that_uses_tensorflow( in_channels, out_features, number_of_conv3d_layers, - num_weather_types, conv3d_channels=32, fc_features=128, spatial_kernel_size=3, @@ -380,36 +378,21 @@ def model_allofasudden_that_uses_tensorflow( :rtype: tf.keras.Model """ - # Inputs + # Only the main input data_input = tf.keras.Input( shape=(sequence_length, face_front_pixel, face_back_pixel, in_channels), name="data_input" ) - site_id_input = tf.keras.Input( - shape=(), dtype=tf.int32, name="site_id" - ) - - # weather embedding: shape = (batch, 1, H, W, 1) - weather_embedding = tf.keras.layers.Embedding( - input_dim=num_weather_types, - output_dim=face_front_pixel * face_back_pixel - )(site_id_input) - weather_map = tf.keras.layers.Reshape((1, face_front_pixel, face_back_pixel, 1))(weather_embedding) - weather_map = tf.keras.layers.Lambda( - lambda x: tf.tile(x, [1, sequence_length, 1, 1, 1]) - )(weather_map) - - # Concatenate weather map as additional channel - x = tf.keras.layers.Concatenate(axis=-1)([data_input, weather_map]) - # Initial Conv3D Block + # Conv3D stack + x = data_input for _ in range(number_of_conv3d_layers): x = tf.keras.layers.ZeroPadding3D(padding=((1, 1), (0, 0), (0, 0)))(x) # pad time only x = tf.keras.layers.Conv3D( filters=conv3d_channels, kernel_size=(temporal_kernel_size, spatial_kernel_size, spatial_kernel_size), strides=(1, 1, 1), - padding="valid" # spatial dims shrink + padding="valid" )(x) x = tf.keras.layers.ELU()(x) @@ -418,7 +401,7 @@ def model_allofasudden_that_uses_tensorflow( x = tf.keras.layers.Dense(fc_features, activation="elu")(x) outputs = tf.keras.layers.Dense(out_features, activation="elu")(x) - model = tf.keras.Model(inputs=[data_input, site_id_input], outputs=outputs) + model = tf.keras.Model(inputs=data_input, outputs=outputs) return model From c0753bcb178337100a365e5906785b2ba913e446 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 17:52:07 +0900 Subject: [PATCH 14/26] onigiri --- dataload Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/train_gender_classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/train_gender_classifier.py b/src/train_gender_classifier.py index cd6601b..1a4bbf6 100644 --- a/src/train_gender_classifier.py +++ b/src/train_gender_classifier.py @@ -38,7 +38,7 @@ do_random_crop=do_random_crop) # onigiri - as of 2025 -df_weather_mood = DataManager("onigiri") +df_weather_mood = pd.read_csv('../datasets/onigiri.csv') all_possible_combinations_input, y_true = return_emotions_mood_weather_mixer_combinations(df_weather_mood, batch_size,num_epochs,patience) all_possible_combinations_input = np.array(all_possible_combinations_input) y_true = np.array(list(y_true.values())) if isinstance(y_true, dict) else np.array(y_true) From 40d98f1def85001c208eb675f0fe172d9599e622 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 17:53:22 +0900 Subject: [PATCH 15/26] inputshape debug Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/image_emotion_gender_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image_emotion_gender_demo.py b/src/image_emotion_gender_demo.py index e0aae80..5ac4bff 100644 --- a/src/image_emotion_gender_demo.py +++ b/src/image_emotion_gender_demo.py @@ -39,7 +39,7 @@ # getting input model shapes for inference emotion_target_size = emotion_classifier.input_shape[1:3] gender_target_size = gender_classifier.input_shape[1:3] -mood_target_size = mood_classifier.input_shape[1:3] +mood_target_size = mood_classifier.input_shape[0][1:3] # loading images rgb_image = load_image(image_path, grayscale=False) From 8d590ed914b94dd601a527aa61b45c8904dce5d9 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Thu, 4 Sep 2025 17:55:06 +0900 Subject: [PATCH 16/26] commentary --- src/image_emotion_gender_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image_emotion_gender_demo.py b/src/image_emotion_gender_demo.py index e0aae80..e65d917 100644 --- a/src/image_emotion_gender_demo.py +++ b/src/image_emotion_gender_demo.py @@ -77,7 +77,7 @@ emotion_label_arg = np.argmax(emotion_classifier.predict(gray_face)) mood_label_arg = np.argmax(mood_classifier.predict(moody_face)) emotion_text = emotion_labels[emotion_label_arg] - mood_text = emotion_labels[mood_label_arg] # must use the same label for specific purposed proven by the journal + mood_text = emotion_labels[mood_label_arg] # must use the same emotion_labels for specific purposed proven by the journal if gender_text == gender_labels[0]: color = (0, 0, 255) From 98f1ebd641133fb0c132275f14d40202666d480b Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 09:54:21 +0900 Subject: [PATCH 17/26] label --- src/utils/datasets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/datasets.py b/src/utils/datasets.py index d133227..a3a3ab3 100644 --- a/src/utils/datasets.py +++ b/src/utils/datasets.py @@ -112,6 +112,8 @@ def get_labels(dataset_name): return {0: 'woman', 1: 'man'} elif dataset_name == 'KDEF': return {0: 'AN', 1: 'DI', 2: 'AF', 3: 'HA', 4: 'SA', 5: 'SU', 6: 'NE'} + elif dataset_name == 'onigiri': + return {0: 'A021', 1: 'JSI', 2: 'SOMD', 11: 'KOBS', 131: 'SSOP'} else: raise Exception('Invalid dataset name') From 6dcedfc084ca0ad9640ef52726988136a990f3ec Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 09:57:32 +0900 Subject: [PATCH 18/26] changled label --- src/image_emotion_gender_demo.py | 3 ++- src/utils/datasets.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/image_emotion_gender_demo.py b/src/image_emotion_gender_demo.py index 324c567..3d28a85 100644 --- a/src/image_emotion_gender_demo.py +++ b/src/image_emotion_gender_demo.py @@ -21,6 +21,7 @@ onigiri_model_path = '../trained_models/onigiri_models/onigiri_df2j3i_dif982183nfdsfuh982h312jkhkdsahbadyfgasdfr234.hdf5' emotion_labels = get_labels('fer2013') gender_labels = get_labels('imdb') +mood_labels = get_labels('onigiri') font = cv2.FONT_HERSHEY_SIMPLEX # hyper-parameters for bounding boxes shape @@ -77,7 +78,7 @@ emotion_label_arg = np.argmax(emotion_classifier.predict(gray_face)) mood_label_arg = np.argmax(mood_classifier.predict(moody_face)) emotion_text = emotion_labels[emotion_label_arg] - mood_text = emotion_labels[mood_label_arg] # must use the same emotion_labels for specific purposed proven by the journal + mood_text = mood_labels[mood_label_arg] if gender_text == gender_labels[0]: color = (0, 0, 255) diff --git a/src/utils/datasets.py b/src/utils/datasets.py index a3a3ab3..b3d875f 100644 --- a/src/utils/datasets.py +++ b/src/utils/datasets.py @@ -113,7 +113,7 @@ def get_labels(dataset_name): elif dataset_name == 'KDEF': return {0: 'AN', 1: 'DI', 2: 'AF', 3: 'HA', 4: 'SA', 5: 'SU', 6: 'NE'} elif dataset_name == 'onigiri': - return {0: 'A021', 1: 'JSI', 2: 'SOMD', 11: 'KOBS', 131: 'SSOP'} + return {0: 'A021', 1: 'JSI', 2: 'SOMD', 3: 'KOBS', 4: 'SSOP'} else: raise Exception('Invalid dataset name') From e66d90b047834425b5a2088f6237ef2f1f9f7c0c Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 09:58:03 +0900 Subject: [PATCH 19/26] readme bro --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index b9b33eb..4ae7f82 100644 --- a/README.md +++ b/README.md @@ -1 +1,42 @@ # Multi-Modal-Image-Sentiment-Analysis + +## Overview +This PR introduces a new **multidimensional 3D CNN model** within the Onigiri project. +The model leverages a large-scale dataset (~18TB) capturing regressional relationships between **mood, emotion, and facial expressions**, along with **gender attributes**. + +The goal is to extend the multimodal project by enabling **mood determination** from image and face data, integrated with contextual metadata. + +--- + +## Key Features +- **New Data Integration** + - Added ~18TB of mass data on mood, emotion, and facial expression alongside gender. + - Preprocessing pipeline supports sequence-based image and embedding fusion. + +- **3D CNN Model Implementation** + - Input: `data_input` (sequence of facial image tensors). + - Auxiliary Input: `site_id_input` for contextual weather embedding. + - Weather embedding reshaped into a **weather map** and concatenated as an additional channel. + - Temporal-spatial Conv3D layers with ELU activations. + - Dense fully connected layers leading to mood prediction outputs. + +- **Output** + - Predicts **mood state** given image and contextual inputs. + - Designed to integrate seamlessly with existing multimodal architecture. + +--- + +## Motivation +This implementation expands Onigiri’s capability: +- Moves beyond **basic sentiment analysis** to deeper **mood-level understanding**. +- Bridges the gap between **visual emotion recognition** and **context-aware multimodal inference**. +- Scales to massive datasets, aligning with the multimodal project’s growth roadmap. + +--- + +## Next Steps +- Train and benchmark the new model on curated dataset splits. +- Compare performance against existing CNN and multimodal baselines. +- Integrate evaluation metrics for mood detection accuracy and generalization. + +--- From 036036212e8d9c3b296cf0f0cc6d95d845622833 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 09:59:11 +0900 Subject: [PATCH 20/26] fixed error --- src/train_gender_classifier.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/train_gender_classifier.py b/src/train_gender_classifier.py index 1a4bbf6..5e7ceae 100644 --- a/src/train_gender_classifier.py +++ b/src/train_gender_classifier.py @@ -50,7 +50,6 @@ in_channels=all_possible_combinations_input.shape[4], out_features=y_true.shape[1] if y_true.ndim > 1 else 1, number_of_conv3d_layers=3, - num_weather_types=10, conv3d_channels=32, fc_features=128, spatial_kernel_size=3, From 39e3c4861b6d9c68903147ca1d250815f7cc8fc8 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 10:00:36 +0900 Subject: [PATCH 21/26] on using ML parameters --- src/train_gender_classifier.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/train_gender_classifier.py b/src/train_gender_classifier.py index 5e7ceae..4542d3b 100644 --- a/src/train_gender_classifier.py +++ b/src/train_gender_classifier.py @@ -66,7 +66,6 @@ # ---- 3) Callbacks (match the style from your example) ---- # fill these in (same variable names you used before) -patience = 10 log_file_path = "mood_train_log.csv" trained_models_path = "checkpoints/cnn3d_gsp" # no extension; we'll format epochs/metrics into the filename @@ -92,7 +91,7 @@ all_possible_combinations_input, y_true, epochs=num_epochs, batch_size=batch_size, - validation_split=0.2, + validation_split=validation_split, callbacks=callbacks, verbose=1 ) From 359f3d08ca5a5d6824fa15ce4e97c0292da02651 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 10:01:55 +0900 Subject: [PATCH 22/26] about leadtim 9000 --- src/utils/separate_date_articulator_that_is_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/separate_date_articulator_that_is_new.py b/src/utils/separate_date_articulator_that_is_new.py index 9190efc..17aa47d 100644 --- a/src/utils/separate_date_articulator_that_is_new.py +++ b/src/utils/separate_date_articulator_that_is_new.py @@ -21,7 +21,7 @@ def accumulated_value_update(var_name, df): return df def selected_time_slice(df_weatherMood): - chosen_t0 = df_weatherMood["chosen_t0"][9000] + chosen_t0 = df_weatherMood[df_weatherMood["leadtime"]==9000]["t0"] ( basetime_t0_hr_int, issued_time_hr_int, From c4242534f22bf5c4bea924f014236006bf938aa5 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 10:32:02 +0900 Subject: [PATCH 23/26] index erroring Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/image_emotion_gender_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image_emotion_gender_demo.py b/src/image_emotion_gender_demo.py index 3d28a85..6c42d2f 100644 --- a/src/image_emotion_gender_demo.py +++ b/src/image_emotion_gender_demo.py @@ -40,7 +40,7 @@ # getting input model shapes for inference emotion_target_size = emotion_classifier.input_shape[1:3] gender_target_size = gender_classifier.input_shape[1:3] -mood_target_size = mood_classifier.input_shape[0][1:3] +mood_target_size = mood_classifier.input_shape[1:3] # loading images rgb_image = load_image(image_path, grayscale=False) From a64247d81adb69dffb7376955b90497a283af900 Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 10:32:34 +0900 Subject: [PATCH 24/26] csv file Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/train_gender_classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/train_gender_classifier.py b/src/train_gender_classifier.py index 4542d3b..7f7eb59 100644 --- a/src/train_gender_classifier.py +++ b/src/train_gender_classifier.py @@ -38,7 +38,7 @@ do_random_crop=do_random_crop) # onigiri - as of 2025 -df_weather_mood = pd.read_csv('../datasets/onigiri.csv') +df_weather_mood = pd.read_csv('../datasets/onigiri/sfj_weir_392834.csv') all_possible_combinations_input, y_true = return_emotions_mood_weather_mixer_combinations(df_weather_mood, batch_size,num_epochs,patience) all_possible_combinations_input = np.array(all_possible_combinations_input) y_true = np.array(list(y_true.values())) if isinstance(y_true, dict) else np.array(y_true) From 055fec98f138d6f83751e92b7162c038fd92f9ce Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 10:34:05 +0900 Subject: [PATCH 25/26] int to str --- src/utils/separate_date_articulator_that_is_new.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/separate_date_articulator_that_is_new.py b/src/utils/separate_date_articulator_that_is_new.py index 17aa47d..6e5ac68 100644 --- a/src/utils/separate_date_articulator_that_is_new.py +++ b/src/utils/separate_date_articulator_that_is_new.py @@ -65,12 +65,14 @@ def return_emotions_mood_weather_mixer_combinations(df_weatherMood, batch_size,n for weather in weather_types ] - all_timesteps = generate_monthly_timestamps(str(start), str(end)) + all_timesteps = generate_monthly_timestamps(start, end) return combined, all_timesteps def generate_monthly_timestamps(start_timestamp, end_timestamp): # datetime 으로 변환 + start_timestamp = str(start_timestamp) + end_timestamp = str(end_timestamp) start_date = datetime.strptime(start_timestamp, "%Y%m%d%H") end_date = datetime.strptime(end_timestamp, "%Y%m%d%H") From b65891c7ec0069617dd8e2da522262e7b623c92b Mon Sep 17 00:00:00 2001 From: kwon-encored Date: Fri, 5 Sep 2025 10:47:01 +0900 Subject: [PATCH 26/26] instruction for GitHub CoPilot --- .github/copilot-instructions.md | 59 +++------------------------------ 1 file changed, 4 insertions(+), 55 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d2cece9..c5b662f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,58 +1,7 @@ description: 'Python coding conventions and guidelines' applyTo: '**/*.py' --- - -# Python Coding Conventions - -## Python Instructions - -- Write clear and concise comments for each function. -- Ensure functions have descriptive names and include type hints. -- Provide docstrings following PEP 257 conventions. -- Use the `typing` module for type annotations (e.g., `List[str]`, `Dict[str, int]`). -- Break down complex functions into smaller, more manageable functions. -- Write docstrings and comments primarily in Korean (unless external libraries or public APIs require English). - -## General Instructions - -- Always prioritize readability and clarity. -- For algorithm-related code, include explanations of the approach used. -- Write code with good maintainability practices, including comments on why certain design decisions were made. -- Handle edge cases and write clear exception handling. -- For libraries or external dependencies, mention their usage and purpose in comments. -- Use consistent naming conventions and follow language-specific best practices. -- Ensure all referenced functions, classes, and variables are properly declared before use. -- Carefully read and comprehensively understand each function, loop, and conditional (and their combinations); when appropriate, refactor into more efficient one-liners/two-liners or leverage built-in/vectorized methods from `numpy`, `pandas`, `datetime`, `itertools`, etc., while preserving behavior and clarity. -- Write concise, efficient, and idiomatic code that is also easily understandable. - -## Code Style and Formatting -- Use snake_case for variable and function names. Use CamelCase for class names. Follow PEP 8 style guidelines. Include type hints for function parameters and return types. -- Follow the **PEP 8** style guide for Python. -- Maintain proper indentation (use 4 spaces for each level of indentation). -- Ensure lines do not exceed 79 characters. -- Place function and class docstrings immediately after the `def` or `class` keyword. -- Use blank lines to separate functions, classes, and code blocks where appropriate. - -## Edge Cases and Testing - -- Always include test cases for critical paths of the application. -- Account for common edge cases like empty inputs, invalid data types, and large datasets. -- Include comments for edge cases and the expected behavior in those cases. -- Write unit tests for functions and document them with docstrings explaining the test cases. - -## Example of Proper Documentation - -```python -def calculate_area(radius: float) -> float: - """ - 원의 면적을 계산하는 함수 - - 주어진 반지름(`radius`)을 이용하여 원의 면적을 계산한다. - 면적은 π * (반지름^2) 공식을 사용한다. - - :param float radius: 원의 반지름 - :return: 원의 면적 값 - :rtype: float - """ - import math - return math.pi * radius ** 2 +- When performing a code review, ensure that variable and function names use snake_case, and class names use CamelCase, following PEP 8 style guidelines. +- When reviewing functions, check if loops or conditionals can be simplified with built-in or vectorized methods (e.g., numpy, pandas, datetime, itertools) while preserving clarity and behavior. +- When reviewing a function, check that its name is appropriate and corresponds to and clearly describes its purpose. +- When reviewing a function, check that its name clearly describes its purpose and that variable names are appropriate and descriptive.