Skip to content

Commit

Permalink
Add support for mean value binning.
Browse files Browse the repository at this point in the history
This allows the output data to remain 16 bit.
  • Loading branch information
pchote committed Jun 23, 2024
1 parent a541539 commit a17010b
Show file tree
Hide file tree
Showing 12 changed files with 64 additions and 37 deletions.
1 change: 1 addition & 0 deletions config/clasp/cam1.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"gain": 26,
"offset": 30,
"binning": 1,
"binning_method": "sum",
"stream": true,
"use_gpsbox": true,
"filters": ["NONE"],
Expand Down
1 change: 1 addition & 0 deletions config/halfmetre.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"gain": 26,
"offset": 30,
"binning": 2,
"binning_method": "sum",
"stream": true,
"use_gpsbox": true,
"filters": ["NONE", "BLOCK", "B", "V", "R"],
Expand Down
1 change: 1 addition & 0 deletions config/superwasp/cam1.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"gain": 26,
"offset": 30,
"binning": 1,
"binning_method": "sum",
"stream": true,
"use_gpsbox": true,
"filters": ["RGB-B"],
Expand Down
1 change: 1 addition & 0 deletions config/superwasp/cam2.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"gain": 26,
"offset": 30,
"binning": 1,
"binning_method": "sum",
"stream": true,
"use_gpsbox": true,
"filters": ["RGB-G"],
Expand Down
1 change: 1 addition & 0 deletions config/superwasp/cam3.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"gain": 26,
"offset": 30,
"binning": 1,
"binning_method": "sum",
"stream": true,
"use_gpsbox": true,
"filters": ["RGB-R"],
Expand Down
1 change: 1 addition & 0 deletions config/superwasp/cam4.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"gain": 26,
"offset": 30,
"binning": 1,
"binning_method": "sum",
"stream": true,
"use_gpsbox": true,
"filters": ["i"],
Expand Down
1 change: 1 addition & 0 deletions config/warwick.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"gain": 56,
"offset": 30,
"binning": 3,
"binning_method": "sum",
"stream": false,
"use_gpsbox": true,
"header_card_capacity": 144,
Expand Down
8 changes: 5 additions & 3 deletions qhy_camd
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ class CameraDaemon:
return self.qhy_command('window', window=window, quiet=quiet)

@Pyro4.expose
def set_binning(self, binning, quiet=False):
def set_binning(self, binning, method, quiet=False):
"""Sets the sensor binning factor"""
if not pyro_client_matches(self._config.control_ips):
return CommandStatus.InvalidControlIP
Expand All @@ -208,7 +208,7 @@ class CameraDaemon:
if not success:
return CommandStatus.Blocked

return self.qhy_command('binning', binning=binning, quiet=quiet)
return self.qhy_command('binning', binning=binning, method=method, quiet=quiet)

@Pyro4.expose
def set_gain(self, gain, quiet=False):
Expand Down Expand Up @@ -257,6 +257,7 @@ class CameraDaemon:
exposure: Exposure time in seconds
window: Tuple of 1-indexed (x1, x2, y1, y2)
bin: number of pixels to bin in x,y
bin_method: add or mean
gain: Gain setting
offset: Offset (bias) setting
stream: stream (live) exposures or take individual frames
Expand Down Expand Up @@ -285,7 +286,8 @@ class CameraDaemon:
self.qhy_command('window', window=window, quiet=quiet)

binning = params.get('bin', self._config.binning)
self.qhy_command('binning', binning=binning, quiet=quiet)
method = params.get('bin_method', self._config.binning_method)
self.qhy_command('binning', binning=binning, method=method, quiet=quiet)

gain = params.get('gain', self._config.gain)
self.qhy_command('gain', gain=gain, quiet=quiet)
Expand Down
30 changes: 17 additions & 13 deletions rockit/camera/qhy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def run_client_command(config_path, usage_prefix, args):
print('warm')
elif 'window' in args[-2:]:
print('default')
elif 'bin' in args[-3:-1]:
print('add mean')
elif len(args) < 3:
print(' '.join(commands))
return 0
Expand Down Expand Up @@ -110,7 +112,7 @@ def status(config, *_):

