Skip to content
This repository has been archived by the owner on Mar 25, 2024. It is now read-only.

Commit

Permalink
Feat/api tornado (#47)
Browse files Browse the repository at this point in the history
* First commit in tornadoapi

* implemented first version of REST api using tornado-web. Tested in setting the pymeix_connection object properties and calling functions (start/stop recording)

* added camera generation parameter in pymepix command line paramteres

* removed api.py file, not needed at the moment

* added parameter in RESTFUL operation for pipeline pixel of centroid

---------

Co-authored-by: BenMoon <BenMoon@users.noreply.github.com>
  • Loading branch information
samartse and BenMoon committed Jan 16, 2024
1 parent 1a95e44 commit 62e9c66
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 9 deletions.
267 changes: 264 additions & 3 deletions pymepix/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,21 @@
import os
import time

import re

import pymepix.config.load_config as cfg
from pymepix.post_processing import run_post_processing
from pymepix.pymepix_connection import PymepixConnection

from pymepix.processing.acquisition import PixelPipeline, CentroidPipeline

from tornado.web import Application, RequestHandler, HTTPError
import json

#from .api.api import make_app
from tornado.ioloop import IOLoop


logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
Expand All @@ -37,8 +48,9 @@

def connect_timepix(args):
if not os.path.exists(args.output):
# Connect to SPIDR
pymepix = PymepixConnection(spidr_address=(args.ip, args.port))
# Connect to camera
pymepix = PymepixConnection(cam_address=(args.ip, args.port),
camera_generation=args.cam_gen)
# If there are no valid timepix detected then quit()
if len(pymepix) == 0:
logging.error(
Expand Down Expand Up @@ -74,17 +86,159 @@ def connect_timepix(args):
f"Outputfile {args.output} already exists. Please make sure the specified file does not exist."
)


def post_process(args):
run_post_processing(
args.file.name,
args.output_file,
args.number_of_processes,
args.timewalk_file,
args.cent_timewalk_file,
args.cam_gen,
)


def is_jsonable(x):
try:
json.dumps(x)
return True
except (TypeError, OverflowError):
return False

def get_path(ref, path):
try:
for tkn in path:
if tkn[0] != '[':
ref = getattr(ref, tkn)
else:
ref = ref[int(tkn[1:-1])]
except:
raise HTTPError(400, u"Bad request")
return ref

class RootHandler(RequestHandler):
def get(self):
self.write({'message': 'Online'})


class TPXpropertyHandler(RequestHandler):
def get(self):

global pymepix_connection_obj

arguments = self.get_arguments('param_name')

tkns = re.findall(r"[\w']+|\[\d+\]", arguments[0])

ref = pymepix_connection_obj
ref = get_path(ref, tkns)

if is_jsonable(ref):
self.write({arguments[0]: ref})
else:
raise HTTPError(405, u"Parameter value is not JSON serializable")


def post(self):
global pymepix_connection_obj

try:
data = json.loads(self.request.body)
except:
raise HTTPError(400, u"Bad request")

for key, val in data.items():
ref = pymepix_connection_obj
tkns = re.findall(r"[\w']+|\[\d+\]", key)
ref = get_path(ref, tkns[:-1])
if tkns[-1][0] != '[':
setattr(ref, tkns[-1], val)
else:
ref[int(tkns[-1][1:-1])] = val

self.write(data)

class TPXmethodHandler(RequestHandler):
def post(self):
global pymepix_connection_obj
try:
data = json.loads(self.request.body)
except:
raise HTTPError(400, u"Bad request")

try:
func_name = data['func_name']

tkns = re.findall(r"[\w']+|\[\d+\]", func_name)

ref = pymepix_connection_obj
ref = get_path(ref, tkns)

data.pop('func_name')
res = ref(**data)
if is_jsonable(res):
self.write({'result': res})
else:
self.write({'result': 'Result is not JSON serializable'})
except:
raise HTTPError(400, u"Bad request")

class PostprocessHandler(RequestHandler):
def post(self):
try:
data = json.loads(self.request.body)
except:
raise HTTPError(400, u"Bad request")

try:
run_post_processing(**data)
except:
raise HTTPError(400, u"Bad request")


def make_app():
urls = [
("/", RootHandler),
(r"/tpxproperty", TPXpropertyHandler),
(r"/tpxmethod", TPXmethodHandler),
(r"/postprocess", PostprocessHandler)
]
return Application(urls, debug=True)


def start_api(args):
global pymepix_connection_obj

if args.pixel_pipeline == 'centroid':
pipeline_class = CentroidPipeline
else:
pipeline_class = PixelPipeline

pymepix_connection_obj = PymepixConnection(cam_address=(args.ip, args.port),\
camera_generation=args.cam_gen,
pipeline_class=pipeline_class)


if len(pymepix_connection_obj) == 0:
logging.error(
"-------ERROR: SPIDR FOUND BUT NO VALID TIMEPIX DEVICE DETECTED ---------- "
)
quit()
if args.spx:
logging.info(f"Opening Sophy file {args.spx}")
pymepix_connection_obj[0].loadConfig(args.spx)

# Switch to TOF mode if set
if args.decode and args.tof:
pymepix_connection_obj[0].acquisition.enableEvents = True

# Set the bias voltage
pymepix_connection_obj.biasVoltage = args.bias

app = make_app()
app.listen(args.api_port)
IOLoop.instance().start()


def main():

parser = argparse.ArgumentParser(description="Timepix acquisition script")
Expand Down Expand Up @@ -168,6 +322,15 @@ def main():
help="Config file",
)

