Web application for managing imagery and routing it through structured grading workflows. Designed to support two primary use cases:
- Grader training — presenting images to human graders and collecting structured assessments
- ML dataset compilation — aggregating grading data for training machine learning models
- Overview
- Key Concepts
- Tech Stack
- User Roles
- Application Workflow
- Grading Forms
- Data Export
- Background Jobs
- Configuration
- Setup
- Flipped Images Logic
The Disease Photo Database allows administrators to:
- Ingest images from named Image Sources (e.g. a clinical study site)
- Organise images into Image Sets (e.g. per-participant collections)
- Assign individual images or entire image sets to Grading Sets
- Assign graders (users) to grading sets
- Present each grader with a structured, step-by-step questionnaire for every image/image set
- Export grading results as CSV for downstream analysis or ML training
A named origin for images (e.g. a clinical site or study). Images and image sets belong to a source. Sources can be marked active/inactive. A source can optionally auto-create image sets from a metadata field on upload.
A single uploaded image file. On creation, EXIF data is extracted and image variants (list thumbnail 150×150, preview 300×300, main 1000×1000) are generated asynchronously. Images carry a flexible JSON metadata blob and a separate exif_data blob.
A named collection of images from a single source (e.g. all images for one study participant). Image sets carry their own JSON metadata blob. Image sets can be auto-created from image metadata on upload.
A named set of gradeables (individual images or image sets) that is assigned to one or more graders. Each grading set has:
- A configurable flipped image percentage (see Flipped Images Logic)
- A list of assigned users
- Progress tracking per user
The join between a user and a grading set. Tracks per-user progress through the grading queue.
A single grading response — one user's answers for one gradeable. Stores the full grading_data JSON blob submitted from the grading form.
| Layer | Technology |
|---|---|
| Framework | Ruby on Rails 6.1, Ruby 3.2 |
| Database | PostgreSQL (JSON columns for metadata) |
| File Storage | Active Storage — local disk or AWS S3 |
| Background Jobs | Sidekiq + Redis |
| Frontend | Bootstrap 5, React (via Webpacker) |
| Image Processing | image_processing gem (libvips/ImageMagick), exifr for EXIF |
| Auth | Passwordless magic-link email login (BCrypt token hash) |
Roles are boolean flags on the User model. A user may hold multiple roles.
| Role | Access |
|---|---|
admin |
Full access — manages grading sets, users, image sources, image admin functions |
image_admin |
Upload images, update metadata, manage image sources |
image_viewer |
Browse images and image sets (read-only) |
grader |
Access the grading dashboard and submit grades |
Authentication uses a passwordless flow: the user enters their email, receives a time-limited magic link, and the resulting token is verified server-side.
Image Source
└── Images (uploaded individually or synced via ParticipantSyncJob)
└── Image Sets (auto-created from metadata field, or managed manually)
└── Grading Set ←── assigned images / image sets
└── User Grading Set ←── assigned graders
└── User Grading Set Image ←── one grading response per gradeable
- An Image Admin creates an Image Source and uploads images (via the web UI or the metadata upload tool).
- Images are optionally grouped into Image Sets automatically based on a configured metadata field.
- An Admin creates a Grading Set, adds images or image sets to it, and assigns graders.
- Each assigned Grader sees their grading sets on the dashboard and works through them one gradeable at a time.
- For each gradeable the grader completes a structured questionnaire (see Grading Forms). Progress is saved automatically after each submission.
- Once all gradeables (including any flipped images) are complete, the grader marks the set as done.
- Admins can download grading results as CSV at any time.
The grading UI is a React application (app/javascript/packs/grading/) that renders a step-by-step questionnaire alongside the image(s) being graded.
| Type | Behaviour |
|---|---|
select_one |
Dropdown — auto-advances on selection |
select_multiple |
Checkbox group — requires explicit Next |
text |
Free-text input — advances on Enter or Next |
relevant— a predicate function; the question is skipped if it returns false (supports both question-level and group-level relevance)constraint— a validation function run on submit; displaysconstraint_messageon failurerequired— blocks advancement if the answer is blank- Groups — questions are organised into named sections (e.g. Image Quality, Exam Findings, Results); entire groups can be conditionally shown
All exports are streamed as CSV.
| Export | Route | Scope |
|---|---|---|
| Image metadata | POST /images/metadata |
Filtered image selection |
| Image EXIF data | POST /images/exif_data |
Filtered image selection |
| Image grading data | POST /images/gradingdata |
Filtered image selection |
| Image set metadata | POST /image_sets/metadata |
Filtered image set selection |
| Image set grading data | POST /image_sets/gradingdata |
Filtered image set selection |
| Grading set results | GET /grading_sets/:id/data.csv |
All responses for a grading set |
Grading data CSVs include fixed columns (id, grading_set, name, source, user_id, user_email) followed by one column per grading form field. Multi-select fields are expanded to one binary column per option.
Jobs run via Sidekiq. The Sidekiq web UI is mounted at /sidekiq (admin only).
| Job | Trigger | Purpose |
|---|---|---|
ExifJob |
After image create | Extracts EXIF metadata from JPEG/TIFF files and stores it on the image |
ImageVariantJob |
After image create | Pre-generates list/preview/main image variants |
Copy .env.example to .env and populate:
| Variable | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
REDIS_URL |
Redis connection string (Sidekiq) |
HOST |
Application hostname (used in mailer links) |
MAILER_FROM |
From address for magic-link emails |
SMTP_USERNAME / SMTP_PASSWORD / SMTP_HOST / SMTP_PORT |
SMTP credentials |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_BUCKET / AWS_REGION |
S3 storage (leave blank to use local disk) |
LOCKED_METADATA_KEYS |
Comma-separated metadata keys that cannot be overwritten via the metadata upload tool |
# Install Ruby dependencies
bundle install
# Install JS dependencies
yarn install
# Create and migrate the database
bundle exec rails db:create db:migrate
# Start the web server and Sidekiq worker
foreman startThe app runs on port 3000 by default (PORT env var overrides this).
A flipped image is a repeat presentation of an already-graded image, used to measure grader consistency (intra-rater reliability).
Each grading set has a flipped_percent (0% by default). When non-zero, after a grader completes all primary images they are presented with a random subset of previously graded images to grade again — without being told they are repeats.
flipped_image_count = ceil(image_count × flipped_percent / 100)
total_to_grade = image_count + flipped_image_count
Grading queue logic:
- Present all primary images in order (
flipped = false). - Once primary images are complete, if
flipped_image_count > 0, begin the flipped phase:- Pick a random already-graded image that has not yet been presented as a flip.
- Present it for grading (stored with
flipped = true).
- The grading set is considered complete when
complete_image_count_total >= total_image_count.
The flipped_percent is stored on the grading set at creation time so that changing the percentage later does not retroactively alter in-progress grading sets.