Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 55 additions & 11 deletions src/instruments/abstract_instruments/optical_spectrum_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

# IMPORTS #####################################################################


import abc

from instruments.abstract_instruments import Instrument
from instruments.util_fns import ProxyList

# CLASSES #####################################################################

Expand All @@ -21,14 +21,29 @@ class OpticalSpectrumAnalyzer(Instrument, metaclass=abc.ABCMeta):
provide a consistent interface to the user.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._channel_count = 1

class Channel(metaclass=abc.ABCMeta):
"""
Abstract base class for physical channels on an optical spectrum analyzer.

All applicable concrete instruments should inherit from this ABC to
provide a consistent interface to the user.

Optical spectrum analyzers that only have a single channel do not need to
define their own concrete implementation of this class. Ones with
multiple channels need their own definition of this class, where
this class contains the concrete implementations of the below
abstract methods. Instruments with 1 channel have their concrete
implementations at the parent instrument level.
"""

def __init__(self, parent, name):
self._parent = parent
self._name = name

# METHODS #

@abc.abstractmethod
Expand All @@ -42,7 +57,10 @@ def wavelength(self, bin_format=True):
:return: The wavelength component of the waveform.
:rtype: `numpy.ndarray`
"""
raise NotImplementedError
if self._parent._channel_count == 1:
return self._parent.wavelength(bin_format=bin_format)
else:
raise NotImplementedError

@abc.abstractmethod
def data(self, bin_format=True):
Expand All @@ -55,22 +73,23 @@ def data(self, bin_format=True):
:return: The y-component of the waveform.
:rtype: `numpy.ndarray`
"""
raise NotImplementedError
if self._parent._channel_count == 1:
return self._parent.data(bin_format=bin_format)
else:
raise NotImplementedError

# PROPERTIES #

@property
@abc.abstractmethod
def channel(self):
"""
Gets an iterator or list for easy Pythonic access to the various
channel objects on the OSA instrument. Typically generated
by the `~instruments.util_fns.ProxyList` helper.
"""
raise NotImplementedError
return ProxyList(self, self.Channel, range(self._channel_count))

@property
@abc.abstractmethod
def start_wl(self):
"""
Gets/sets the the start wavelength of the OSA. This is
Expand All @@ -81,12 +100,10 @@ def start_wl(self):
raise NotImplementedError

@start_wl.setter
@abc.abstractmethod
def start_wl(self, newval):
raise NotImplementedError

@property
@abc.abstractmethod
def stop_wl(self):
"""
Gets/sets the the stop wavelength of the OSA. This is
Expand All @@ -97,12 +114,10 @@ def stop_wl(self):
raise NotImplementedError

@stop_wl.setter
@abc.abstractmethod
def stop_wl(self, newval):
raise NotImplementedError

@property
@abc.abstractmethod
def bandwidth(self):
"""
Gets/sets the the bandwidth of the OSA. This is
Expand All @@ -113,7 +128,6 @@ def bandwidth(self):
raise NotImplementedError

@bandwidth.setter
@abc.abstractmethod
def bandwidth(self, newval):
raise NotImplementedError

Expand All @@ -125,3 +139,33 @@ def start_sweep(self):
Forces a start sweep on the attached OSA.
"""
raise NotImplementedError

def wavelength(self, bin_format=True):
"""
Gets the x-axis of the specified data source channel. This is an
abstract property.

:param bool bin_format: If the waveform should be transfered in binary
(``True``) or ASCII (``False``) formats.
:return: The wavelength component of the waveform.
:rtype: `numpy.ndarray`
"""
if self._channel_count > 1:
return self.channel[0].wavelength(bin_format=bin_format)
else:
raise NotImplementedError

def data(self, bin_format=True):
"""
Gets the y-axis of the specified data source channel. This is an
abstract property.

:param bool bin_format: If the waveform should be transfered in binary
(``True``) or ASCII (``False``) formats.
:return: The y-component of the waveform.
:rtype: `numpy.ndarray`
"""
if self._channel_count > 1:
return self.channel[0].data(bin_format=bin_format)
else:
raise NotImplementedError
2 changes: 1 addition & 1 deletion src/instruments/yokogawa/yokogawa6370.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@


