Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
88b41d3
chore: Added initial script
joda01 Mar 14, 2025
e89d90c
chore: Added readme
joda01 Mar 14, 2025
b2e85bb
fix: Threshold settings
joda01 Apr 18, 2025
e703630
first unittests finished
manfred-seiwald Apr 25, 2025
12c8dce
overwrite ref_exports with well 0 is 15
manfred-seiwald Apr 26, 2025
6c84975
updated readme.md
manfred-seiwald Apr 27, 2025
313de41
chore: Added runner
joda01 Jul 26, 2025
e941bc3
Merge branch 'init' of https://github.com/joda01/imagec-test into init
joda01 Jul 26, 2025
cdd69f8
chore: Runner fix
joda01 Jul 26, 2025
98ec506
chore: List
joda01 Jul 26, 2025
d260ff3
chore: Added run test
joda01 Jul 26, 2025
69ebb22
chore: Start test
joda01 Jul 26, 2025
5bf21d8
chore: Fixed copy
joda01 Jul 26, 2025
b402f41
chore: Removed
joda01 Jul 26, 2025
9b6c3b2
chore: Added expected data
joda01 Jul 26, 2025
144f7a3
chore: Finished unit test
joda01 Jul 26, 2025
077ecda
chore: Fail on error
joda01 Jul 26, 2025
b08ead1
chore: Do not execute test
joda01 Jul 26, 2025
b29c67c
chore: Smaller fix
joda01 Jul 26, 2025
76dbb86
chore: Adapted unit test for pixel size
joda01 Aug 20, 2025
34aa205
chore: Unit test fix
joda01 Aug 21, 2025
4338e3c
chore: Added scenario2
joda01 Aug 21, 2025
2f8d158
chore: Correcte unit test
joda01 Aug 21, 2025
0feaba6
chore: Running unti test
joda01 Aug 21, 2025
2fc4110
chore: Adapted unit test for new imagec version
joda01 Oct 23, 2025
1f1b479
chore: Fixed unit test
joda01 Oct 23, 2025
f07fad4
chore: Adapted unit test
joda01 Nov 11, 2025
53177e9
chore: Adapted unit test
joda01 Nov 30, 2025
912ced1
chore: Add profile
joda01 Dec 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM ubuntu:noble-20240429 AS build

RUN apt-get update && apt install -y python3-all pip git
RUN apt-get update && apt install -y python3-pandas python3-openpyxl
33 changes: 33 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/cpp
{
// docker run your_image_name
"name": "C++",
"dockerFile": "./Dockerfile",
"mounts": [
{
"source": "/home/joachim-danmayr/Documents/github/conan_cache",
"target": "/root/.conan2",
"type": "bind"
}
],
"runArgs": [],
"customizations": {
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/sh"
},
// Add the IDs of extensions you want installed when the container is created.
"vscode": {
"extensions": [
"ms-vscode.cpptools",
"llvm-vs-code-extensions.vscode-clangd",
"GrapeCity.gc-excelviewer",
"streetsidesoftware.code-spell-checker",
"vscjava.vscode-java-pack",
"xaver.clang-format",
"felgo.felgo"
]
}
}
}
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@joda01
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot

version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
18 changes: 18 additions & 0 deletions .github/workflows/run-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: imagec-test

on:
push:
branches:
- main
- init

jobs:
prepare-test:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- run: |
echo "Download latest ImageC ..."
# ./prepare_test_bench.sh /home/actions-runner/test_data
# echo "Run test ..."
# ./run_test.sh /home/actions-runner/test_data
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/imagec-x64-linux
/tmp
/test_data
/scenarios/*/results
/scenarios/*/models
30 changes: 30 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

{
"name": "Python Debugger: run test",
"type": "debugpy",
"request": "launch",
"program": "run_test.py",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"args": [
"./tmp/",
"./ref_exports/"
]
},
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
}

]
}
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"python.testing.unittestArgs": [
"-v",
"-s",
".",
"-p",
"test*.py"
],
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true
}
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,17 @@
# imagec-test
Automatic test execution for ImageC
TODO:
* add dockerfile to repository
* add to dockerfile: pip install unittest-xml-reporting (v3.2)

1) Download `test_data.zip` and extract to project folder
2) Execute `prepare_test_bench.sh` which downloads the latest ImageC version and copy the AI model paths to the ImageC folder
3) Execute `run_test.sh` to run the tests.
4) If you want to create new reference export files: execute `run_export.sh --ref`. This will copy the reference export files to the
folder /ref_exports