w = [x + 1 for x in data['window']]
print(f' Output window is {TFmt.Bold}[{w[0]}:{w[1]},{w[2]}:{w[3]}]{TFmt.Clear}')
print(f' Binning is {TFmt.Bold}{data["binning"]}x{data["binning"]}{TFmt.Clear}')
print(f' Binning is {TFmt.Bold}{data["binning"]}x{data["binning"]}{TFmt.Clear} ({TFmt.Bold}{data["binning_method"]}{TFmt.Clear})')
if data['filter']:
print(f' Filter is {TFmt.Bold}{data["filter"]}{TFmt.Clear}')
return 0
Expand Down Expand Up @@ -170,20 +172,22 @@ def set_window(config, usage_prefix, args):

def set_binning(config, usage_prefix, args):
"""Set the camera binning"""
if len(args) == 1:
binning = None
if args[0] != 'default':
try:
binning = int(args[0])
except ValueError:
print(f'usage: {usage_prefix} bin <pixels>')
return -1
if len(args) == 1 and args[0] == 'default':
binning = method = None
elif len(args) == 2 and args[1] in ['sum', 'mean']:
try:
binning = int(args[0])
except ValueError:
print(f'usage: {usage_prefix} bin <pixels> (sum|mean)')
return -1
method = args[1]
else:
print(f'usage: {usage_prefix} bin <pixels> (sum|mean)')
return -1

with config.daemon.connect() as camd:
return camd.set_binning(binning)
with config.daemon.connect() as camd:
return camd.set_binning(binning, method)

print(f'usage: {usage_prefix} bin <pixels>')
return -1


def set_streaming(config, usage_prefix, args):
Expand Down
11 changes: 8 additions & 3 deletions rockit/camera/qhy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
'additionalProperties': False,
'required': [
'daemon', 'pipeline_daemon', 'pipeline_handover_timeout', 'log_name', 'control_machines',
'client_commands_module',
'camera_device_id', 'camera_id', 'cooler_setpoint', 'cooler_update_delay', 'cooler_pwm_step',
'worker_processes', 'framebuffer_bytes', 'mode', 'gain', 'offset', 'binning', 'stream', 'use_gpsbox',
'client_commands_module', 'camera_device_id', 'camera_id',
'cooler_setpoint', 'cooler_update_delay', 'cooler_pwm_step', 'worker_processes',
'framebuffer_bytes', 'mode', 'gain', 'offset', 'binning', 'binning_method', 'stream', 'use_gpsbox',
'header_card_capacity', 'output_path', 'output_prefix', 'expcount_path'
],
'properties': {
Expand Down Expand Up @@ -101,6 +101,10 @@
'min': 1,
'max': 9600,
},
'binning_method': {
'type': 'string',
'enum': ['sum', 'mean'],
},
'stream': {
'type': 'boolean',
},
Expand Down Expand Up @@ -164,6 +168,7 @@ def __init__(self, config_filename):
self.gain = config_json['gain']
self.offset = config_json['offset']
self.binning = config_json['binning']
self.binning_method = config_json['binning_method']
self.stream = config_json['stream']
self.use_gpsbox = config_json['use_gpsbox']
self.filters = config_json.get('filters', [])
Expand Down
29 changes: 14 additions & 15 deletions rockit/camera/qhy/outputprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from astropy.time import Time
import astropy.units as u
import numpy as np
from numpy.lib.stride_tricks import as_strided
from rockit.common import daemons, log
from .constants import CoolerMode