from enum import IntEnum, Enum
import hashlib
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanup unused import


from instruments.units import ureg as u

Expand Down Expand Up @@ -46,6 +45,7 @@ class Yokogawa6370(OpticalSpectrumAnalyzer):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._channel_count = len(self.Traces)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now required from metaclass. see channel property below for range


if isinstance(self._file, SocketCommunicator):
self.terminator = "\r\n" # TCP IP connection terminator
Expand Down
40 changes: 22 additions & 18 deletions tests/test_abstract_inst/test_optical_spectrum_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,9 @@
def osa(monkeypatch):
"""Patch and return Optical Spectrum Analyzer class for access."""
inst = ik.abstract_instruments.OpticalSpectrumAnalyzer
chan = ik.abstract_instruments.OpticalSpectrumAnalyzer.Channel
monkeypatch.setattr(inst, "__abstractmethods__", set())
return inst


@pytest.fixture
def osc(monkeypatch):
"""Patch and return OSAChannel class for access."""
inst = ik.abstract_instruments.OpticalSpectrumAnalyzer.Channel
monkeypatch.setattr(inst, "__abstractmethods__", set())
monkeypatch.setattr(chan, "__abstractmethods__", set())
return inst


Expand All @@ -37,8 +31,8 @@ def osc(monkeypatch):
def test_osa_channel(osa):
"""Get channel: ensure existence."""
with expected_protocol(osa, [], []) as inst:
with pytest.raises(NotImplementedError):
_ = inst.channel
ch = inst.channel[0]
assert isinstance(ch, ik.abstract_instruments.OpticalSpectrumAnalyzer.Channel)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the channel itself is automatically implemented now and - if a single channel instrument - will forward to the parent's channel. so this needed to be changed



def test_osa_start_wl(osa):
Expand Down Expand Up @@ -78,15 +72,25 @@ def test_osa_start_sweep(osa):
# OSAChannel #


def test_osa_channel_wavelength(osc):
@pytest.mark.parametrize("num_ch", [1, 5])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take care of single and multichannel instruments

def test_osa_channel_wavelength(osa, num_ch):
"""Channel wavelength method: ensure existence."""
inst = osc()
with pytest.raises(NotImplementedError):
inst.wavelength()
with expected_protocol(osa, [], []) as inst:
inst._channel_count = num_ch
ch = inst.channel[0]
with pytest.raises(NotImplementedError):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call single/mutli channel instrument through the inst.channel ProxyList function and ensure proper forwarding

ch.wavelength()
with pytest.raises(NotImplementedError):
inst.wavelength() # single channel instrument
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the single channel function to call (directly from instrument class) and ensure it exists and raises appropriate error



def test_osa_channel_data(osc):
@pytest.mark.parametrize("num_ch", [1, 5])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

def test_osa_channel_data(osa, num_ch):
"""Channel data method: ensure existence."""
inst = osc()
with pytest.raises(NotImplementedError):
inst.data()
with expected_protocol(osa, [], []) as inst:
inst._channel_count = num_ch
ch = inst.channel[0]
with pytest.raises(NotImplementedError):
ch.data()
with pytest.raises(NotImplementedError):
inst.data() # single channel instrument
3 changes: 1 addition & 2 deletions tests/test_yokogawa/test_yokogawa_6370.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

# IMPORTS #####################################################################

import hashlib
import struct

from hypothesis import (
Expand All @@ -15,7 +14,6 @@
import socket

import instruments as ik
from instruments.abstract_instruments.comm import SocketCommunicator
from instruments.optional_dep_finder import numpy
from tests import (
expected_protocol,
Expand All @@ -31,6 +29,7 @@

def test_channel_is_channel_class():
inst = ik.yokogawa.Yokogawa6370.open_test()
assert inst._channel_count == len(inst.Traces)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ik.yokogawa.Yokogawa6370.Traces is 5 channels long, this is provided to the proxy list. Making sure here we set this now, as it is a new variable defined in the metaclass.

assert isinstance(inst.channel["A"], inst.Channel) is True


Expand Down
Loading