Open problems:
* The dataset contains one plate with 2 wells (A5 and A6). During the analysis the assignment of the group_id is arbitrary, either group_id=0 for A5 and group_id=1 for A6, or vice versa.
* As the group_id is also used for export, the export for group_id=0 could be well A5 or A6.
* For the ref_exports group_id=0 is used for A5, group_id=1 for A6.
* If the group_ids match there are 4 files with differences. It always concerns dapi_mb@spot-Intensity avg[]

124 changes: 124 additions & 0 deletions compare_excel_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import os
import sys
import pandas as pd
import numpy as np


def normalize_and_compare(df1, df2, tol=1e-6):
# Check shape and column names
if df1.shape[1] != df2.shape[1] or list(df1.columns) != list(df2.columns):
return False, "Shape or column mismatch"

diff_mask = pd.DataFrame(False, index=df1.index, columns=df1.columns)

for col in df1.columns:
# Skip cell comparison for "Object ID" columns
if "object id" in col.lower():
continue

s1 = df1[col]
s2 = df2[col]

if pd.api.types.is_numeric_dtype(s1) and pd.api.types.is_numeric_dtype(s2):
equal = np.isclose(s1.fillna(np.nan), s2.fillna(np.nan), atol=tol, equal_nan=True)
diff_mask[col] = ~equal
else:
equal = (s1.fillna("").astype(str).str.strip() ==
s2.fillna("").astype(str).str.strip())
diff_mask[col] = ~equal

is_equal = not diff_mask.any().any()
return is_equal, diff_mask


def detailed_diff_report(df1, df2, diff_mask, sheet_name):
print(f" ➤ Differences found in sheet '{sheet_name}':")
rows, cols = np.where(diff_mask.to_numpy())
for r, c in zip(rows, cols):
col_name = df1.columns[c]
# Skip reporting "Object ID" differences (already excluded)
if "object id" in col_name.lower():
continue
val1 = df1.iat[r, c]
val2 = df2.iat[r, c]
print(f" - Row {r+1}, Column '{col_name}': file1 = '{val1}' vs file2 = '{val2}'")


def compare_excel_files(folder1, folder2, tol=1e-6):
has_difference = False

files1 = set(f for f in os.listdir(folder1) if f.endswith('.xlsx'))
files2 = set(f for f in os.listdir(folder2) if f.endswith('.xlsx'))
all_files = sorted(files1 | files2)

for filename in all_files:
path1 = os.path.join(folder1, filename)
path2 = os.path.join(folder2, filename)

print(f"\n📄 Comparing file: {filename}")

if filename not in files1:
print(f" ❌ Missing in folder1: {filename}")
has_difference = True
continue
if filename not in files2:
print(f" ❌ Missing in folder2: {filename}")
has_difference = True
continue

try:
excel1 = pd.ExcelFile(path1)
excel2 = pd.ExcelFile(path2)

sheets1 = excel1.sheet_names
sheets2 = excel2.sheet_names

if set(sheets1) != set(sheets2):
print(" ❗ Sheet names differ:")
print(" ➤ Only in folder1:", set(sheets1) - set(sheets2))
print(" ➤ Only in folder2:", set(sheets2) - set(sheets1))
has_difference = True

# Skip first sheet
common_sheets = sorted(set(sheets1[1:]) & set(sheets2[1:]))

for sheet in common_sheets:
df1 = excel1.parse(sheet)
df2 = excel2.parse(sheet)

equal, result = normalize_and_compare(df1, df2, tol=tol)

if equal:
print(f" ✅ Sheet '{sheet}' is identical (within tol={tol}).")
else:
print(f" ❌ Sheet '{sheet}' differs:")
has_difference = True
if isinstance(result, str):
print(f" ➤ {result}")
else:
detailed_diff_report(df1, df2, result, sheet)

except Exception as e:
print(f" ⚠️ Error processing {filename}: {e}")
has_difference = True

return 1 if has_difference else 0


if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python compare_excel_files.py <folder1> <folder2>")
sys.exit(1)

folder1 = sys.argv[1]
folder2 = sys.argv[2]

if not os.path.isdir(folder1):
print(f"❌ Folder not found: {folder1}")
sys.exit(1)
if not os.path.isdir(folder2):
print(f"❌ Folder not found: {folder2}")
sys.exit(1)

exit_code = compare_excel_files(folder1, folder2, tol=1e-6)
sys.exit(exit_code)
85 changes: 85 additions & 0 deletions old/run_export.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@


