β Not validated on camera footage β and the 256 px recommendation is withdrawn
This model is trained on Places365 photographs. It has now been tested against 1,684 frames of fixed-camera hospital-room video, where every frame is a patient room, so the correct answer is "yes" on all of them:
variant says "patient room" 64Γ64 17.5% 256Γ256 11.0% It calls 83β89% of genuine hospital rooms not a patient room. Neither variant is validated on wall- or ceiling-mounted camera footage, and neither should be relied on for it.
The recommendation to prefer the 256 px variant is withdrawn. That advice was based on held-out Places365 validation (0.689 β 0.741 balanced accuracy), which is real but did not predict deployment: on camera footage the 256 px model is worse than the 64 px one it was meant to replace. In-domain improvement and deployment behaviour pointed in opposite directions here.
The numbers further down remain accurate for photographs, which is the domain they were measured in. Use this model on photographic inputs, or validate it on your own footage first.
Test caveat: the footage is generated rather than filmed, and contains one room and roughly two people, so it cannot measure cross-ward generalisation. But the ground truth is certain and a 17.5% score is not rescued by that.
clinical-scene-nano
Two scene rungs, two input resolutions. 47,122 parameters each. 188 KB. 64Γ64 at ~3,000 fps, or 256Γ256 at ~220 fps β the 256 variant is substantially more accurate.
hospital_room.onnx β is this a patient room, as opposed to a bedroom, hotel room, dorm or nursing home?
clinical_space.onnx β is this a clinical space (hospital / hospital room / operating room / nursing home) rather than a domestic one?
Read this before the numbers
These are trained on Places365, which is handheld photographs, not camera footage. A wall- or ceiling-mounted camera sees wider, higher, emptier and worse-lit scenes than a person with a phone does. That deployment gap is UNMEASURED. If you point this at a real room, you are running an experiment.
Operations, not diagnosis. This answers "what kind of space is the camera looking at" β a routing and context question. It says nothing about anyone in the room, and nothing clinical.
Not a medical device. Nothing here is validated for clinical use.
The hard negatives are the whole design
"Clinical space vs the outdoors" is trivial and worth nothing. The question that matters for a
camera is whether a room containing a bed is a patient room or somebody's bedroom. So V2b's
negatives are bedroom, hotel_room, dorm_room and nursing_home β same furniture, same layout,
same lighting. "There is a bed in this image" wins nothing here.
Resolution: predicted first, then measured
The discriminating cues here sit at very different physical scales. In a room ~4,000 mm across, a 64Γ64 input renders them at:
| cue | mm | @64 px | @256 px |
|---|---|---|---|
| hospital bed | 2000 | 32.0 | 128.0 |
| IV pole | 1800 | 28.8 | 115.2 |
| bed rails | 900 | 14.4 | 57.6 |
| patient monitor | 400 | 6.4 β | 25.6 |
| wall gas outlets | 300 | 4.8 β | 19.2 |
| IV bag | 250 | 4.0 β | 16.0 |
| call button / cord | 150 | 2.4 β | 9.6 |
β = below the ~8 px classifiability threshold.
The prediction, recorded before testing: at 64Γ64 this model cannot see monitors, gas outlets, IV bags or call cords β the cues that most strongly separate a hospital room from a bedroom β so it must be leaning on bed shape and room layout, and should gain substantially at higher resolution.
Measured, on the official val split:
| model | 64Γ64 | 256Γ256 |
|---|---|---|
hospital_room (hard task) |
0.689 | 0.741 |
clinical_space (broad task) |
0.764 | 0.784 |
The hard task gains more (+0.052) than the broad one (+0.020), which is what the prediction implies: telling a patient room from a bedroom needs the small clinical cues, while telling a clinical space from a domestic one is separable on large-scale differences that were already visible at 64 px.
The margin over the cheap baseline nearly triples, because global statistics cannot use extra pixels:
| resolution | nano | cheap baseline | margin |
|---|---|---|---|
| 64 | 0.6400 | 0.6122 | +0.0278 |
| 128 | 0.6839 | 0.6161 | +0.0678 |
| 256 | 0.6989 | 0.6200 | +0.0789 |
For photographic input, use the 256 variant unless severely compute-bound β but see the warning at the top: on camera footage it is worse than the 64 px model, and neither is validated there. 4.5 ms per frame is ~220 fps, and a scene classification only needs running once a minute β room identity changes rarely.
Results β official Places365 validation split, untouched by training
Balanced accuracy (mean of per-class recall), because V2b's val set is 100 positives against 400 negatives and plain accuracy would reward a model for always answering "not a hospital room".
| model | val balanced | face-free | random-split (3 seeds) | cheap baseline | majority |
|---|---|---|---|---|---|
hospital_room |
0.689 | 0.682 | 0.676 Β± 0.008 | 0.589 | 0.500 |
clinical_space |
0.764 | 0.759 | 0.756 Β± 0.007 | 0.628 | 0.500 |
The cheap baseline is the stronger of a transferred single threshold and a ~66-parameter logistic regression over ten cheap image statistics, both fitted on train and evaluated on test. A single threshold alone is too weak a bar to be honest about.
The shortcut audit β corrected, and stronger than the first version
Places365's clinical classes skew heavily toward images containing people. A model could score well by learning "photograph containing a person", and that model would be useless on a camera watching an empty room β the actual use. So the models are re-scored on images with no detected person.
This audit was first run with a Haar face cascade and that was too weak. Frontal-face detection
fired on 17% of hospital_room; a proper person detector (YOLOv8n-pose) finds a person in 76%.
The face-based audit therefore left most people-containing images in the "face-free" set, and the
conclusion it supported was much thinner than it appeared. Redone with person detection:
| person in positives | person in negatives | all | person-free | person-present | |
|---|---|---|---|---|---|
hospital_room |
76% | 38% | 0.689 | 0.670 (n=273, 24 pos) | 0.676 |
clinical_space |
64% | 27% | 0.764 | 0.798 (n=437, 146 pos) | 0.716 |
The conclusion survives the stronger test. hospital_room loses 0.019 with every detected person
removed, and clinical_space gets better (0.764 β 0.798) β it performs worse on the people-heavy
subset, which is the opposite of a people shortcut.
One honest weakness: the hospital_room person-free figure rests on 24 positive images. That is
a thin basis for a headline claim, and it is reported rather than rounded away. clinical_space's
146 positives make its person-free number the more trustworthy of the two.
Prior art
Scene recognition on Places365 is mature and well-studied β Zhou et al., Places: A 10 Million Image
Database for Scene Recognition (TPAMI 2017), with ResNet-class models reaching roughly 55% top-1 on
the full 365-way problem at tens of millions of parameters. This is not that task and the numbers
are not comparable: these are binary problems over 5β8 hand-picked classes at ~1/500th the
parameters. Binary hospital_room-vs-bedrooms is not a standard benchmark, so there is no published
SOTA to hold this against, and the only honest comparison is the cheap baseline on the identical
split β which is what the table reports.
Nothing about the architecture is novel. What is offered here is the packaging: hard negatives chosen so the task cannot be won on furniture, an explicit shortcut audit, and a stated refusal to claim the deployment transfer has been measured.
Usage
import numpy as np, onnxruntime as ort, cv2
so = ort.SessionOptions(); so.intra_op_num_threads = 1
so.add_session_config_entry("session.intra_op.allow_spinning", "0") # else it burns cores idling
sess = ort.InferenceSession("hospital_room.onnx", sess_options=so,
providers=["CPUExecutionProvider"])
im = cv2.imread("room.jpg") # BGR, any size
x = cv2.resize(im, (64, 64), interpolation=cv2.INTER_AREA).astype(np.float32) / 255.0
x = (x - x.mean()) / (x.std() + 1e-6) # per-image standardisation, as trained
x = x.transpose(2, 0, 1)[None]
logits = sess.run(None, {"image": x})[0]
print("patient room" if logits.argmax() == 1 else "not a patient room")
Input is BGR as OpenCV loads it, standardised per image. Feeding RGB will degrade it silently.
Where this sits in a cascade
V0 camera-health-nano is the frame usable? (published separately)
V2 clinical_space.onnx what kind of space is this?
V2b hospital_room.onnx patient room, or just a bedroom?
A scene rung is a natural always-on tier because a room's identity changes rarely β run it once a minute, not once a frame.
Limitations
- Photographs, not camera views. The single largest unmeasured risk.
- 0.689 balanced on the hard task is modest. It is well above the 0.589 cheap baseline and well above chance, and it is not close to reliable on a single frame. Aggregate over many frames.
waiting_roomis absent from the negatives: the Places365 train archive truncated beforew/. A medical waiting room may therefore classify unpredictably.- Trained at 64x64. Fine detail that distinguishes clinical from domestic furniture at a glance β rails, monitors, IV poles β is largely destroyed at this resolution.
- Binary only. A room that is neither clinical nor domestic gets forced into one of two answers.
- Places365 reflects the geographic and economic distribution of its web-sourced photographs. Wards that do not look like that distribution are not represented.
Known failure modes, measured
- People skew, not room skew. A person detector finds someone in 76% of
hospital_roomimages against 38% of the bed-room negatives. Re-scoring on images with no detected person costs only 0.019 (0.689 β 0.670), so the model is reading rooms β but the person-free subset contains just 24 positives, which is thin for a headline claim. - Small clinical cues are invisible at 64Γ64. Patient monitors land on 6.4 px, wall gas outlets 4.8 px, IV bags 4.0 px β all below the ~8 px threshold. The 64 px variant is therefore leaning on bed shape and layout, which is why the 256 px variant gains +0.052.
waiting_roomis absent from the negatives (the Places365 train archive truncated beforew/), so medical waiting rooms may classify unpredictably.- Single-frame balanced accuracy of 0.741 is a triage signal. Aggregate over frames.