forked from M-Labs/artiq
Compare commits
17 Commits
180a0fface
...
1f71cb9538
Author | SHA1 | Date | |
---|---|---|---|
|
1f71cb9538 | ||
5392ba3812 | |||
611a4bcaab | |||
0389be14fe | |||
27241cb2c3 | |||
a4ad97a3fd | |||
4ddad5fd17 | |||
|
d78ebb6bbb | ||
464befba63 | |||
1075e89514 | |||
342fa5aaf6 | |||
fc5bc1bb5c | |||
9e655332e3 | |||
b3678e8bfb | |||
|
dcc0f8d579 | ||
afbd83799c | |||
641d4cc362 |
@ -19,6 +19,7 @@ ARTIQ-9 (Unreleased)
|
|||||||
for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module.
|
for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module.
|
||||||
* Dashboard:
|
* Dashboard:
|
||||||
- Experiment windows can have different colors, selected by the user.
|
- Experiment windows can have different colors, selected by the user.
|
||||||
|
- The Log pane now adapts to dark system color themes.
|
||||||
- Schedule display columns can now be reordered and shown/hidden using the table
|
- Schedule display columns can now be reordered and shown/hidden using the table
|
||||||
header context menu.
|
header context menu.
|
||||||
- State files are now automatically backed up upon successful loading.
|
- State files are now automatically backed up upon successful loading.
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
from numpy import array, int32, int64, uint8, uint16, iinfo
|
from numpy import array, int32, int64, ndarray
|
||||||
|
|
||||||
|
|
||||||
from artiq.language.core import syscall, kernel
|
from artiq.language.core import syscall, kernel
|
||||||
from artiq.language.types import TInt32, TNone, TList
|
from artiq.language.types import TInt32, TNone, TList
|
||||||
from artiq.coredevice.rtio import rtio_output, rtio_input_timestamped_data
|
from artiq.coredevice.rtio import rtio_output, rtio_input_timestamped_data
|
||||||
from artiq.coredevice.grabber import OutOfSyncException, GrabberTimeoutException
|
|
||||||
from artiq.experiment import *
|
from artiq.experiment import *
|
||||||
|
|
||||||
|
|
||||||
|
class OutOfSyncException(Exception):
|
||||||
|
"""Raised when an incorrect number of ROI engine outputs has been
|
||||||
|
retrieved from the RTIO input FIFO."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CXPGrabberTimeoutException(Exception):
|
||||||
|
"""Raised when a timeout occurs while attempting to read CoaXPress Grabber RTIO input events."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@syscall(flags={"nounwind"})
|
@syscall(flags={"nounwind"})
|
||||||
def cxp_download_xml_file(buffer: TList(TInt32)) -> TInt32:
|
def cxp_download_xml_file(buffer: TList(TInt32)) -> TInt32:
|
||||||
raise NotImplementedError("syscall not simulated")
|
raise NotImplementedError("syscall not simulated")
|
||||||
@ -24,7 +35,7 @@ def cxp_write32(addr: TInt32, val: TInt32) -> TNone:
|
|||||||
|
|
||||||
|
|
||||||
@syscall(flags={"nounwind"})
|
@syscall(flags={"nounwind"})
|
||||||
def cxp_start_roi_viewer(x0: TInt32, x1: TInt32, y0: TInt32, y1: TInt32) -> TNone:
|
def cxp_start_roi_viewer(x0: TInt32, y0: TInt32, x1: TInt32, y1: TInt32) -> TNone:
|
||||||
raise NotImplementedError("syscall not simulated")
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +46,79 @@ def cxp_download_roi_viewer_frame(
|
|||||||
raise NotImplementedError("syscall not simulated")
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(data, file_path):
|
||||||
|
"""
|
||||||
|
Write big-endian encoded data to PC
|
||||||
|
|
||||||
|
:param data: a list of 32-bit integers
|
||||||
|
:param file_path: a relative path on PC
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
To download the XML file to PC: ::
|
||||||
|
|
||||||
|
# Prepare a big enough buffer
|
||||||
|
buffer = [0] * 25600
|
||||||
|
|
||||||
|
# Read the XML file and write it to PC
|
||||||
|
cxp_grabber.read_local_xml(buffer)
|
||||||
|
write_file(buffer, "camera_setting.xml")
|
||||||
|
|
||||||
|
"""
|
||||||
|
array(data, dtype=">i").tofile(file_path)
|
||||||
|
|
||||||
|
|
||||||
|
def write_pgm(frame, file_path, pixel_width):
|
||||||
|
"""
|
||||||
|
Write the frame as PGM file to PC.
|
||||||
|
|
||||||
|
:param frame: a 2D array of 32-bit integers
|
||||||
|
:param file_path: a relative path on PC
|
||||||
|
:param pixel_width: bit depth that the PGM will use (8 or 16)
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
To capture a 32x64 frame and write it as a 8-bit PGM file to PC: ::
|
||||||
|
|
||||||
|
# Prepare a 32x64 2D array
|
||||||
|
frame = numpy.array([[0] * 32] * 64)
|
||||||
|
|
||||||
|
# Setup the camera to use LinkTriger0 and start acquisition
|
||||||
|
# (Read the camera setting XML file for details)
|
||||||
|
cxp_grabber.write32(TRIG_SETTING_ADDR, 0)
|
||||||
|
...
|
||||||
|
|
||||||
|
# Setup ROI viewer coordinate and start the viewer capture
|
||||||
|
cxp_grabber.start_roi_viewer(0, 0, 32, 64)
|
||||||
|
|
||||||
|
# Send LinkTrigger0
|
||||||
|
cxp_grabber.send_cxp_linktrigger(0)
|
||||||
|
|
||||||
|
# Read the frame from ROI viewer and write it as a 8-bit PGM image to PC
|
||||||
|
cxp_grabber.read_roi_viewer_frame(frame)
|
||||||
|
write_pgm(frame, "frame.pgm", 8)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not isinstance(frame, ndarray):
|
||||||
|
raise ValueError("Frame must be a numpy array")
|
||||||
|
|
||||||
|
if pixel_width == 8:
|
||||||
|
frame = frame.astype("u1")
|
||||||
|
elif pixel_width == 16:
|
||||||
|
# PGM use big-endian
|
||||||
|
frame = frame.astype(">u2")
|
||||||
|
else:
|
||||||
|
raise ValueError("PGM file format only supports 8-bit or 16-bit per pixel")
|
||||||
|
|
||||||
|
# Save as PGM binary variant
|
||||||
|
# https://en.wikipedia.org/wiki/Netpbm#Description
|
||||||
|
with open(file_path, "wb") as file:
|
||||||
|
max_value = (2**pixel_width) - 1
|
||||||
|
width, height = len(frame[0]), len(frame)
|
||||||
|
file.write(f"P5\n{width} {height}\n{max_value}\n".encode("ASCII"))
|
||||||
|
file.write(frame.tobytes())
|
||||||
|
|
||||||
|
|
||||||
class CXPGrabber:
|
class CXPGrabber:
|
||||||
"""Driver for the CoaXPress Grabber camera interface."""
|
"""Driver for the CoaXPress Grabber camera interface."""
|
||||||
|
|
||||||
@ -153,7 +237,7 @@ class CXPGrabber:
|
|||||||
this call or the next.
|
this call or the next.
|
||||||
|
|
||||||
If the timeout is reached before data is available, the exception
|
If the timeout is reached before data is available, the exception
|
||||||
:exc:`artiq.coredevice.grabber.GrabberTimeoutException` is raised.
|
:exc:`CXPGrabberTimeoutException` is raised.
|
||||||
|
|
||||||
:param timeout_mu: Timestamp at which a timeout will occur. Set to -1
|
:param timeout_mu: Timestamp at which a timeout will occur. Set to -1
|
||||||
(default) to disable timeout.
|
(default) to disable timeout.
|
||||||
@ -162,7 +246,9 @@ class CXPGrabber:
|
|||||||
timeout_mu, self.roi_gating_ch
|
timeout_mu, self.roi_gating_ch
|
||||||
)
|
)
|
||||||
if timestamp == -1:
|
if timestamp == -1:
|
||||||
raise GrabberTimeoutException("Timeout before Grabber frame available")
|
raise CXPGrabberTimeoutException(
|
||||||
|
"Timeout before CoaXPress Grabber frame available"
|
||||||
|
)
|
||||||
if sentinel != self.sentinel:
|
if sentinel != self.sentinel:
|
||||||
raise OutOfSyncException
|
raise OutOfSyncException
|
||||||
|
|
||||||
@ -173,7 +259,7 @@ class CXPGrabber:
|
|||||||
if roi_output == self.sentinel:
|
if roi_output == self.sentinel:
|
||||||
raise OutOfSyncException
|
raise OutOfSyncException
|
||||||
if timestamp == -1:
|
if timestamp == -1:
|
||||||
raise GrabberTimeoutException(
|
raise CXPGrabberTimeoutException(
|
||||||
"Timeout retrieving ROIs (attempting to read more ROIs than enabled?)"
|
"Timeout retrieving ROIs (attempting to read more ROIs than enabled?)"
|
||||||
)
|
)
|
||||||
data[i] = roi_output
|
data[i] = roi_output
|
||||||
@ -203,97 +289,52 @@ class CXPGrabber:
|
|||||||
cxp_write32(address, value)
|
cxp_write32(address, value)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def download_local_xml(self, file_path, buffer_size=102400):
|
def read_local_xml(self, buffer):
|
||||||
"""
|
"""
|
||||||
Downloads the XML setting file to PC from the camera if available.
|
Read the XML setting file from the camera if available.
|
||||||
|
Data will be in 32-bit big-endian encoding.
|
||||||
The file format may be .zip or .xml depending on the camera model.
|
The file format may be .zip or .xml depending on the camera model.
|
||||||
|
|
||||||
.. warning:: This is NOT a real-time operation.
|
.. warning:: This is NOT a real-time operation.
|
||||||
|
|
||||||
:param file_path: a relative path on PC
|
:param buffer: list to be filled
|
||||||
:param buffer_size: size of read buffer expressed in bytes; must be a multiple of 4
|
:returns: number of 32-bit words read
|
||||||
"""
|
"""
|
||||||
buffer = [0] * (buffer_size // 4)
|
return cxp_download_xml_file(buffer)
|
||||||
size_read = cxp_download_xml_file(buffer)
|
|
||||||
self._write_file(buffer[:size_read], file_path)
|
|
||||||
|
|
||||||
@rpc
|
|
||||||
def _write_file(self, data, file_path):
|
|
||||||
"""
|
|
||||||
Write big endian encoded data into a file
|
|
||||||
|
|
||||||
:param data: a list of 32-bit integers
|
|
||||||
:param file_path: a relative path on PC
|
|
||||||
"""
|
|
||||||
byte_arr = bytearray()
|
|
||||||
for d in data:
|
|
||||||
byte_arr += d.to_bytes(4, "big", signed=True)
|
|
||||||
with open(file_path, "wb") as binary_file:
|
|
||||||
binary_file.write(byte_arr)
|
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def start_roi_viewer(self, x0, x1, y0, y1):
|
def start_roi_viewer(self, x0, y0, x1, y1):
|
||||||
"""
|
"""
|
||||||
Defines the coordinates of ROI viewer and start the capture.
|
Defines the coordinates of ROI viewer and start the capture.
|
||||||
|
|
||||||
Unlike :exc:`setup_roi`, ROI viewer has a maximum size limit of 4096 pixels.
|
Unlike :exc:`setup_roi`, ROI viewer has a maximum height limit of 1024 and total size limit of 4096 pixels.
|
||||||
|
|
||||||
.. warning:: This is NOT a real-time operation.
|
.. warning:: This is NOT a real-time operation.
|
||||||
"""
|
"""
|
||||||
cxp_start_roi_viewer(x0, x1, y0, y1)
|
cxp_start_roi_viewer(x0, y0, x1, y1)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def download_roi_viewer_frame(self, file_path):
|
def read_roi_viewer_frame(self, frame):
|
||||||
"""
|
"""
|
||||||
Downloads the ROI viewer frame as a PGM file to PC.
|
Read the ROI viewer frame.
|
||||||
|
|
||||||
The user must :exc:`start_roi_viewer` and trigger the camera before the frame is avaiable.
|
The user must :exc:`start_roi_viewer` and trigger the camera before the frame is available.
|
||||||
|
|
||||||
.. warning:: This is NOT a real-time operation.
|
.. warning:: This is NOT a real-time operation.
|
||||||
|
|
||||||
:param file_path: a relative path on PC
|
:param frame: a 2D array of 32-bit integers
|
||||||
|
:returns: the frame bit depth
|
||||||
"""
|
"""
|
||||||
buffer = [0] * 1024
|
buffer = [0] * 1024
|
||||||
width, height, pixel_width = cxp_download_roi_viewer_frame(buffer)
|
width, height, pixel_width = cxp_download_roi_viewer_frame(buffer)
|
||||||
self._write_pgm(buffer, width, height, pixel_width, file_path)
|
if height != len(frame) or width != len(frame[0]):
|
||||||
|
raise ValueError(
|
||||||
|
"The frame matrix size is not the same as ROI viewer frame size"
|
||||||
|
)
|
||||||
|
|
||||||
@rpc
|
for y in range(height):
|
||||||
def _write_pgm(self, data, width, height, pixel_width, file_path):
|
offset = (((width + 3) & (~3)) // 4) * y
|
||||||
"""
|
for x in range(width):
|
||||||
Write pixel data into a PGM file.
|
# each buffer element holds 4 pixels
|
||||||
|
frame[y][x] = (buffer[offset + (x // 4)] >> (16 * (x % 4))) & 0xFFFF
|
||||||
:param data: a list of 64-bit integers
|
return pixel_width
|
||||||
:param file_path: a relative path on PC
|
|
||||||
"""
|
|
||||||
if ".pgm" not in file_path.lower():
|
|
||||||
raise ValueError("The file extension must be .pgm")
|
|
||||||
|
|
||||||
pixels = []
|
|
||||||
width_aligned = (width + 3) & (~3)
|
|
||||||
for d in data[: width_aligned * height // 4]:
|
|
||||||
pixels += [
|
|
||||||
d & 0xFFFF,
|
|
||||||
(d >> 16) & 0xFFFF,
|
|
||||||
(d >> 32) & 0xFFFF,
|
|
||||||
(d >> 48) & 0xFFFF,
|
|
||||||
]
|
|
||||||
|
|
||||||
if pixel_width == 8:
|
|
||||||
dtype = uint8
|
|
||||||
else:
|
|
||||||
dtype = uint16
|
|
||||||
# pad to 16-bit for compatibility, as most software can only read 8, 16-bit PGM
|
|
||||||
pixels = [p << (16 - pixel_width) for p in pixels]
|
|
||||||
|
|
||||||
# trim the frame if the width is not multiple of 4
|
|
||||||
frame = array([pixels], dtype).reshape((height, width_aligned))[:, :width]
|
|
||||||
|
|
||||||
# save as PGM binary variant
|
|
||||||
# https://en.wikipedia.org/wiki/Netpbm#Description
|
|
||||||
with open(file_path, "wb") as file:
|
|
||||||
file.write(f"P5\n{width} {height}\n{iinfo(dtype).max}\n".encode("ASCII"))
|
|
||||||
if dtype == uint8:
|
|
||||||
file.write(frame.tobytes())
|
|
||||||
else:
|
|
||||||
# PGM use big endian
|
|
||||||
file.write(frame.astype(">u2").tobytes())
|
|
||||||
|
@ -223,10 +223,11 @@ mod imp {
|
|||||||
// mask in format of 1 << channel (or 0 for disabling output)
|
// mask in format of 1 << channel (or 0 for disabling output)
|
||||||
// PCA9548 support only for now
|
// PCA9548 support only for now
|
||||||
start(busno)?;
|
start(busno)?;
|
||||||
write(busno, address << 1)?;
|
let write_result = write(busno, address << 1)
|
||||||
write(busno, mask)?;
|
.and_then( |_| write(busno, mask) );
|
||||||
stop(busno)?;
|
let stop_result = stop(busno);
|
||||||
Ok(())
|
|
||||||
|
write_result.and(stop_result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,19 +40,21 @@ impl EEPROM {
|
|||||||
self.select()?;
|
self.select()?;
|
||||||
|
|
||||||
i2c::start(self.busno)?;
|
i2c::start(self.busno)?;
|
||||||
i2c::write(self.busno, self.address)?;
|
let read_result = i2c::write(self.busno, self.address)
|
||||||
i2c::write(self.busno, addr)?;
|
.and_then( |_| i2c::write(self.busno, addr))
|
||||||
|
.and_then( |_| i2c::restart(self.busno))
|
||||||
|
.and_then( |_| i2c::write(self.busno, self.address | 1))
|
||||||
|
.and_then( |_| {
|
||||||
|
let buf_len = buf.len();
|
||||||
|
for (i, byte) in buf.iter_mut().enumerate() {
|
||||||
|
*byte = i2c::read(self.busno, i < buf_len - 1)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
i2c::restart(self.busno)?;
|
let stop_result = i2c::stop(self.busno);
|
||||||
i2c::write(self.busno, self.address | 1)?;
|
|
||||||
let buf_len = buf.len();
|
|
||||||
for (i, byte) in buf.iter_mut().enumerate() {
|
|
||||||
*byte = i2c::read(self.busno, i < buf_len - 1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
i2c::stop(self.busno)?;
|
read_result.and(stop_result)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// > The 24AA02XEXX is programmed at the factory with a
|
/// > The 24AA02XEXX is programmed at the factory with a
|
||||||
|
@ -151,11 +151,12 @@ impl IoExpander {
|
|||||||
|
|
||||||
fn write(&self, addr: u8, value: u8) -> Result<(), i2c::Error> {
|
fn write(&self, addr: u8, value: u8) -> Result<(), i2c::Error> {
|
||||||
i2c::start(self.busno)?;
|
i2c::start(self.busno)?;
|
||||||
i2c::write(self.busno, self.address)?;
|
let write_result = i2c::write(self.busno, self.address)
|
||||||
i2c::write(self.busno, addr)?;
|
.and_then( |_| i2c::write(self.busno, addr))
|
||||||
i2c::write(self.busno, value)?;
|
.and_then( |_| i2c::write(self.busno, value));
|
||||||
i2c::stop(self.busno)?;
|
|
||||||
Ok(())
|
let stop_result = i2c::stop(self.busno);
|
||||||
|
write_result.and(stop_result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_ack(&self) -> Result<bool, i2c::Error> {
|
fn check_ack(&self) -> Result<bool, i2c::Error> {
|
||||||
|
@ -303,8 +303,9 @@ class PixelUnpacker(Module):
|
|||||||
|
|
||||||
sink_cases = {}
|
sink_cases = {}
|
||||||
for i in range(ring_buf_size//sink_dw):
|
for i in range(ring_buf_size//sink_dw):
|
||||||
|
byte = [self.sink.data[i * 8 : (i + 1) * 8] for i in range(sink_dw // 8)]
|
||||||
sink_cases[i] = [
|
sink_cases[i] = [
|
||||||
ring_buf[sink_dw*i:sink_dw*(i+1)].eq(self.sink.data),
|
ring_buf[sink_dw*i:sink_dw*(i+1)].eq(Cat([b[::-1] for b in byte])),
|
||||||
]
|
]
|
||||||
self.sync += If(self.sink.stb, Case(w_cnt, sink_cases))
|
self.sync += If(self.sink.stb, Case(w_cnt, sink_cases))
|
||||||
|
|
||||||
@ -314,7 +315,7 @@ class PixelUnpacker(Module):
|
|||||||
for j in range(4):
|
for j in range(4):
|
||||||
source_cases[i].append(
|
source_cases[i].append(
|
||||||
self.source.data[max_pixel_width * j : max_pixel_width * (j + 1)].eq(
|
self.source.data[max_pixel_width * j : max_pixel_width * (j + 1)].eq(
|
||||||
ring_buf[(source_dw * i) + (size * j) : (source_dw * i) + (size * (j + 1))]
|
ring_buf[(source_dw * i) + (size * j) : (source_dw * i) + (size * (j + 1))][::-1]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -403,7 +404,10 @@ class PixelCoordinateTracker(Module):
|
|||||||
pix.x.eq(x_r),
|
pix.x.eq(x_r),
|
||||||
pix.y.eq(y_r),
|
pix.y.eq(y_r),
|
||||||
pix.gray.eq(self.sink.data[max_pixel_width*i:max_pixel_width*(i+1)]),
|
pix.gray.eq(self.sink.data[max_pixel_width*i:max_pixel_width*(i+1)]),
|
||||||
)
|
),
|
||||||
|
If(pix.eof,
|
||||||
|
pix.y.eq(self.y_size),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,11 @@ from collections import namedtuple
|
|||||||
_WORDLAYOUT = namedtuple("WordLayout", ["data", "k", "stb", "eop"])
|
_WORDLAYOUT = namedtuple("WordLayout", ["data", "k", "stb", "eop"])
|
||||||
|
|
||||||
|
|
||||||
|
def _switch_bit_order(s, width):
|
||||||
|
bits = "{:0{width}b}".format(s, width=width)
|
||||||
|
return int(bits[::-1], 2)
|
||||||
|
|
||||||
|
|
||||||
def MonoPixelPacketGenerator(
|
def MonoPixelPacketGenerator(
|
||||||
x_size,
|
x_size,
|
||||||
y_size,
|
y_size,
|
||||||
@ -21,7 +26,7 @@ def MonoPixelPacketGenerator(
|
|||||||
for x in range(x_size):
|
for x in range(x_size):
|
||||||
# full white pixel
|
# full white pixel
|
||||||
gray = (2**pixel_width) - 1
|
gray = (2**pixel_width) - 1
|
||||||
packed += gray << x * pixel_width
|
packed += _switch_bit_order(gray, pixel_width) << x * pixel_width
|
||||||
|
|
||||||
# Line marker
|
# Line marker
|
||||||
packet += [
|
packet += [
|
||||||
@ -41,11 +46,13 @@ def MonoPixelPacketGenerator(
|
|||||||
|
|
||||||
for i in range(words_per_image_line):
|
for i in range(words_per_image_line):
|
||||||
serialized = (packed & (0xFFFF_FFFF << i * word_width)) >> i * word_width
|
serialized = (packed & (0xFFFF_FFFF << i * word_width)) >> i * word_width
|
||||||
|
word = []
|
||||||
|
for j in range(4):
|
||||||
|
word += [C(_switch_bit_order((serialized >> 8 * j) & 0xFF, 8), 8)]
|
||||||
|
|
||||||
eop = 1 if ((i == words_per_image_line - 1) and with_eol_marked) else 0
|
eop = 1 if ((i == words_per_image_line - 1) and with_eol_marked) else 0
|
||||||
packet.append(
|
packet.append(
|
||||||
_WORDLAYOUT(
|
_WORDLAYOUT(data=Cat(word), k=Replicate(0, 4), stb=1, eop=eop),
|
||||||
data=C(serialized, word_width), k=Replicate(0, 4), stb=1, eop=eop
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return packet
|
return packet
|
||||||
|
@ -43,7 +43,7 @@ class _LogFilterProxyModel(QtCore.QSortFilterProxyModel):
|
|||||||
|
|
||||||
|
|
||||||
class _Model(QtCore.QAbstractItemModel):
|
class _Model(QtCore.QAbstractItemModel):
|
||||||
def __init__(self):
|
def __init__(self, palette):
|
||||||
QtCore.QAbstractTableModel.__init__(self)
|
QtCore.QAbstractTableModel.__init__(self)
|
||||||
|
|
||||||
self.headers = ["Source", "Message"]
|
self.headers = ["Source", "Message"]
|
||||||
@ -58,11 +58,16 @@ class _Model(QtCore.QAbstractItemModel):
|
|||||||
|
|
||||||
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.SystemFont.FixedFont)
|
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.SystemFont.FixedFont)
|
||||||
|
|
||||||
self.white = QtGui.QBrush(QtGui.QColor(255, 255, 255))
|
self.default_bg = palette.base()
|
||||||
self.black = QtGui.QBrush(QtGui.QColor(0, 0, 0))
|
self.default_fg = palette.text()
|
||||||
self.debug_fg = QtGui.QBrush(QtGui.QColor(55, 55, 55))
|
self.debug_fg = palette.placeholderText()
|
||||||
self.warning_bg = QtGui.QBrush(QtGui.QColor(255, 255, 180))
|
is_dark_mode = self.default_bg.color().lightness() < self.default_fg.color().lightness()
|
||||||
self.error_bg = QtGui.QBrush(QtGui.QColor(255, 150, 150))
|
if is_dark_mode:
|
||||||
|
self.warning_bg = QtGui.QBrush(QtGui.QColor(90, 74, 0))
|
||||||
|
self.error_bg = QtGui.QBrush(QtGui.QColor(98, 24, 24))
|
||||||
|
else:
|
||||||
|
self.warning_bg = QtGui.QBrush(QtGui.QColor(255, 255, 180))
|
||||||
|
self.error_bg = QtGui.QBrush(QtGui.QColor(255, 150, 150))
|
||||||
|
|
||||||
def headerData(self, col, orientation, role):
|
def headerData(self, col, orientation, role):
|
||||||
if (orientation == QtCore.Qt.Orientation.Horizontal
|
if (orientation == QtCore.Qt.Orientation.Horizontal
|
||||||
@ -163,13 +168,13 @@ class _Model(QtCore.QAbstractItemModel):
|
|||||||
elif level >= logging.WARNING:
|
elif level >= logging.WARNING:
|
||||||
return self.warning_bg
|
return self.warning_bg
|
||||||
else:
|
else:
|
||||||
return self.white
|
return self.default_bg
|
||||||
elif role == QtCore.Qt.ItemDataRole.ForegroundRole:
|
elif role == QtCore.Qt.ItemDataRole.ForegroundRole:
|
||||||
level = self.entries[msgnum][0]
|
level = self.entries[msgnum][0]
|
||||||
if level <= logging.DEBUG:
|
if level <= logging.DEBUG:
|
||||||
return self.debug_fg
|
return self.debug_fg
|
||||||
else:
|
else:
|
||||||
return self.black
|
return self.default_fg
|
||||||
elif role == QtCore.Qt.ItemDataRole.DisplayRole:
|
elif role == QtCore.Qt.ItemDataRole.DisplayRole:
|
||||||
v = self.entries[msgnum]
|
v = self.entries[msgnum]
|
||||||
column = index.column()
|
column = index.column()
|
||||||
@ -265,7 +270,7 @@ class LogDock(QDockWidgetCloseDetect):
|
|||||||
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
||||||
self.log.header().resizeSection(0, 26*cw)
|
self.log.header().resizeSection(0, 26*cw)
|
||||||
|
|
||||||
self.model = _Model()
|
self.model = _Model(self.palette())
|
||||||
self.proxy_model = _LogFilterProxyModel()
|
self.proxy_model = _LogFilterProxyModel()
|
||||||
self.proxy_model.setSourceModel(self.model)
|
self.proxy_model.setSourceModel(self.model)
|
||||||
self.log.setModel(self.proxy_model)
|
self.log.setModel(self.proxy_model)
|
||||||
|
@ -15,6 +15,7 @@ import traceback
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import linecache
|
import linecache
|
||||||
|
import threading
|
||||||
|
|
||||||
import h5py
|
import h5py
|
||||||
|
|
||||||
@ -38,23 +39,42 @@ from artiq import __version__ as artiq_version
|
|||||||
|
|
||||||
|
|
||||||
ipc = None
|
ipc = None
|
||||||
|
ipc_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
def get_object():
|
def get_object():
|
||||||
line = ipc.readline().decode()
|
ipc_lock.acquire()
|
||||||
return pyon.decode(line)
|
try:
|
||||||
|
line = ipc.readline()
|
||||||
|
finally:
|
||||||
|
ipc_lock.release()
|
||||||
|
return pyon.decode(line.decode())
|
||||||
|
|
||||||
|
|
||||||
def put_object(obj):
|
def put_object(obj):
|
||||||
ds = pyon.encode(obj)
|
ds = (pyon.encode(obj) + "\n").encode()
|
||||||
ipc.write((ds + "\n").encode())
|
ipc_lock.acquire()
|
||||||
|
try:
|
||||||
|
ipc.write(ds)
|
||||||
|
finally:
|
||||||
|
ipc_lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
def put_and_get_object(obj):
|
||||||
|
ds = (pyon.encode(obj) + "\n").encode()
|
||||||
|
ipc_lock.acquire()
|
||||||
|
try:
|
||||||
|
ipc.write(ds)
|
||||||
|
line = ipc.readline()
|
||||||
|
finally:
|
||||||
|
ipc_lock.release()
|
||||||
|
return pyon.decode(line.decode())
|
||||||
|
|
||||||
|
|
||||||
def make_parent_action(action):
|
def make_parent_action(action):
|
||||||
def parent_action(*args, **kwargs):
|
def parent_action(*args, **kwargs):
|
||||||
request = {"action": action, "args": args, "kwargs": kwargs}
|
request = {"action": action, "args": args, "kwargs": kwargs}
|
||||||
put_object(request)
|
reply = put_and_get_object(request)
|
||||||
reply = get_object()
|
|
||||||
if "action" in reply:
|
if "action" in reply:
|
||||||
if reply["action"] == "terminate":
|
if reply["action"] == "terminate":
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
14
flake.lock
generated
14
flake.lock
generated
@ -48,11 +48,11 @@
|
|||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1743494143,
|
"lastModified": 1744341011,
|
||||||
"narHash": "sha256-OxeNED91hCgVsbgwRUpmP5BJ4dtilMxF2otGsQ+UBaQ=",
|
"narHash": "sha256-sl8DJxAtqdLUxztNcgUWd9Xp1q271BLc/MfXmJlLkck=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "e4f6fbeeebd8d888f2a12d5be027c9394a37ad89",
|
"rev": "800edf35db6ddb05b1f89d13b422b78d12ee024c",
|
||||||
"revCount": 1583,
|
"revCount": 1588,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.m-labs.hk/m-labs/nac3.git"
|
"url": "https://git.m-labs.hk/m-labs/nac3.git"
|
||||||
},
|
},
|
||||||
@ -63,11 +63,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1741851582,
|
"lastModified": 1744098102,
|
||||||
"narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=",
|
"narHash": "sha256-tzCdyIJj9AjysC3OuKA+tMD/kDEDAF9mICPDU7ix0JA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6607cf789e541e7873d40d3a8f7815ea92204f32",
|
"rev": "c8cd81426f45942bb2906d5ed2fe21d2f19d95b7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user