parser_connect.add_argument(
"-g",
"--cam_gen",
dest="cam_gen",
type=int,
default=3,
help="Camera generation",
)

parser_post_process = subparsers.add_parser(
"post-process", help="Perform post-processing with a acquired raw data file."
)
Expand Down Expand Up @@ -219,6 +382,104 @@ def main():
help="Config file",
)

parser_post_process.add_argument(
"-g",
"--cam_gen",
dest="cam_gen",
type=int,
default=3,
help="Camera generation",
)


parser_api_service = subparsers.add_parser(
"api-service", help="start api service."
)

parser_api_service.set_defaults(func=start_api)

parser_api_service.add_argument(
"-i",
"--ip",
dest="ip",
type=str,
default=cfg.default_cfg["timepix"]["tpx_ip"],
help="IP address of Timepix",
)

parser_api_service.add_argument(
"-p",
"--port",
dest="port",
type=int,
default=50000,
help="TCP port to use for the connection",
)

parser_api_service.add_argument(
"-api_p",
"--api_port",
dest="api_port",
type=int,
default=8080,
help="TCP port to use for API",
)

parser_api_service.add_argument(
"--config",
dest="cfg",
type=str,
default="default.yaml",
help="Config file",
)

parser_api_service.add_argument(
"-s", "--spx", dest="spx", type=str, help="Sophy config file to load"
)

parser_api_service.add_argument(
"-d",
"--decode",
dest="decode",
type=bool,
help="Store decoded values instead",
default=False,
)
parser_api_service.add_argument(
"-T",
"--tof",
dest="tof",
type=bool,
help="Compute TOF if decode is enabled",
default=False,
)

parser_api_service.add_argument(
"-v",
"--bias",
dest="bias",
type=float,
default=50,
help="Bias voltage in Volts",
)

parser_api_service.add_argument(
"-g",
"--cam_gen",
dest="cam_gen",
type=int,
default=3,
help="Camera generation",
)

parser_api_service.add_argument(
"-pl",
"--pipeline",
dest="pixel_pipeline",
type=str,
default='pixel',
help="Processing pipeline, options: centroid, pixel. Default - pixel",
)


args = parser.parse_args()
Expand Down
12 changes: 6 additions & 6 deletions pymepix/pymepix_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,11 @@ def __init__(self,

self.camera_generation = camera_generation

controllerClass = self.timepix_controller_class_factory(camera_generation)
controllerClass = self._timepix_controller_class_factory(camera_generation)

self._controller = controllerClass(spidr_address, pc_ip, udp_ip_port)

TimepixDeviceClass = self.timepix_device_class_factory(camera_generation)
TimepixDeviceClass = self._timepix_device_class_factory(camera_generation)
self._timepix_devices: list[TimepixDeviceClass] = []

self._data_queue = Queue()
Expand Down Expand Up @@ -210,7 +210,7 @@ def _pollCallback(self, data_type, data):
self._poll_buffer.append((data_type, data))

def _createTimepix(self, pipeline_class=PixelPipeline):
TimepixDeviceClass = self.timepix_device_class_factory(self.camera_generation)
TimepixDeviceClass = self._timepix_device_class_factory(self.camera_generation)
for x in self._controller:
status, enabled, locked = x.linkStatus
if enabled != 0 and locked == enabled:
Expand Down Expand Up @@ -305,15 +305,15 @@ def __len__(self):
def getDevice(self, num) -> TimepixDevice:
return self._timepix_devices[num]

def timepix_device_class_factory(self, camera_generation):
timepix_device_classes = {3: TimepixDevice, \
def _timepix_device_class_factory(self, camera_generation):
timepix_device_classes = {3: TimepixDevice,\
4: Timepix4Device}
if camera_generation in timepix_device_classes:
return timepix_device_classes[camera_generation]
else:
raise ValueError(f'No timepix device for camera generation {camera_generation}')

def timepix_controller_class_factory(self, camera_generation):
def _timepix_controller_class_factory(self, camera_generation):

timepix_controller_classes = {3: SPIDRController, \
4: Timepix4Controller}
Expand Down

0 comments on commit 62e9c66

Please sign in to comment.