#
# Execute the analysis
#
run_analyze_scenario_01(){
QT_QPA_PLATFORM="offscreen"
export QT_QPA_PLATFORM
MY_PATH=$PWD
SCENARIO_PATH="$MY_PATH/scenarios/scenario_01"
cd imagec-x64-linux
rm -rf ${MY_PATH}/test_data/test_images_v1/imagec
./imagec.sh --run ${SCENARIO_PATH}/scenario_v1.icproj --run-path ${MY_PATH}/test_data/test_images_v1
cd ..
}

#
# Run the unit test
# runs only outside docker
#
run_export_files_01(){
image_names=(
B2_15_5ADVMLE.vsi.vsi
B4_15_5ADVMLE.vsi.vsi
B8_15_5ADVMLE.vsi.vsi
B2_16_5ADVMLE.vsi.vsi
B4_16_5ADVMLE.vsi.vsi
B8_16_5ADVMLE.vsi.vsi
)
QT_QPA_PLATFORM="offscreen"
export QT_QPA_PLATFORM
MY_PATH=$PWD
rm -rf $MY_PATH/$TARGET_FOLDER
mkdir -p $MY_PATH/$TARGET_FOLDER
SCENARIO_PATH="$MY_PATH/scenarios/scenario_01"
cd imagec-x64-linux
#cd /home/manfred/Documents/imagec-x64-linux-bundle/imagec-x64-linux
./imagec.sh --export ${MY_PATH}/test_data/test_images_v1/imagec/*/results.icdb --export-output $MY_PATH/$TARGET_FOLDER/plate.xlsx --export-columns ${SCENARIO_PATH}/export_scenario_v1.ictemplexp --export-type xlsx --export-format list --export-view plate --export-filter "1 0"
./imagec.sh --export ${MY_PATH}/test_data/test_images_v1/imagec/*/results.icdb --export-output $MY_PATH/$TARGET_FOLDER/well_0.xlsx --export-columns ${SCENARIO_PATH}/export_scenario_v1.ictemplexp --export-type xlsx --export-format list --export-view well --export-filter "1 0"
./imagec.sh --export ${MY_PATH}/test_data/test_images_v1/imagec/*/results.icdb --export-output $MY_PATH/$TARGET_FOLDER/well_1.xlsx --export-columns ${SCENARIO_PATH}/export_scenario_v1.ictemplexp --export-type xlsx --export-format list --export-view well --export-filter "1 1"
./imagec.sh --export ${MY_PATH}/test_data/test_images_v1/imagec/*/results.icdb --export-output $MY_PATH/$TARGET_FOLDER/plate_heatmap.xlsx --export-columns ${SCENARIO_PATH}/export_scenario_v1.ictemplexp --export-type xlsx --export-format heatmap --export-view plate --export-filter "1 0"
./imagec.sh --export ${MY_PATH}/test_data/test_images_v1/imagec/*/results.icdb --export-output $MY_PATH/$TARGET_FOLDER/well_0_heatmap.xlsx --export-columns ${SCENARIO_PATH}/export_scenario_v1.ictemplexp --export-type xlsx --export-format heatmap --export-view well --export-filter "1 0"
./imagec.sh --export ${MY_PATH}/test_data/test_images_v1/imagec/*/results.icdb --export-output $MY_PATH/$TARGET_FOLDER/well_1_heatmap.xlsx --export-columns ${SCENARIO_PATH}/export_scenario_v1.ictemplexp --export-type xlsx --export-format heatmap --export-view well --export-filter "1 1"
for image in "${image_names[@]}"; do
./imagec.sh --export ${MY_PATH}/test_data/test_images_v1/imagec/*/results.icdb --export-output $MY_PATH/$TARGET_FOLDER/${image}.xlsx --export-columns ${SCENARIO_PATH}/export_scenario_v1.ictemplexp --export-type xlsx --export-format list --export-view image --export-filter "1 0 $image"
done
cd ..
python3 convert_xlsx_to_csv.py $MY_PATH/$TARGET_FOLDER
cd ..
}

run_all(){
run_analyze_scenario_01
run_export_files_01
}

# Check for arguments
if [ $# -eq 0 ]; then
echo "run_export script usage:"
echo " [--current] Execute --execute the pipeline and store files in /tmp."
echo " [--ref] Execute --execute the pipeline and store files in /ref_exports as ground truth"
exit 1
fi

# Parse arguments
for arg in "$@"; do
case $arg in
--current)
# Execute init conan once after you initial setup the project
TARGET_FOLDER="tmp"
run_all
;;
--ref)
# Execute make every time some external deps have been changed or added
TARGET_FOLDER="ref_exports"
run_all
;;
*)
echo "Unknown option: $arg"
echo "Usage: $0 [--current] [--ref]"
exit 1
;;
esac
done

Loading
Loading