Expand Down Expand Up @@ -201,25 +202,23 @@ def output_process(process_queue, processing_framebuffer, processing_framebuffer
data = data[window_region[2]:window_region[3] + 1, window_region[0]:window_region[1] + 1]

if frame['binning'] > 1:
nrows, ncols = data.shape
n_binned_cols = ncols//frame['binning']
n_binned_rows = nrows//frame['binning']
binned_cols = np.zeros((nrows, n_binned_cols), dtype=np.uint32)

for i in range(nrows):
binned_cols[i] = np.sum(data[i][:n_binned_cols*frame['binning']]
.reshape(n_binned_cols, frame['binning']), axis=1)

data = np.zeros((n_binned_rows, n_binned_cols), dtype=np.uint32)
for i in range(n_binned_cols):
data[:, i] = np.sum(binned_cols[:, i][:n_binned_rows*frame['binning']]
.reshape(n_binned_rows, frame['binning']), axis=1)
# Create a 4d view of the 2d image, where the two new axes
# correspond to the pixels that are to be binned in the x and y axes
binning = np.array((frame['binning'], frame['binning']))
blocked_shape = tuple(data.shape // binning) + tuple(binning)
blocked_strides = tuple(data.strides * binning) + data.strides
blocked = as_strided(data, shape=blocked_shape, strides=blocked_strides)

if frame['binning_method'] == 'sum':
data = np.sum(blocked, axis=(2, 3), dtype=np.uint32)
else:
data = np.mean(blocked, axis=(2, 3), dtype=np.uint16)

image_region = bin_sensor_region(image_region, frame['binning'])
bias_region = bin_sensor_region(bias_region, frame['binning'])
dark_region = bin_sensor_region(dark_region, frame['binning'])
window_region[1] = frame['window_region'][0] + n_binned_cols * frame['binning'] - 1
window_region[3] = frame['window_region'][2] + n_binned_rows * frame['binning'] - 1
window_region[1] = frame['window_region'][0] + blocked_shape[1] * frame['binning'] - 1
window_region[3] = frame['window_region'][2] + blocked_shape[0] * frame['binning'] - 1

if image_region is not None:
image_region_header = ('IMAG-RGN', format_sensor_region(image_region),
Expand Down
16 changes: 13 additions & 3 deletions rockit/camera/qhy/qhyprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def __init__(self, config, processing_queue,
# Crop output data to detector coordinates
self._window_region = [0, 0, 0, 0]
self._binning = config.binning
self._binning_method = config.binning_method

# Image geometry (marking edges of overscan etc)
self._image_region = [0, 0, 0, 0]
Expand Down Expand Up @@ -410,6 +411,7 @@ def __run_exposure_sequence(self, quiet):
'cooler_setpoint': self._cooler_setpoint,
'window_region': self._window_region,
'binning': self._binning,
'binning_method': self._binning_method,
'image_region': self._image_region,
'bias_region': self._bias_region,
'dark_region': self._dark_region,
Expand Down Expand Up @@ -773,21 +775,28 @@ def set_window(self, window, quiet):
else:
return CommandStatus.Failed

def set_binning(self, binning, quiet):
def set_binning(self, binning, method, quiet):
"""Set the camera binning"""
if self.is_acquiring:
return CommandStatus.CameraNotIdle

if binning is None:
binning = self._config.binning

if method is None:
method = self._config.binning_method

if not isinstance(binning, int) or binning < 1:
return CommandStatus.Failed

if method not in ['sum', 'mean']:
return CommandStatus.Failed

self._binning = binning
self._binning_method = method

if not quiet:
log.info(self._config.log_name, f'Binning set to {binning}')
log.info(self._config.log_name, f'Binning set to {binning} ({method})')

return CommandStatus.Succeeded

Expand Down Expand Up @@ -926,6 +935,7 @@ def report_status(self):
'exposure_progress': exposure_progress,
'window': self._window_region,
'binning': self._binning,
'binning_method': self._binning_method,
'sequence_frame_limit': self._sequence_frame_limit,
'sequence_frame_count': sequence_frame_count,
'filter': self._filter,
Expand Down Expand Up @@ -986,7 +996,7 @@ def qhy_process(camd_pipe, config,
elif command == 'window':
camd_pipe.send(cam.set_window(args['window'], args['quiet']))
elif command == 'binning':
camd_pipe.send(cam.set_binning(args['binning'], args['quiet']))
camd_pipe.send(cam.set_binning(args['binning'], args['method'], args['quiet']))
elif command == 'start':
camd_pipe.send(cam.start_sequence(args['count'], args['quiet']))
elif command == 'stop':
Expand Down

0 comments on commit a17010b

Please sign in to comment.