Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,7 +0,0 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
build/
|
||||
dist/
|
||||
MANIFEST
|
||||
*.egg-info
|
674
LICENSE
674
LICENSE
@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
@ -1,4 +0,0 @@
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include qspectrumanalyzer.desktop
|
||||
include qspectrumanalyzer.png
|
30
PKGBUILD
30
PKGBUILD
@ -1,30 +0,0 @@
|
||||
# Maintainer: Michal Krenek (Mikos) <m.krenek@gmail.com>
|
||||
pkgname=qspectrumanalyzer
|
||||
pkgver=2.2.0
|
||||
pkgrel=1
|
||||
pkgdesc="Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power, hackrf_sweep, rtl_power, rx_power and other backends)"
|
||||
arch=('any')
|
||||
url="https://github.com/xmikos/qspectrumanalyzer"
|
||||
license=('GPL3')
|
||||
depends=('python-qt.py' 'python-pyqt5' 'python-pyqtgraph' 'soapy_power>=1.6.0')
|
||||
makedepends=('python-setuptools')
|
||||
optdepends=(
|
||||
'hackrf: hackrf_sweep backend (wideband spectrum monitoring with sweep rate of 8 GHz/s)'
|
||||
'rtl_power_fftw-git: alternative RTL-SDR backend using FFTW library (much faster than rtl_power)'
|
||||
'rtl-sdr-keenerd-git: better version of rtl_power backend'
|
||||
'rtl-sdr: original rtl_power backend (slightly broken, use rtl-sdr-keenerd-git instead)'
|
||||
'rx_tools: rx_power backend (universal SoapySDR based backend, but seems slow and buggy)'
|
||||
)
|
||||
source=(https://github.com/xmikos/qspectrumanalyzer/archive/v$pkgver.tar.gz)
|
||||
|
||||
build() {
|
||||
cd "$srcdir/$pkgname-$pkgver"
|
||||
python setup.py build
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$srcdir/$pkgname-$pkgver"
|
||||
python setup.py install --root="$pkgdir"
|
||||
}
|
||||
|
||||
# vim:set ts=2 sw=2 et:
|
184
README.rst
184
README.rst
@ -1,184 +0,0 @@
|
||||
QSpectrumAnalyzer
|
||||
=================
|
||||
|
||||
Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power,
|
||||
hackrf_sweep, rtl_power, rx_power and other backends)
|
||||
|
||||
Screenshots
|
||||
-----------
|
||||
|
||||
.. image:: https://xmikos.github.io/qspectrumanalyzer/qspectrumanalyzer_screenshot.png
|
||||
|
||||
.. image:: https://xmikos.github.io/qspectrumanalyzer/qspectrumanalyzer_screenshot2.png
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
- Python >= 3.3
|
||||
- PyQt4 / PyQt5 / PySide / PySide2
|
||||
- Qt.py (https://github.com/mottosso/Qt.py)
|
||||
- PyQtGraph (http://www.pyqtgraph.org)
|
||||
- soapy_power (https://github.com/xmikos/soapy_power)
|
||||
- Optional: hackrf / rtl-sdr / rtl_power_fftw / rx_tools
|
||||
|
||||
Backends
|
||||
--------
|
||||
|
||||
Default backend
|
||||
***************
|
||||
|
||||
- **soapy_power** (https://github.com/xmikos/soapy_power)
|
||||
|
||||
``soapy_power`` is the default and recommended universal SDR backend in QSpectrumAnalyzer.
|
||||
It is based on `SoapySDR <https://github.com/pothosware/SoapySDR>`_ and supports
|
||||
nearly all SDR platforms (RTL-SDR, HackRF, Airspy, SDRplay, LimeSDR, bladeRF,
|
||||
USRP and some other SDR devices). It is highly configurable (see additional parameters
|
||||
help in *Settings* menu) and supports short acquisition time for
|
||||
near real-time continuous measurement.
|
||||
|
||||
Other backends
|
||||
**************
|
||||
|
||||
- **hackrf_sweep** (https://github.com/mossmann/hackrf)
|
||||
|
||||
``hackrf_sweep`` backend enables wideband spectrum monitoring by rapidly retuning the radio
|
||||
without requiring individual tuning requests from the host computer. This allows unprecedented
|
||||
sweep rate of 8 GHz per second. Only HackRF is supported.
|
||||
|
||||
- **rtl_power_fftw** (https://github.com/AD-Vega/rtl-power-fftw)
|
||||
|
||||
``rtl_power_fftw`` is alternative backend for RTL-SDR devices and has various
|
||||
benefits over ``rtl_power``. E.g. better FFT performance (thanks to
|
||||
use of ``fftw`` library) and possibility to use short acquisition time
|
||||
for near real-time continuous measurement (minimum interval in original
|
||||
``rtl_power`` is 1 second).
|
||||
|
||||
- **rtl_power** (https://github.com/keenerd/rtl-sdr)
|
||||
|
||||
``rtl_power`` is original backend for RTL-SDR devices. There are better alternatives now, but
|
||||
if you want to use it, you should use `Keenerds fork of rtl-sdr <https://github.com/keenerd/rtl-sdr>`_
|
||||
(latest Git revision), because ``rtl_power`` in original rtl-sdr package (from osmocom.org)
|
||||
is broken (especially when used with cropping).
|
||||
|
||||
- **rx_power** (https://github.com/rxseger/rx_tools) *[unsupported]*
|
||||
|
||||
``rx_power`` (part of ``rx_tools``) is also based on SoapySDR (like default ``soapy_power`` backend)
|
||||
and therefore supports nearly all SDR platforms. But it is much slower than soapy_power, doesn't support
|
||||
near real-time continuous measurement (minimum interval is 1 second, same as ``rtl_power``)
|
||||
and is buggy. Backend is currently unsupported, if you want to fix it, patches are welcome.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Start QSpectrumAnalyzer by running ``qspectrumanalyzer``.
|
||||
|
||||
You can choose which backend you want to use in *File* -> *Settings*
|
||||
(or *Application menu* -> *Preferences* on Mac OS X), default is
|
||||
``soapy_power``. Device, sample rate, bandwidth, LNB LO, path to backend executable
|
||||
and additional backend parameters can be also manually specified there. You can
|
||||
also set waterfall plot history size. Default is 100 lines, be aware that
|
||||
really large sweeps (with a lot of bins) would require a lot of system
|
||||
memory, so don't make this number too big.
|
||||
|
||||
Controls should be intuitive, but if you want consistent results, you should
|
||||
turn off automatic gain control (set gain to some fixed number) and also set
|
||||
crop to 20% or more. For finding out ppm correction factor for your rtl-sdr
|
||||
stick, use `kalibrate-rtl <https://github.com/steve-m/kalibrate-rtl>`_.
|
||||
|
||||
You can move and zoom plot with mouse, change plot settings or export plots
|
||||
from right-click menu. Waterfall plot black/white levels and color lookup
|
||||
table can be changed in mini-histogram widget (on *Levels* tab).
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Arch Linux:
|
||||
***********
|
||||
|
||||
Stable version:
|
||||
::
|
||||
|
||||
git clone https://aur.archlinux.org/qspectrumanalyzer.git
|
||||
cd qspectrumanalyzer
|
||||
makepkg -sri
|
||||
|
||||
Git master branch:
|
||||
::
|
||||
|
||||
git clone https://aur.archlinux.org/qspectrumanalyzer-git.git
|
||||
cd qspectrumanalyzer-git
|
||||
makepkg -sri
|
||||
|
||||
Or simply use `pacaur <https://aur.archlinux.org/packages/pacaur>`_ (or any other AUR helper)
|
||||
which will also automatically install all QSpectrumAnalyzer dependencies:
|
||||
::
|
||||
|
||||
pacaur -S qspectrumanalyzer
|
||||
pacaur -S qspectrumanalyzer-git
|
||||
|
||||
Ubuntu:
|
||||
*******
|
||||
::
|
||||
|
||||
# Add SoapySDR PPA to your system
|
||||
sudo add-apt-repository -y ppa:myriadrf/drivers
|
||||
|
||||
# Update list of packages
|
||||
sudo apt-get update
|
||||
|
||||
# Install basic dependencies
|
||||
sudo apt-get install python3-pip python3-pyqt5 python3-numpy python3-scipy soapysdr python3-soapysdr
|
||||
|
||||
# Install SoapySDR drivers for your hardware (e.g. RTL-SDR, Airspy, HackRF, LimeSDR, etc.)
|
||||
sudo apt-get install soapysdr-module-rtlsdr soapysdr-module-airspy soapysdr-module-hackrf soapysdr-module-lms7
|
||||
|
||||
# Install QSpectrumAnalyzer locally for your current user
|
||||
pip3 install --user qspectrumanalyzer
|
||||
|
||||
``qspectrumanalyzer`` and ``soapy_power`` executables will be then placed in
|
||||
``~/.local/bin`` directory, you can add it to your PATH in ``~/.bashrc``.
|
||||
|
||||
If you want to install QSpectrumAnalyzer directly from Git master branch, you can use this procedure:
|
||||
::
|
||||
|
||||
git clone https://github.com/xmikos/qspectrumanalyzer.git
|
||||
cd qspectrumanalyzer
|
||||
pip3 install --user .
|
||||
|
||||
Windows:
|
||||
********
|
||||
|
||||
*Only 64-bit Windows are supported (there are no public 32-bit builds of SoapySDR
|
||||
libraries and drivers).*
|
||||
|
||||
1. install `SoapySDR <https://github.com/pothosware/SoapySDR/wiki>`_ libraries and drivers
|
||||
(bundled as part of Pothos SDR installer: `download <http://downloads.myriadrf.org/builds/PothosSDR/?C=M;O=D>`_).
|
||||
This bundle also includes other great SDR apps like `CubicSDR <http://cubicsdr.com>`_, `GQRX <http://gqrx.dk>`_,
|
||||
`GNU Radio Companion <https://gnuradio.org>`_, `Pothos GUI <https://github.com/pothosware/pothos/wiki>`_,
|
||||
`Lime Suite <https://github.com/myriadrf/LimeSuite>`_ and `Zadig <http://zadig.akeo.ie>`_.
|
||||
Utilities like ``hackrf_sweep`` and ``rtl_power`` are also included.
|
||||
2. download QSpectrumAnalyzer installer or portable zip archive from GitHub
|
||||
`releases <https://github.com/xmikos/qspectrumanalyzer/releases>`_ page
|
||||
3. after you connect your SDR device, you have to run `Zadig <http://zadig.akeo.ie>`_ to install USB drivers
|
||||
|
||||
You can also install QSpectrumAnalyzer manually from `PyPI <https://pypi.python.org>`_:
|
||||
|
||||
1. install Python 3.6.x (64-bit) from `python.org <https://www.python.org>`_ and add Python to PATH
|
||||
2. install `SoapySDR <https://github.com/pothosware/SoapySDR/wiki>`_ libraries and drivers
|
||||
(bundled as part of Pothos SDR installer: `download <http://downloads.myriadrf.org/builds/PothosSDR/?C=M;O=D>`_)
|
||||
3. Open ``cmd.exe`` and run::
|
||||
|
||||
pip install PyQt5
|
||||
pip install QSpectrumAnalyzer
|
||||
|
||||
You should then be able to run it with ``qspectrumanalyzer`` (or ``python -m qspectrumanalyzer``
|
||||
if it doesn't work for you).
|
||||
|
||||
Todo:
|
||||
-----
|
||||
|
||||
- save FFT history (allow big waterfall plot saved to file)
|
||||
- automatic peak detection / highlighting
|
||||
- display average noise level
|
||||
- frequency markers / bookmarks with notes (even importing and exporting .csv file with
|
||||
predefined channels, etc.)
|
@ -1,10 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=QSpectrumAnalyzer
|
||||
GenericName=Spectrum analyzer
|
||||
Comment=Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power, hackrf_sweep, rtl_power, rx_power and other backends)
|
||||
Exec=qspectrumanalyzer
|
||||
Icon=qspectrumanalyzer
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Qt;Science;DataVisualization;Electricity;HamRadio;
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.9 KiB |
@ -1,133 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg4290"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
viewBox="0 0 32 32"
|
||||
inkscape:export-filename="qspectrumanalyzer.png"
|
||||
inkscape:export-xdpi="135"
|
||||
inkscape:export-ydpi="135"
|
||||
sodipodi:docname="qspectrumanalyzer.svg">
|
||||
<defs
|
||||
id="defs4292" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6568543"
|
||||
inkscape:cx="0.51171273"
|
||||
inkscape:cy="17.429759"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-bbox="false"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="709"
|
||||
inkscape:window-x="-4"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4298" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4295">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<rect
|
||||
style="fill:#000000;stroke:#666666;stroke-opacity:1"
|
||||
id="rect4300"
|
||||
width="30"
|
||||
height="30"
|
||||
x="1"
|
||||
y="1"
|
||||
rx="3"
|
||||
ry="3" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 16,1 0,30"
|
||||
id="path4302"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 31,16 1,16"
|
||||
id="path4302-5"
|
||||
inkscape:connector-curvature="0" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path4302"
|
||||
id="use4369"
|
||||
transform="translate(-7.5,0)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path4302"
|
||||
id="use4371"
|
||||
transform="translate(7.5,0)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4373"
|
||||
d="M 31,8.5 1,8.5"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path4302-5"
|
||||
id="use4375"
|
||||
transform="translate(0,7.5)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#00ff00;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 1.0625,25.25 c 0,0 2.5545099,5.432144 4.3470654,0.903939 0.6415968,-1.620748 1.1843693,-10.505075 2.3294368,-10.451145 1.124605,0.05297 1.2928473,9.79388 3.0231028,11.399454 0.836834,0.735662 2.019611,-2.789575 2.96484,-2.691821 1.178304,0.121857 2.125956,3.657855 3.181223,2.383073 2.07617,-2.508052 1.527752,-21.6078848 2.588897,-21.5746877 1.200441,0.037555 1.333727,19.4989257 3.765313,21.7522057 1.533625,1.421166 1.791478,-2.106359 3.115303,-2.461012 0.660576,-0.176968 2.034012,3.420657 2.702589,3.52241 0.772278,0.117536 1.624099,-1.153766 2.01348,-0.813666"
|
||||
id="path4394"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csscssssssc" />
|
||||
<rect
|
||||
style="fill:none;stroke:#666666;stroke-opacity:1"
|
||||
id="rect4300-8"
|
||||
width="30"
|
||||
height="30"
|
||||
x="1"
|
||||
y="1"
|
||||
rx="3"
|
||||
ry="3" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.3 KiB |
@ -1,544 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys, os, signal, time, argparse
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
from qspectrumanalyzer import backends
|
||||
from qspectrumanalyzer.version import __version__
|
||||
from qspectrumanalyzer.data import DataStorage
|
||||
from qspectrumanalyzer.plot import SpectrumPlotWidget, WaterfallPlotWidget
|
||||
from qspectrumanalyzer.utils import str_to_color, human_time
|
||||
|
||||
from qspectrumanalyzer.settings import QSpectrumAnalyzerSettings
|
||||
from qspectrumanalyzer.smoothing import QSpectrumAnalyzerSmoothing
|
||||
from qspectrumanalyzer.persistence import QSpectrumAnalyzerPersistence
|
||||
from qspectrumanalyzer.colors import QSpectrumAnalyzerColors
|
||||
from qspectrumanalyzer.baseline import QSpectrumAnalyzerBaseline
|
||||
|
||||
from qspectrumanalyzer.ui_qspectrumanalyzer import Ui_QSpectrumAnalyzerMainWindow
|
||||
|
||||
debug = False
|
||||
|
||||
# Allow CTRL+C and/or SIGTERM to kill us (PyQt blocks it otherwise)
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
||||
|
||||
|
||||
class QSpectrumAnalyzerMainWindow(QtWidgets.QMainWindow, Ui_QSpectrumAnalyzerMainWindow):
|
||||
"""QSpectrumAnalyzer main window"""
|
||||
def __init__(self, parent=None):
|
||||
# Initialize UI
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
# Set window icon
|
||||
icon_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "qspectrumanalyzer.svg")
|
||||
self.setWindowIcon(QtGui.QIcon(icon_path))
|
||||
|
||||
# Create progress bar
|
||||
self.progressbar = QtWidgets.QProgressBar()
|
||||
self.progressbar.setMaximumWidth(250)
|
||||
self.progressbar.setVisible(False)
|
||||
self.statusbar.addPermanentWidget(self.progressbar)
|
||||
|
||||
# Create plot widgets and update UI
|
||||
self.spectrumPlotWidget = SpectrumPlotWidget(self.mainPlotLayout)
|
||||
self.waterfallPlotWidget = WaterfallPlotWidget(self.waterfallPlotLayout, self.histogramPlotLayout)
|
||||
|
||||
# Link main spectrum plot to waterfall plot
|
||||
self.spectrumPlotWidget.plot.setXLink(self.waterfallPlotWidget.plot)
|
||||
|
||||
# Setup power thread and connect signals
|
||||
self.update_status_timer = QtCore.QTimer()
|
||||
self.update_status_timer.timeout.connect(self.update_status)
|
||||
self.prev_sweep_time = None
|
||||
self.prev_data_timestamp = None
|
||||
self.start_timestamp = None
|
||||
self.data_storage = None
|
||||
self.power_thread = None
|
||||
self.backend = None
|
||||
self.setup_power_thread()
|
||||
|
||||
self.update_buttons()
|
||||
self.load_settings()
|
||||
|
||||
def setup_power_thread(self):
|
||||
"""Create power_thread and connect signals to slots"""
|
||||
if self.power_thread:
|
||||
self.stop()
|
||||
|
||||
settings = QtCore.QSettings()
|
||||
self.data_storage = DataStorage(max_history_size=settings.value("waterfall_history_size", 100, int))
|
||||
self.data_storage.data_updated.connect(self.update_data)
|
||||
self.data_storage.data_updated.connect(self.spectrumPlotWidget.update_plot)
|
||||
self.data_storage.data_updated.connect(self.spectrumPlotWidget.update_persistence)
|
||||
self.data_storage.data_recalculated.connect(self.spectrumPlotWidget.recalculate_plot)
|
||||
self.data_storage.data_recalculated.connect(self.spectrumPlotWidget.recalculate_persistence)
|
||||
self.data_storage.history_updated.connect(self.waterfallPlotWidget.update_plot)
|
||||
self.data_storage.history_recalculated.connect(self.waterfallPlotWidget.recalculate_plot)
|
||||
self.data_storage.average_updated.connect(self.spectrumPlotWidget.update_average)
|
||||
self.data_storage.baseline_updated.connect(self.spectrumPlotWidget.update_baseline)
|
||||
self.data_storage.peak_hold_max_updated.connect(self.spectrumPlotWidget.update_peak_hold_max)
|
||||
self.data_storage.peak_hold_min_updated.connect(self.spectrumPlotWidget.update_peak_hold_min)
|
||||
|
||||
# Setup default values and limits in case that backend is changed
|
||||
backend = settings.value("backend", "soapy_power")
|
||||
try:
|
||||
backend_module = getattr(backends, backend)
|
||||
except AttributeError:
|
||||
backend_module = backends.soapy_power
|
||||
|
||||
if self.backend is None or backend != self.backend:
|
||||
self.backend = backend
|
||||
self.gainSpinBox.setMinimum(backend_module.Info.gain_min)
|
||||
self.gainSpinBox.setMaximum(backend_module.Info.gain_max)
|
||||
self.gainSpinBox.setValue(backend_module.Info.gain)
|
||||
self.startFreqSpinBox.setMinimum(backend_module.Info.start_freq_min)
|
||||
self.startFreqSpinBox.setMaximum(backend_module.Info.start_freq_max)
|
||||
self.startFreqSpinBox.setValue(backend_module.Info.start_freq)
|
||||
self.stopFreqSpinBox.setMinimum(backend_module.Info.stop_freq_min)
|
||||
self.stopFreqSpinBox.setMaximum(backend_module.Info.stop_freq_max)
|
||||
self.stopFreqSpinBox.setValue(backend_module.Info.stop_freq)
|
||||
self.binSizeSpinBox.setMinimum(backend_module.Info.bin_size_min)
|
||||
self.binSizeSpinBox.setMaximum(backend_module.Info.bin_size_max)
|
||||
self.binSizeSpinBox.setValue(backend_module.Info.bin_size)
|
||||
self.intervalSpinBox.setMinimum(backend_module.Info.interval_min)
|
||||
self.intervalSpinBox.setMaximum(backend_module.Info.interval_max)
|
||||
self.intervalSpinBox.setValue(backend_module.Info.interval)
|
||||
self.ppmSpinBox.setMinimum(backend_module.Info.ppm_min)
|
||||
self.ppmSpinBox.setMaximum(backend_module.Info.ppm_max)
|
||||
self.ppmSpinBox.setValue(backend_module.Info.ppm)
|
||||
self.cropSpinBox.setMinimum(backend_module.Info.crop_min)
|
||||
self.cropSpinBox.setMaximum(backend_module.Info.crop_max)
|
||||
self.cropSpinBox.setValue(backend_module.Info.crop)
|
||||
|
||||
# Setup default values and limits in case that LNB LO is changed
|
||||
lnb_lo = settings.value("lnb_lo", 0, float) / 1e6
|
||||
|
||||
start_freq_min = backend_module.Info.start_freq_min + lnb_lo
|
||||
start_freq_max = backend_module.Info.start_freq_max + lnb_lo
|
||||
start_freq = self.startFreqSpinBox.value()
|
||||
stop_freq_min = backend_module.Info.stop_freq_min + lnb_lo
|
||||
stop_freq_max = backend_module.Info.stop_freq_max + lnb_lo
|
||||
stop_freq = self.stopFreqSpinBox.value()
|
||||
|
||||
self.startFreqSpinBox.setMinimum(start_freq_min if start_freq_min > 0 else 0)
|
||||
self.startFreqSpinBox.setMaximum(start_freq_max)
|
||||
if start_freq < start_freq_min or start_freq > start_freq_max:
|
||||
self.startFreqSpinBox.setValue(start_freq_min)
|
||||
|
||||
self.stopFreqSpinBox.setMinimum(stop_freq_min if stop_freq_min > 0 else 0)
|
||||
self.stopFreqSpinBox.setMaximum(stop_freq_max)
|
||||
if stop_freq < stop_freq_min or stop_freq > stop_freq_max:
|
||||
self.stopFreqSpinBox.setValue(stop_freq_max)
|
||||
|
||||
self.power_thread = backend_module.PowerThread(self.data_storage)
|
||||
self.power_thread.powerThreadStarted.connect(self.on_power_thread_started)
|
||||
self.power_thread.powerThreadStopped.connect(self.on_power_thread_stopped)
|
||||
|
||||
def set_dock_size(self, dock, width, height):
|
||||
"""Ugly hack for resizing QDockWidget (because it doesn't respect minimumSize / sizePolicy set in Designer)
|
||||
Link: https://stackoverflow.com/questions/2722939/c-resize-a-docked-qt-qdockwidget-programmatically"""
|
||||
old_min_size = dock.minimumSize()
|
||||
old_max_size = dock.maximumSize()
|
||||
|
||||
if width >= 0:
|
||||
if dock.width() < width:
|
||||
dock.setMinimumWidth(width)
|
||||
else:
|
||||
dock.setMaximumWidth(width)
|
||||
|
||||
if height >= 0:
|
||||
if dock.height() < height:
|
||||
dock.setMinimumHeight(height)
|
||||
else:
|
||||
dock.setMaximumHeight(height)
|
||||
|
||||
QtCore.QTimer.singleShot(0, lambda: self.set_dock_size_callback(dock, old_min_size, old_max_size))
|
||||
|
||||
def set_dock_size_callback(self, dock, old_min_size, old_max_size):
|
||||
"""Return to original QDockWidget minimumSize and maximumSize after running set_dock_size()"""
|
||||
dock.setMinimumSize(old_min_size)
|
||||
dock.setMaximumSize(old_max_size)
|
||||
|
||||
def load_settings(self):
|
||||
"""Restore spectrum analyzer settings and window geometry"""
|
||||
settings = QtCore.QSettings()
|
||||
self.startFreqSpinBox.setValue(settings.value("start_freq", 87.0, float))
|
||||
self.stopFreqSpinBox.setValue(settings.value("stop_freq", 108.0, float))
|
||||
self.binSizeSpinBox.setValue(settings.value("bin_size", 10.0, float))
|
||||
self.intervalSpinBox.setValue(settings.value("interval", 10.0, float))
|
||||
self.gainSpinBox.setValue(settings.value("gain", 0, float))
|
||||
self.ppmSpinBox.setValue(settings.value("ppm", 0, int))
|
||||
self.cropSpinBox.setValue(settings.value("crop", 0, int))
|
||||
self.mainCurveCheckBox.setChecked(settings.value("main_curve", 1, int))
|
||||
self.peakHoldMaxCheckBox.setChecked(settings.value("peak_hold_max", 0, int))
|
||||
self.peakHoldMinCheckBox.setChecked(settings.value("peak_hold_min", 0, int))
|
||||
self.averageCheckBox.setChecked(settings.value("average", 0, int))
|
||||
self.smoothCheckBox.setChecked(settings.value("smooth", 0, int))
|
||||
self.persistenceCheckBox.setChecked(settings.value("persistence", 0, int))
|
||||
self.baselineCheckBox.setChecked(settings.value("baseline", 0, int))
|
||||
self.subtractBaselineCheckBox.setChecked(settings.value("subtract_baseline", 0, int))
|
||||
|
||||
# Restore window state
|
||||
if settings.value("window_state"):
|
||||
self.restoreState(settings.value("window_state"))
|
||||
if settings.value("plotsplitter_state"):
|
||||
self.plotSplitter.restoreState(settings.value("plotsplitter_state"))
|
||||
|
||||
# Migration from older version of config file
|
||||
if settings.value("config_version", 1, int) < 2:
|
||||
# Make tabs from docks when started for first time
|
||||
self.tabifyDockWidget(self.settingsDockWidget, self.levelsDockWidget)
|
||||
self.settingsDockWidget.raise_()
|
||||
self.set_dock_size(self.controlsDockWidget, 0, 0)
|
||||
self.set_dock_size(self.frequencyDockWidget, 0, 0)
|
||||
# Update config version
|
||||
settings.setValue("config_version", 2)
|
||||
|
||||
# Window geometry has to be restored only after show(), because initial
|
||||
# maximization doesn't work otherwise (at least not in some window managers on X11)
|
||||
self.show()
|
||||
if settings.value("window_geometry"):
|
||||
self.restoreGeometry(settings.value("window_geometry"))
|
||||
|
||||
def save_settings(self):
|
||||
"""Save spectrum analyzer settings and window geometry"""
|
||||
settings = QtCore.QSettings()
|
||||
settings.setValue("start_freq", self.startFreqSpinBox.value())
|
||||
settings.setValue("stop_freq", self.stopFreqSpinBox.value())
|
||||
settings.setValue("bin_size", self.binSizeSpinBox.value())
|
||||
settings.setValue("interval", self.intervalSpinBox.value())
|
||||
settings.setValue("gain", self.gainSpinBox.value())
|
||||
settings.setValue("ppm", self.ppmSpinBox.value())
|
||||
settings.setValue("crop", self.cropSpinBox.value())
|
||||
settings.setValue("main_curve", int(self.mainCurveCheckBox.isChecked()))
|
||||
settings.setValue("peak_hold_max", int(self.peakHoldMaxCheckBox.isChecked()))
|
||||
settings.setValue("peak_hold_min", int(self.peakHoldMinCheckBox.isChecked()))
|
||||
settings.setValue("average", int(self.averageCheckBox.isChecked()))
|
||||
settings.setValue("smooth", int(self.smoothCheckBox.isChecked()))
|
||||
settings.setValue("persistence", int(self.persistenceCheckBox.isChecked()))
|
||||
settings.setValue("baseline", int(self.baselineCheckBox.isChecked()))
|
||||
settings.setValue("subtract_baseline", int(self.subtractBaselineCheckBox.isChecked()))
|
||||
|
||||
# Save window state and geometry
|
||||
settings.setValue("window_geometry", self.saveGeometry())
|
||||
settings.setValue("window_state", self.saveState())
|
||||
settings.setValue("plotsplitter_state", self.plotSplitter.saveState())
|
||||
|
||||
def show_status(self, message, timeout=2000):
|
||||
"""Show message in status bar"""
|
||||
self.statusbar.showMessage(message, timeout)
|
||||
|
||||
def update_buttons(self):
|
||||
"""Update state of control buttons"""
|
||||
self.startButton.setEnabled(not self.power_thread.alive)
|
||||
self.singleShotButton.setEnabled(not self.power_thread.alive)
|
||||
self.stopButton.setEnabled(self.power_thread.alive)
|
||||
|
||||
def update_data(self, data_storage):
|
||||
"""Update GUI when new data is received"""
|
||||
timestamp = time.time()
|
||||
self.prev_sweep_time = timestamp - self.prev_data_timestamp
|
||||
self.prev_data_timestamp = timestamp
|
||||
self.update_status()
|
||||
|
||||
def update_status(self):
|
||||
"""Update status bar"""
|
||||
timestamp = time.time()
|
||||
status = []
|
||||
|
||||
if self.power_thread.params["hops"]:
|
||||
status.append(self.tr("Frequency hops: {}").format(self.power_thread.params["hops"]))
|
||||
|
||||
status.append(self.tr("Total time: {} | Sweep time: {:.2f} s ({:.2f} FPS)").format(
|
||||
human_time(timestamp - self.start_timestamp),
|
||||
self.prev_sweep_time,
|
||||
(1 / self.prev_sweep_time) if self.prev_sweep_time else 0
|
||||
))
|
||||
|
||||
self.show_status(" | ".join(status), timeout=0)
|
||||
self.update_progress(timestamp - self.prev_data_timestamp)
|
||||
|
||||
def update_progress(self, value):
|
||||
"""Update progress bar"""
|
||||
value *= 1000
|
||||
value_max = self.intervalSpinBox.value() * 1000
|
||||
|
||||
if value_max < 1000:
|
||||
return
|
||||
|
||||
if value > value_max + 1000:
|
||||
self.progressbar.setRange(0, 0)
|
||||
value = value_max
|
||||
elif value > value_max:
|
||||
value = value_max
|
||||
else:
|
||||
self.progressbar.setRange(0, value_max)
|
||||
|
||||
self.progressbar.setValue(value)
|
||||
|
||||
def on_power_thread_started(self):
|
||||
"""Update buttons state when power thread is started"""
|
||||
self.update_buttons()
|
||||
self.progressbar.setVisible(True)
|
||||
|
||||
def on_power_thread_stopped(self):
|
||||
"""Update buttons state and status bar when power thread is stopped"""
|
||||
self.update_buttons()
|
||||
self.update_status_timer.stop()
|
||||
self.update_status()
|
||||
self.progressbar.setVisible(False)
|
||||
|
||||
def start(self, single_shot=False):
|
||||
"""Start power thread"""
|
||||
settings = QtCore.QSettings()
|
||||
|
||||
self.prev_sweep_time = 0
|
||||
self.prev_data_timestamp = time.time()
|
||||
self.start_timestamp = self.prev_data_timestamp
|
||||
|
||||
if self.intervalSpinBox.value() >= 1:
|
||||
self.progressbar.setRange(0, self.intervalSpinBox.value() * 1000)
|
||||
else:
|
||||
self.progressbar.setRange(0, 0)
|
||||
self.update_progress(0)
|
||||
self.update_status_timer.start(100)
|
||||
|
||||
self.waterfallPlotWidget.history_size = settings.value("waterfall_history_size", 100, int)
|
||||
self.waterfallPlotWidget.clear_plot()
|
||||
|
||||
self.spectrumPlotWidget.main_curve = bool(self.mainCurveCheckBox.isChecked())
|
||||
self.spectrumPlotWidget.main_color = str_to_color(settings.value("main_color", "255, 255, 0, 255"))
|
||||
self.spectrumPlotWidget.peak_hold_max = bool(self.peakHoldMaxCheckBox.isChecked())
|
||||
self.spectrumPlotWidget.peak_hold_max_color = str_to_color(settings.value("peak_hold_max_color", "255, 0, 0, 255"))
|
||||
self.spectrumPlotWidget.peak_hold_min = bool(self.peakHoldMinCheckBox.isChecked())
|
||||
self.spectrumPlotWidget.peak_hold_min_color = str_to_color(settings.value("peak_hold_min_color", "0, 0, 255, 255"))
|
||||
self.spectrumPlotWidget.average = bool(self.averageCheckBox.isChecked())
|
||||
self.spectrumPlotWidget.average_color = str_to_color(settings.value("average_color", "0, 255, 255, 255"))
|
||||
self.spectrumPlotWidget.baseline = bool(self.baselineCheckBox.isChecked())
|
||||
self.spectrumPlotWidget.baseline_color = str_to_color(settings.value("baseline_color", "255, 0, 255, 255"))
|
||||
self.spectrumPlotWidget.persistence = bool(self.persistenceCheckBox.isChecked())
|
||||
self.spectrumPlotWidget.persistence_length = settings.value("persistence_length", 5, int)
|
||||
self.spectrumPlotWidget.persistence_decay = settings.value("persistence_decay", "exponential")
|
||||
self.spectrumPlotWidget.persistence_color = str_to_color(settings.value("persistence_color", "0, 255, 0, 255"))
|
||||
self.spectrumPlotWidget.clear_plot()
|
||||
self.spectrumPlotWidget.clear_peak_hold_max()
|
||||
self.spectrumPlotWidget.clear_peak_hold_min()
|
||||
self.spectrumPlotWidget.clear_average()
|
||||
self.spectrumPlotWidget.clear_baseline()
|
||||
self.spectrumPlotWidget.clear_persistence()
|
||||
|
||||
self.data_storage.reset()
|
||||
self.data_storage.set_smooth(
|
||||
bool(self.smoothCheckBox.isChecked()),
|
||||
settings.value("smooth_length", 11, int),
|
||||
settings.value("smooth_window", "hanning")
|
||||
)
|
||||
self.data_storage.set_subtract_baseline(
|
||||
bool(self.subtractBaselineCheckBox.isChecked()),
|
||||
settings.value("baseline_file", None)
|
||||
)
|
||||
|
||||
if not self.power_thread.alive:
|
||||
self.power_thread.setup(
|
||||
float(self.startFreqSpinBox.value()),
|
||||
float(self.stopFreqSpinBox.value()),
|
||||
float(self.binSizeSpinBox.value()),
|
||||
interval=float(self.intervalSpinBox.value()),
|
||||
gain=float(self.gainSpinBox.value()),
|
||||
ppm=int(self.ppmSpinBox.value()),
|
||||
crop=int(self.cropSpinBox.value()) / 100.0,
|
||||
single_shot=single_shot,
|
||||
device=settings.value("device", ""),
|
||||
sample_rate=settings.value("sample_rate", 2560000, float),
|
||||
bandwidth=settings.value("bandwidth", 0, float),
|
||||
lnb_lo=settings.value("lnb_lo", 0, float)
|
||||
)
|
||||
self.power_thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""Stop power thread"""
|
||||
if self.power_thread.alive:
|
||||
self.power_thread.stop()
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_startButton_clicked(self):
|
||||
self.start()
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_singleShotButton_clicked(self):
|
||||
self.start(single_shot=True)
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_stopButton_clicked(self):
|
||||
self.stop()
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def on_mainCurveCheckBox_toggled(self, checked):
|
||||
self.spectrumPlotWidget.main_curve = checked
|
||||
if self.spectrumPlotWidget.curve.xData is None:
|
||||
self.spectrumPlotWidget.update_plot(self.data_storage)
|
||||
self.spectrumPlotWidget.curve.setVisible(checked)
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def on_peakHoldMaxCheckBox_toggled(self, checked):
|
||||
self.spectrumPlotWidget.peak_hold_max = checked
|
||||
if self.spectrumPlotWidget.curve_peak_hold_max.xData is None:
|
||||
self.spectrumPlotWidget.update_peak_hold_max(self.data_storage)
|
||||
self.spectrumPlotWidget.curve_peak_hold_max.setVisible(checked)
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def on_peakHoldMinCheckBox_toggled(self, checked):
|
||||
self.spectrumPlotWidget.peak_hold_min = checked
|
||||
if self.spectrumPlotWidget.curve_peak_hold_min.xData is None:
|
||||
self.spectrumPlotWidget.update_peak_hold_min(self.data_storage)
|
||||
self.spectrumPlotWidget.curve_peak_hold_min.setVisible(checked)
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def on_averageCheckBox_toggled(self, checked):
|
||||
self.spectrumPlotWidget.average = checked
|
||||
if self.spectrumPlotWidget.curve_average.xData is None:
|
||||
self.spectrumPlotWidget.update_average(self.data_storage)
|
||||
self.spectrumPlotWidget.curve_average.setVisible(checked)
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def on_persistenceCheckBox_toggled(self, checked):
|
||||
self.spectrumPlotWidget.persistence = checked
|
||||
if self.spectrumPlotWidget.persistence_curves[0].xData is None:
|
||||
self.spectrumPlotWidget.recalculate_persistence(self.data_storage)
|
||||
for curve in self.spectrumPlotWidget.persistence_curves:
|
||||
curve.setVisible(checked)
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def on_smoothCheckBox_toggled(self, checked):
|
||||
settings = QtCore.QSettings()
|
||||
self.data_storage.set_smooth(
|
||||
checked,
|
||||
settings.value("smooth_length", 11, int),
|
||||
settings.value("smooth_window", "hanning")
|
||||
)
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def on_baselineCheckBox_toggled(self, checked):
|
||||
self.spectrumPlotWidget.baseline = checked
|
||||
if self.spectrumPlotWidget.curve_baseline.xData is None:
|
||||
self.spectrumPlotWidget.update_baseline(self.data_storage)
|
||||
self.spectrumPlotWidget.curve_baseline.setVisible(checked)
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def on_subtractBaselineCheckBox_toggled(self, checked):
|
||||
settings = QtCore.QSettings()
|
||||
self.data_storage.set_subtract_baseline(
|
||||
checked,
|
||||
settings.value("baseline_file", None)
|
||||
)
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_baselineButton_clicked(self):
|
||||
dialog = QSpectrumAnalyzerBaseline(self)
|
||||
if dialog.exec_():
|
||||
settings = QtCore.QSettings()
|
||||
self.data_storage.set_subtract_baseline(
|
||||
bool(self.subtractBaselineCheckBox.isChecked()),
|
||||
settings.value("baseline_file", None)
|
||||
)
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_smoothButton_clicked(self):
|
||||
dialog = QSpectrumAnalyzerSmoothing(self)
|
||||
if dialog.exec_():
|
||||
settings = QtCore.QSettings()
|
||||
self.data_storage.set_smooth(
|
||||
bool(self.smoothCheckBox.isChecked()),
|
||||
settings.value("smooth_length", 11, int),
|
||||
settings.value("smooth_window", "hanning")
|
||||
)
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_persistenceButton_clicked(self):
|
||||
prev_persistence_length = self.spectrumPlotWidget.persistence_length
|
||||
dialog = QSpectrumAnalyzerPersistence(self)
|
||||
if dialog.exec_():
|
||||
settings = QtCore.QSettings()
|
||||
persistence_length = settings.value("persistence_length", 5, int)
|
||||
self.spectrumPlotWidget.persistence_length = persistence_length
|
||||
self.spectrumPlotWidget.persistence_decay = settings.value("persistence_decay", "exponential")
|
||||
|
||||
# If only decay function has been changed, just reset colors
|
||||
if persistence_length == prev_persistence_length:
|
||||
self.spectrumPlotWidget.set_colors()
|
||||
else:
|
||||
self.spectrumPlotWidget.recalculate_persistence(self.data_storage)
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_colorsButton_clicked(self):
|
||||
dialog = QSpectrumAnalyzerColors(self)
|
||||
if dialog.exec_():
|
||||
settings = QtCore.QSettings()
|
||||
self.spectrumPlotWidget.main_color = str_to_color(settings.value("main_color", "255, 255, 0, 255"))
|
||||
self.spectrumPlotWidget.peak_hold_max_color = str_to_color(settings.value("peak_hold_max_color", "255, 0, 0, 255"))
|
||||
self.spectrumPlotWidget.peak_hold_min_color = str_to_color(settings.value("peak_hold_min_color", "0, 0, 255, 255"))
|
||||
self.spectrumPlotWidget.average_color = str_to_color(settings.value("average_color", "0, 255, 255, 255"))
|
||||
self.spectrumPlotWidget.persistence_color = str_to_color(settings.value("persistence_color", "0, 255, 0, 255"))
|
||||
self.spectrumPlotWidget.baseline_color = str_to_color(settings.value("baseline_color", "255, 0, 255, 255"))
|
||||
self.spectrumPlotWidget.set_colors()
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_action_Settings_triggered(self):
|
||||
dialog = QSpectrumAnalyzerSettings(self)
|
||||
if dialog.exec_():
|
||||
self.setup_power_thread()
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_action_About_triggered(self):
|
||||
QtWidgets.QMessageBox.information(self, self.tr("About - QSpectrumAnalyzer"),
|
||||
self.tr("QSpectrumAnalyzer {}").format(__version__))
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_action_Quit_triggered(self):
|
||||
self.close()
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Save settings when main window is closed"""
|
||||
self.stop()
|
||||
self.save_settings()
|
||||
|
||||
|
||||
def main():
|
||||
global debug
|
||||
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="qspectrumanalyzer",
|
||||
description="Spectrum analyzer for multiple SDR platforms",
|
||||
)
|
||||
parser.add_argument("--debug", action="store_true",
|
||||
help="detailed debugging messages")
|
||||
parser.add_argument("--version", action="version",
|
||||
version="%(prog)s {}".format(__version__))
|
||||
args, unparsed_args = parser.parse_known_args()
|
||||
debug = args.debug
|
||||
|
||||
try:
|
||||
# Hide console window on Windows
|
||||
if sys.platform == 'win32' and not debug:
|
||||
from qspectrumanalyzer import windows
|
||||
windows.set_attached_console_visible(False)
|
||||
|
||||
# Start PyQt application
|
||||
app = QtWidgets.QApplication(sys.argv[:1] + unparsed_args)
|
||||
app.setOrganizationName("QSpectrumAnalyzer")
|
||||
app.setOrganizationDomain("qspectrumanalyzer.eutopia.cz")
|
||||
app.setApplicationName("QSpectrumAnalyzer")
|
||||
window = QSpectrumAnalyzerMainWindow()
|
||||
sys.exit(app.exec_())
|
||||
finally:
|
||||
# Unhide console window on Windows (we don't want to leave zombies behind)
|
||||
if sys.platform == 'win32' and not debug:
|
||||
windows.set_attached_console_visible(True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,117 +0,0 @@
|
||||
import os, threading, shlex
|
||||
|
||||
from Qt import QtCore
|
||||
|
||||
from qspectrumanalyzer import subprocess
|
||||
|
||||
|
||||
class BaseInfo:
|
||||
"""Default device metadata"""
|
||||
sample_rate_min = 0
|
||||
sample_rate_max = 3200000
|
||||
sample_rate = 2560000
|
||||
bandwidth_min = 0
|
||||
bandwidth_max = 0
|
||||
bandwidth = 0
|
||||
gain_min = -1
|
||||
gain_max = 49.6
|
||||
gain = 37
|
||||
start_freq_min = 0
|
||||
start_freq_max = 2200
|
||||
start_freq = 87
|
||||
stop_freq_min = 0
|
||||
stop_freq_max = 2200
|
||||
stop_freq = 108
|
||||
bin_size_min = 0
|
||||
bin_size_max = 2800
|
||||
bin_size = 10
|
||||
interval_min = 0
|
||||
interval_max = 3600
|
||||
interval = 1
|
||||
ppm_min = -999
|
||||
ppm_max = 999
|
||||
ppm = 0
|
||||
crop_min = 0
|
||||
crop_max = 99
|
||||
crop = 0
|
||||
additional_params = ''
|
||||
help_device = None
|
||||
|
||||
@classmethod
|
||||
def help_params(cls, executable):
|
||||
cmdline = shlex.split(executable)
|
||||
try:
|
||||
text = subprocess.check_output(cmdline + ['-h'], universal_newlines=True,
|
||||
stderr=subprocess.STDOUT, env=dict(os.environ, COLUMNS='125'),
|
||||
console=False)
|
||||
except subprocess.CalledProcessError as e:
|
||||
text = e.output
|
||||
except OSError:
|
||||
text = '{} executable not found!'.format(executable)
|
||||
return text
|
||||
|
||||
|
||||
class BasePowerThread(QtCore.QThread):
|
||||
"""Thread which runs Power Spectral Density acquisition and calculation process"""
|
||||
powerThreadStarted = QtCore.Signal()
|
||||
powerThreadStopped = QtCore.Signal()
|
||||
|
||||
def __init__(self, data_storage, parent=None):
|
||||
super().__init__(parent)
|
||||
self.data_storage = data_storage
|
||||
self.alive = False
|
||||
self.process = None
|
||||
self._shutdown_lock = threading.Lock()
|
||||
|
||||
def stop(self):
|
||||
"""Stop power process thread"""
|
||||
self.process_stop()
|
||||
self.alive = False
|
||||
self.wait()
|
||||
|
||||
def setup(self, start_freq, stop_freq, bin_size, interval=10.0, gain=-1, ppm=0, crop=0,
|
||||
single_shot=False, device=0, sample_rate=2560000, bandwidth=0, lnb_lo=0):
|
||||
"""Setup power process params"""
|
||||
raise NotImplementedError
|
||||
|
||||
def process_start(self):
|
||||
"""Start power process"""
|
||||
raise NotImplementedError
|
||||
|
||||
def process_stop(self):
|
||||
"""Terminate power process"""
|
||||
with self._shutdown_lock:
|
||||
if self.process:
|
||||
if self.process.poll() is None:
|
||||
try:
|
||||
self.process.terminate()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
self.process.wait()
|
||||
self.process = None
|
||||
|
||||
def parse_output(self, line):
|
||||
"""Parse one line of output from power process"""
|
||||
raise NotImplementedError
|
||||
|
||||
def run(self):
|
||||
"""Power process thread main loop"""
|
||||
self.process_start()
|
||||
self.alive = True
|
||||
self.powerThreadStarted.emit()
|
||||
|
||||
for line in self.process.stdout:
|
||||
if not self.alive:
|
||||
break
|
||||
self.parse_output(line)
|
||||
|
||||
self.process_stop()
|
||||
self.alive = False
|
||||
self.powerThreadStopped.emit()
|
||||
|
||||
|
||||
# Build list of all backends
|
||||
__all__ = ['soapy_power', 'hackrf_sweep', 'rtl_power', 'rtl_power_fftw', 'rx_power']
|
||||
|
||||
# Import all backends
|
||||
from qspectrumanalyzer.backends import soapy_power, hackrf_sweep, rtl_power, rtl_power_fftw, rx_power
|
@ -1,171 +0,0 @@
|
||||
import struct, shlex, sys, time
|
||||
|
||||
import numpy as np
|
||||
from Qt import QtCore
|
||||
|
||||
from qspectrumanalyzer import subprocess
|
||||
from qspectrumanalyzer.backends import BaseInfo, BasePowerThread
|
||||
|
||||
|
||||
class Info(BaseInfo):
|
||||
"""hackrf_sweep device metadata"""
|
||||
sample_rate_min = 20000000
|
||||
sample_rate_max = 20000000
|
||||
sample_rate = 20000000
|
||||
gain_min = -1
|
||||
gain_max = 102
|
||||
gain = 40
|
||||
start_freq_min = 0
|
||||
start_freq_max = 7230
|
||||
start_freq = 0
|
||||
stop_freq_min = 0
|
||||
stop_freq_max = 7250
|
||||
stop_freq = 6000
|
||||
bin_size_min = 3
|
||||
bin_size_max = 5000
|
||||
bin_size = 1000
|
||||
interval = 0
|
||||
ppm_min = 0
|
||||
ppm_max = 0
|
||||
ppm = 0
|
||||
crop_min = 0
|
||||
crop_max = 0
|
||||
crop = 0
|
||||
|
||||
|
||||
class PowerThread(BasePowerThread):
|
||||
"""Thread which runs hackrf_sweep process"""
|
||||
def setup(self, start_freq=0, stop_freq=6000, bin_size=1000,
|
||||
interval=0.0, gain=40, ppm=0, crop=0, single_shot=False,
|
||||
device=0, sample_rate=20000000, bandwidth=0, lnb_lo=0):
|
||||
"""Setup hackrf_sweep params"""
|
||||
# Small bin sizes (<40 kHz) are only suitable with an arbitrarily
|
||||
# reduced sweep interval. Bin sizes smaller than 3 kHz showed to be
|
||||
# infeasible also in these cases.
|
||||
if bin_size < 3:
|
||||
bin_size = 3
|
||||
if bin_size > 5000:
|
||||
bin_size = 5000
|
||||
|
||||
# We only support whole numbers of steps with bandwidth equal to the
|
||||
# sample rate.
|
||||
step_bandwidth = sample_rate / 1000000
|
||||
total_bandwidth = stop_freq - start_freq
|
||||
step_count = 1 + (total_bandwidth - 1) // step_bandwidth
|
||||
total_bandwidth = step_count * step_bandwidth
|
||||
stop_freq = start_freq + total_bandwidth
|
||||
|
||||
# distribute gain between two analog gain stages
|
||||
if gain > 102:
|
||||
gain = 102
|
||||
lna_gain = 8 * (gain // 18) if gain >= 0 else 0
|
||||
vga_gain = 2 * ((gain - lna_gain) // 2) if gain >= 0 else 0
|
||||
|
||||
self.params = {
|
||||
"start_freq": start_freq, # MHz
|
||||
"stop_freq": stop_freq, # MHz
|
||||
"hops": 0,
|
||||
"device": 0,
|
||||
"sample_rate": 20e6, # sps
|
||||
"bin_size": bin_size, # kHz
|
||||
"interval": interval, # seconds
|
||||
"gain": gain,
|
||||
"lna_gain": lna_gain,
|
||||
"vga_gain": vga_gain,
|
||||
"ppm": 0,
|
||||
"crop": 0,
|
||||
"single_shot": single_shot
|
||||
}
|
||||
self.lnb_lo = lnb_lo
|
||||
self.databuffer = {"timestamp": [], "x": [], "y": []}
|
||||
self.lastsweep = 0
|
||||
self.interval = interval
|
||||
|
||||
def process_start(self):
|
||||
"""Start hackrf_sweep process"""
|
||||
if not self.process and self.params:
|
||||
settings = QtCore.QSettings()
|
||||
cmdline = shlex.split(settings.value("executable", "hackrf_sweep"))
|
||||
cmdline.extend([
|
||||
"-f", "{}:{}".format(int(self.params["start_freq"] - self.lnb_lo / 1e6),
|
||||
int(self.params["stop_freq"] - self.lnb_lo / 1e6)),
|
||||
"-B",
|
||||
"-w", "{}".format(int(self.params["bin_size"] * 1000)),
|
||||
])
|
||||
|
||||
if self.params["gain"] >= 0:
|
||||
cmdline.extend([
|
||||
"-l", "{}".format(int(self.params["lna_gain"])),
|
||||
"-g", "{}".format(int(self.params["vga_gain"])),
|
||||
])
|
||||
|
||||
if self.params["single_shot"]:
|
||||
cmdline.append("-1")
|
||||
|
||||
additional_params = settings.value("params", Info.additional_params)
|
||||
if additional_params:
|
||||
cmdline.extend(shlex.split(additional_params))
|
||||
|
||||
print('Starting backend:')
|
||||
print(' '.join(cmdline))
|
||||
print()
|
||||
self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
|
||||
universal_newlines=False, console=False)
|
||||
|
||||
def parse_output(self, buf):
|
||||
"""Parse one buf of output from hackrf_sweep"""
|
||||
(low_edge, high_edge) = struct.unpack('QQ', buf[:16])
|
||||
data = np.fromstring(buf[16:], dtype='<f4')
|
||||
step = (high_edge - low_edge) / len(data)
|
||||
|
||||
if (low_edge // 1000000) <= (self.params["start_freq"] - self.lnb_lo / 1e6):
|
||||
# Reset databuffer at the start of each sweep even if we somehow
|
||||
# did not complete the previous sweep.
|
||||
self.databuffer = {"timestamp": [], "x": [], "y": []}
|
||||
x_axis = list(np.arange(low_edge + self.lnb_lo + step / 2, high_edge + self.lnb_lo, step))
|
||||
self.databuffer["x"].extend(x_axis)
|
||||
for i in range(len(data)):
|
||||
self.databuffer["y"].append(data[i])
|
||||
if (high_edge / 1e6) >= (self.params["stop_freq"] - self.lnb_lo / 1e6):
|
||||
# We've reached the end of a pass. If it went too fast for our sweep interval, ignore it
|
||||
t_finish = time.time()
|
||||
if (t_finish < self.lastsweep + self.interval):
|
||||
return
|
||||
self.lastsweep = t_finish
|
||||
|
||||
# otherwise sort and display the data.
|
||||
sorted_data = sorted(zip(self.databuffer["x"], self.databuffer["y"]))
|
||||
self.databuffer["x"], self.databuffer["y"] = [list(x) for x in zip(*sorted_data)]
|
||||
self.data_storage.update(self.databuffer)
|
||||
|
||||
def run(self):
|
||||
"""hackrf_sweep thread main loop"""
|
||||
self.process_start()
|
||||
self.alive = True
|
||||
self.powerThreadStarted.emit()
|
||||
|
||||
while self.alive:
|
||||
try:
|
||||
buf = self.process.stdout.read(4)
|
||||
except AttributeError as e:
|
||||
print(e, file=sys.stderr)
|
||||
continue
|
||||
|
||||
if buf:
|
||||
(record_length,) = struct.unpack('I', buf)
|
||||
try:
|
||||
buf = self.process.stdout.read(record_length)
|
||||
except AttributeError as e:
|
||||
print(e, file=sys.stderr)
|
||||
continue
|
||||
|
||||
if buf:
|
||||
self.parse_output(buf)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
self.process_stop()
|
||||
self.alive = False
|
||||
self.powerThreadStopped.emit()
|
@ -1,104 +0,0 @@
|
||||
import shlex
|
||||
|
||||
import numpy as np
|
||||
from Qt import QtCore
|
||||
|
||||
from qspectrumanalyzer import subprocess
|
||||
from qspectrumanalyzer.backends import BaseInfo, BasePowerThread
|
||||
|
||||
|
||||
class Info(BaseInfo):
|
||||
"""rtl_power device metadata"""
|
||||
pass
|
||||
|
||||
|
||||
class PowerThread(BasePowerThread):
|
||||
"""Thread which runs rtl_power process"""
|
||||
def setup(self, start_freq, stop_freq, bin_size, interval=10.0, gain=-1, ppm=0, crop=0,
|
||||
single_shot=False, device=0, sample_rate=2560000, bandwidth=0, lnb_lo=0):
|
||||
"""Setup rtl_power params"""
|
||||
if bin_size > 2800:
|
||||
bin_size = 2800
|
||||
self.params = {
|
||||
"start_freq": start_freq,
|
||||
"stop_freq": stop_freq,
|
||||
"bin_size": bin_size,
|
||||
"interval": interval,
|
||||
"device": device,
|
||||
"sample_rate": sample_rate,
|
||||
"hops": 0,
|
||||
"gain": gain,
|
||||
"ppm": ppm,
|
||||
"crop": crop,
|
||||
"single_shot": single_shot
|
||||
}
|
||||
self.lnb_lo = lnb_lo
|
||||
self.databuffer = {}
|
||||
self.last_timestamp = ""
|
||||
|
||||
def process_start(self):
|
||||
"""Start rtl_power process"""
|
||||
if not self.process and self.params:
|
||||
settings = QtCore.QSettings()
|
||||
cmdline = shlex.split(settings.value("executable", "rtl_power"))
|
||||
cmdline.extend([
|
||||
"-f", "{}M:{}M:{}k".format(self.params["start_freq"] - self.lnb_lo / 1e6,
|
||||
self.params["stop_freq"] - self.lnb_lo / 1e6,
|
||||
self.params["bin_size"]),
|
||||
"-i", "{}".format(self.params["interval"]),
|
||||
"-d", "{}".format(self.params["device"]),
|
||||
"-p", "{}".format(self.params["ppm"]),
|
||||
"-c", "{}".format(self.params["crop"])
|
||||
])
|
||||
|
||||
if self.params["sample_rate"] > 0:
|
||||
cmdline.extend(["-r", "{}M".format(self.params["sample_rate"] / 1e6)])
|
||||
if self.params["gain"] >= 0:
|
||||
cmdline.extend(["-g", "{}".format(self.params["gain"])])
|
||||
if self.params["single_shot"]:
|
||||
cmdline.append("-1")
|
||||
|
||||
additional_params = settings.value("params", Info.additional_params)
|
||||
if additional_params:
|
||||
cmdline.extend(shlex.split(additional_params))
|
||||
|
||||
print('Starting backend:')
|
||||
print(' '.join(cmdline))
|
||||
print()
|
||||
self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
|
||||
universal_newlines=True, console=False)
|
||||
|
||||
def parse_output(self, line):
|
||||
"""Parse one line of output from rtl_power"""
|
||||
line = [col.strip() for col in line.split(",")]
|
||||
timestamp = " ".join(line[:2])
|
||||
start_freq = int(line[2])
|
||||
stop_freq = int(line[3])
|
||||
step = float(line[4])
|
||||
samples = float(line[5])
|
||||
|
||||
x_axis = list(np.linspace(start_freq + self.lnb_lo, stop_freq + self.lnb_lo,
|
||||
round((stop_freq - start_freq) / step)))
|
||||
y_axis = [float(y) for y in line[6:]]
|
||||
if len(x_axis) != len(y_axis):
|
||||
print("ERROR: len(x_axis) != len(y_axis), use newer version of rtl_power!")
|
||||
if len(x_axis) > len(y_axis):
|
||||
print("Trimming x_axis...")
|
||||
x_axis = x_axis[:len(y_axis)]
|
||||
else:
|
||||
print("Trimming y_axis...")
|
||||
y_axis = y_axis[:len(x_axis)]
|
||||
|
||||
if timestamp != self.last_timestamp:
|
||||
self.last_timestamp = timestamp
|
||||
self.databuffer = {"timestamp": timestamp,
|
||||
"x": x_axis,
|
||||
"y": y_axis}
|
||||
else:
|
||||
self.databuffer["x"].extend(x_axis)
|
||||
self.databuffer["y"].extend(y_axis)
|
||||
|
||||
# This have to be stupid like this to be compatible with old broken version of rtl_power. Right way is:
|
||||
# if stop_freq == (self.params["stop_freq"] - self.lnb_lo / 1e6) * 1e6:
|
||||
if stop_freq > ((self.params["stop_freq"] - self.lnb_lo / 1e6) * 1e6) - step:
|
||||
self.data_storage.update(self.databuffer)
|
@ -1,145 +0,0 @@
|
||||
import math, shlex
|
||||
|
||||
from Qt import QtCore
|
||||
|
||||
from qspectrumanalyzer import subprocess
|
||||
from qspectrumanalyzer.backends import BaseInfo, BasePowerThread
|
||||
|
||||
|
||||
class Info(BaseInfo):
|
||||
"""rtl_power_fftw device metadata"""
|
||||
pass
|
||||
|
||||
|
||||
class PowerThread(BasePowerThread):
|
||||
"""Thread which runs rtl_power_fftw process"""
|
||||
def setup(self, start_freq, stop_freq, bin_size, interval=10.0, gain=-1, ppm=0, crop=0,
|
||||
single_shot=False, device=0, sample_rate=2560000, bandwidth=0, lnb_lo=0):
|
||||
"""Setup rtl_power_fftw params"""
|
||||
crop = crop * 100
|
||||
overlap = crop * 2
|
||||
freq_range = stop_freq * 1e6 - start_freq * 1e6
|
||||
min_overhang = sample_rate * overlap * 0.01
|
||||
hops = math.ceil((freq_range - min_overhang) / (sample_rate - min_overhang))
|
||||
overhang = (hops * sample_rate - freq_range) / (hops - 1) if hops > 1 else 0
|
||||
if bin_size > 2800:
|
||||
bin_size = 2800
|
||||
bins = math.ceil(sample_rate / (bin_size * 1e3))
|
||||
crop_freq = sample_rate * crop * 0.01
|
||||
|
||||
self.params = {
|
||||
"start_freq": start_freq,
|
||||
"stop_freq": stop_freq,
|
||||
"freq_range": freq_range,
|
||||
"device": device,
|
||||
"sample_rate": int(sample_rate),
|
||||
"bin_size": bin_size,
|
||||
"bins": bins,
|
||||
"interval": interval,
|
||||
"hops": hops,
|
||||
"time": interval / hops,
|
||||
"gain": int(gain * 10),
|
||||
"ppm": ppm,
|
||||
"crop": crop,
|
||||
"overlap": overlap,
|
||||
"min_overhang": min_overhang,
|
||||
"overhang": overhang,
|
||||
"single_shot": single_shot
|
||||
}
|
||||
self.lnb_lo = lnb_lo
|
||||
self.freqs = [self.get_hop_freq(hop) for hop in range(hops)]
|
||||
self.freqs_crop = [(f[0] + crop_freq, f[1] - crop_freq) for f in self.freqs]
|
||||
self.databuffer = {"timestamp": [], "x": [], "y": []}
|
||||
self.databuffer_hop = {"timestamp": [], "x": [], "y": []}
|
||||
self.hop = 0
|
||||
self.prev_line = ""
|
||||
|
||||
def get_hop_freq(self, hop):
|
||||
"""Get start and stop frequency for particular hop"""
|
||||
start_freq = self.params["start_freq"] * 1e6 + (self.params["sample_rate"] - self.params["overhang"]) * hop
|
||||
stop_freq = start_freq + self.params["sample_rate"] - (self.params["sample_rate"] / self.params["bins"])
|
||||
return (start_freq, stop_freq)
|
||||
|
||||
def process_start(self):
|
||||
"""Start rtl_power_fftw process"""
|
||||
if not self.process and self.params:
|
||||
settings = QtCore.QSettings()
|
||||
cmdline = shlex.split(settings.value("executable", "rtl_power_fftw"))
|
||||
cmdline.extend([
|
||||
"-f", "{}M:{}M".format(self.params["start_freq"] - self.lnb_lo / 1e6,
|
||||
self.params["stop_freq"] - self.lnb_lo / 1e6),
|
||||
"-b", "{}".format(self.params["bins"]),
|
||||
"-t", "{}".format(self.params["time"]),
|
||||
"-d", "{}".format(self.params["device"]),
|
||||
"-r", "{}".format(self.params["sample_rate"]),
|
||||
"-p", "{}".format(self.params["ppm"]),
|
||||
"-q",
|
||||
])
|
||||
|
||||
if self.params["gain"] >= 0:
|
||||
cmdline.extend(["-g", "{}".format(self.params["gain"])])
|
||||
if self.params["overlap"] > 0:
|
||||
cmdline.extend(["-o", "{}".format(self.params["overlap"])])
|
||||
if not self.params["single_shot"]:
|
||||
cmdline.append("-c")
|
||||
|
||||
additional_params = settings.value("params", Info.additional_params)
|
||||
if additional_params:
|
||||
cmdline.extend(shlex.split(additional_params))
|
||||
|
||||
print('Starting backend:')
|
||||
print(' '.join(cmdline))
|
||||
print()
|
||||
self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
|
||||
universal_newlines=True, console=False)
|
||||
|
||||
def parse_output(self, line):
|
||||
"""Parse one line of output from rtl_power_fftw"""
|
||||
line = line.strip()
|
||||
|
||||
# One empty line => new hop
|
||||
if not line and self.prev_line:
|
||||
self.hop += 1
|
||||
self.databuffer["x"].extend(self.databuffer_hop["x"])
|
||||
self.databuffer["y"].extend(self.databuffer_hop["y"])
|
||||
self.databuffer_hop = {"timestamp": [], "x": [], "y": []}
|
||||
|
||||
# Two empty lines => new set
|
||||
elif not line and not self.prev_line:
|
||||
self.hop = 0
|
||||
self.data_storage.update(self.databuffer)
|
||||
self.databuffer = {"timestamp": [], "x": [], "y": []}
|
||||
|
||||
# Get timestamp for new hop and set
|
||||
elif line.startswith("# Acquisition start:"):
|
||||
timestamp = line.split(":", 1)[1].strip()
|
||||
if not self.databuffer_hop["timestamp"]:
|
||||
self.databuffer_hop["timestamp"] = timestamp
|
||||
if not self.databuffer["timestamp"]:
|
||||
self.databuffer["timestamp"] = timestamp
|
||||
|
||||
# Skip other comments
|
||||
elif line.startswith("#"):
|
||||
pass
|
||||
|
||||
# Parse frequency and power
|
||||
elif line[0].isdigit():
|
||||
freq, power = line.split()
|
||||
freq, power = float(freq) + self.lnb_lo, float(power)
|
||||
start_freq, stop_freq = self.freqs_crop[self.hop]
|
||||
|
||||
# Apply cropping
|
||||
if freq >= start_freq and freq <= stop_freq:
|
||||
# Skip overlapping frequencies
|
||||
if not self.databuffer["x"] or freq > self.databuffer["x"][-1]:
|
||||
#print(" {:.3f} MHz".format(freq / 1e6))
|
||||
self.databuffer_hop["x"].append(freq)
|
||||
self.databuffer_hop["y"].append(power)
|
||||
else:
|
||||
#print(" Overlapping {:.3f} MHz".format(freq / 1e6))
|
||||
pass
|
||||
else:
|
||||
#print(" Cropping {:.3f} MHz".format(freq / 1e6))
|
||||
pass
|
||||
|
||||
self.prev_line = line
|
@ -1,109 +0,0 @@
|
||||
import shlex
|
||||
|
||||
import numpy as np
|
||||
from Qt import QtCore
|
||||
|
||||
from qspectrumanalyzer import subprocess
|
||||
from qspectrumanalyzer.backends import BaseInfo, BasePowerThread
|
||||
|
||||
|
||||
class Info(BaseInfo):
|
||||
"""rx_power device metadata"""
|
||||
sample_rate_min = 0
|
||||
sample_rate_max = 0
|
||||
sample_rate = 0
|
||||
start_freq_min = 0
|
||||
start_freq_max = 7250
|
||||
stop_freq_min = 0
|
||||
stop_freq_max = 7250
|
||||
gain_min = -1
|
||||
gain_max = 999
|
||||
bin_size_min = 0
|
||||
bin_size_max = 2800
|
||||
|
||||
|
||||
class PowerThread(BasePowerThread):
|
||||
"""Thread which runs rx_power process"""
|
||||
def setup(self, start_freq, stop_freq, bin_size, interval=10.0, gain=-1, ppm=0, crop=0,
|
||||
single_shot=False, device=0, sample_rate=2560000, bandwidth=0, lnb_lo=0):
|
||||
"""Setup rx_power params"""
|
||||
self.params = {
|
||||
"start_freq": start_freq,
|
||||
"stop_freq": stop_freq,
|
||||
"bin_size": bin_size,
|
||||
"interval": interval,
|
||||
"device": device,
|
||||
"hops": 0,
|
||||
"gain": gain,
|
||||
"ppm": ppm,
|
||||
"crop": crop,
|
||||
"single_shot": single_shot
|
||||
}
|
||||
self.lnb_lo = lnb_lo
|
||||
self.databuffer = {}
|
||||
self.last_timestamp = ""
|
||||
|
||||
def process_start(self):
|
||||
"""Start rx_power process"""
|
||||
if not self.process and self.params:
|
||||
settings = QtCore.QSettings()
|
||||
cmdline = shlex.split(settings.value("executable", "rx_power"))
|
||||
cmdline.extend([
|
||||
"-f", "{}M:{}M:{}k".format(self.params["start_freq"] - self.lnb_lo / 1e6,
|
||||
self.params["stop_freq"] - self.lnb_lo / 1e6,
|
||||
self.params["bin_size"]),
|
||||
"-i", "{}".format(self.params["interval"]),
|
||||
"-d", "{}".format(self.params["device"]),
|
||||
"-p", "{}".format(self.params["ppm"]),
|
||||
"-c", "{}".format(self.params["crop"])
|
||||
])
|
||||
|
||||
if self.params["gain"] >= 0:
|
||||
cmdline.extend(["-g", "{}".format(self.params["gain"])])
|
||||
if self.params["single_shot"]:
|
||||
cmdline.append("-1")
|
||||
|
||||
additional_params = settings.value("params", Info.additional_params)
|
||||
if additional_params:
|
||||
cmdline.extend(shlex.split(additional_params))
|
||||
|
||||
print('Starting backend:')
|
||||
print(' '.join(cmdline))
|
||||
print()
|
||||
self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
|
||||
universal_newlines=True, console=False)
|
||||
|
||||
def parse_output(self, line):
|
||||
"""Parse one line of output from rx_power"""
|
||||
line = [col.strip() for col in line.split(",")]
|
||||
timestamp = " ".join(line[:2])
|
||||
start_freq = int(line[2])
|
||||
stop_freq = int(line[3])
|
||||
step = float(line[4])
|
||||
samples = float(line[5])
|
||||
|
||||
x_axis = list(np.linspace(start_freq + self.lnb_lo, stop_freq + self.lnb_lo,
|
||||
round((stop_freq - start_freq) / step)))
|
||||
y_axis = [float(y) for y in line[6:]]
|
||||
if len(x_axis) != len(y_axis):
|
||||
print("ERROR: len(x_axis) != len(y_axis)!")
|
||||
if len(x_axis) > len(y_axis):
|
||||
print("Trimming x_axis...")
|
||||
x_axis = x_axis[:len(y_axis)]
|
||||
else:
|
||||
print("Trimming y_axis...")
|
||||
y_axis = y_axis[:len(x_axis)]
|
||||
|
||||
if timestamp != self.last_timestamp:
|
||||
self.last_timestamp = timestamp
|
||||
self.databuffer = {"timestamp": timestamp,
|
||||
"x": x_axis,
|
||||
"y": y_axis}
|
||||
else:
|
||||
self.databuffer["x"].extend(x_axis)
|
||||
self.databuffer["y"].extend(y_axis)
|
||||
|
||||
# This have to be stupid like this to be compatible with old broken version of rtl_power. Right way is:
|
||||
# if stop_freq == (self.params["stop_freq"] - self.lnb_lo / 1e6) * 1e6:
|
||||
if stop_freq > ((self.params["stop_freq"] - self.lnb_lo / 1e6) * 1e6) - step:
|
||||
self.data_storage.update(self.databuffer)
|
@ -1,254 +0,0 @@
|
||||
import os, sys, shlex, signal
|
||||
|
||||
import numpy as np
|
||||
from Qt import QtCore
|
||||
|
||||
from qspectrumanalyzer import subprocess
|
||||
from qspectrumanalyzer.backends import BaseInfo, BasePowerThread
|
||||
|
||||
try:
|
||||
from soapypower.writer import SoapyPowerBinFormat
|
||||
formatter = SoapyPowerBinFormat()
|
||||
except ImportError:
|
||||
print('soapy_power module not found!')
|
||||
formatter = None
|
||||
|
||||
|
||||
class Info(BaseInfo):
|
||||
"""soapy_power device metadata"""
|
||||
sample_rate_min = 0
|
||||
sample_rate_max = 61440000
|
||||
bandwidth_min = 0
|
||||
bandwidth_max = 61440000
|
||||
start_freq_min = 0
|
||||
start_freq_max = 7250
|
||||
stop_freq_min = 0
|
||||
stop_freq_max = 7250
|
||||
gain_min = -1
|
||||
gain_max = 999
|
||||
bin_size_min = 0
|
||||
bin_size_max = 10000
|
||||
additional_params = '--even --fft-window boxcar --remove-dc'
|
||||
|
||||
@classmethod
|
||||
def help_device(cls, executable, device):
|
||||
cmdline = shlex.split(executable)
|
||||
try:
|
||||
text = subprocess.check_output(cmdline + ['--detect'], universal_newlines=True,
|
||||
stderr=subprocess.DEVNULL, env=dict(os.environ, COLUMNS='125'),
|
||||
console=False)
|
||||
text += '\n'
|
||||
text += subprocess.check_output(cmdline + ['--device', device, '--info'], universal_newlines=True,
|
||||
stderr=subprocess.DEVNULL, env=dict(os.environ, COLUMNS='125'),
|
||||
console=False)
|
||||
except subprocess.CalledProcessError as e:
|
||||
text = e.output
|
||||
except OSError:
|
||||
text = '{} executable not found!'.format(executable)
|
||||
return text
|
||||
|
||||
|
||||
class PowerThread(BasePowerThread):
|
||||
"""Thread which runs soapy_power process"""
|
||||
def setup(self, start_freq, stop_freq, bin_size, interval=10.0, gain=-1, ppm=0, crop=0,
|
||||
single_shot=False, device="", sample_rate=2560000, bandwidth=0, lnb_lo=0):
|
||||
"""Setup soapy_power params"""
|
||||
self.params = {
|
||||
"start_freq": start_freq,
|
||||
"stop_freq": stop_freq,
|
||||
"device": device,
|
||||
"sample_rate": sample_rate,
|
||||
"bandwidth": bandwidth,
|
||||
"bin_size": bin_size,
|
||||
"interval": interval,
|
||||
"hops": 0,
|
||||
"gain": gain,
|
||||
"ppm": ppm,
|
||||
"crop": crop * 100,
|
||||
"single_shot": single_shot
|
||||
}
|
||||
self.lnb_lo = lnb_lo
|
||||
self.databuffer = {"timestamp": [], "x": [], "y": []}
|
||||
self.min_freq = None
|
||||
|
||||
self.pipe_read = None
|
||||
self.pipe_read_fd = None
|
||||
self.pipe_write_fd = None
|
||||
self.pipe_write_handle = None
|
||||
|
||||
def process_start(self):
|
||||
"""Start soapy_power process"""
|
||||
if not self.process and self.params:
|
||||
# Create pipe used for communication with soapy_power process
|
||||
self.pipe_read_fd, self.pipe_write_fd = os.pipe()
|
||||
self.pipe_read = open(self.pipe_read_fd, 'rb')
|
||||
os.set_inheritable(self.pipe_write_fd, True)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
self.pipe_write_handle = subprocess.make_inheritable_handle(self.pipe_write_fd)
|
||||
|
||||
# Prepare soapy_power cmdline parameters
|
||||
settings = QtCore.QSettings()
|
||||
cmdline = shlex.split(settings.value("executable", "soapy_power"))
|
||||
cmdline.extend([
|
||||
"-f", "{}M:{}M".format(self.params["start_freq"],
|
||||
self.params["stop_freq"]),
|
||||
"-B", "{}k".format(self.params["bin_size"]),
|
||||
"-T", "{}".format(self.params["interval"]),
|
||||
"-d", "{}".format(self.params["device"]),
|
||||
"-r", "{}".format(self.params["sample_rate"]),
|
||||
"-p", "{}".format(self.params["ppm"]),
|
||||
"-F", "soapy_power_bin",
|
||||
"--output-fd", "{}".format(
|
||||
int(self.pipe_write_handle) if sys.platform == 'win32' else self.pipe_write_fd
|
||||
),
|
||||
])
|
||||
|
||||
if self.lnb_lo != 0:
|
||||
cmdline.extend(["--lnb-lo", "{}".format(self.lnb_lo)])
|
||||
if self.params["bandwidth"] > 0:
|
||||
cmdline.extend(["-w", "{}".format(self.params["bandwidth"])])
|
||||
if self.params["gain"] >= 0:
|
||||
cmdline.extend(["-g", "{}".format(self.params["gain"])])
|
||||
if self.params["crop"] > 0:
|
||||
cmdline.extend(["-k", "{}".format(self.params["crop"])])
|
||||
if not self.params["single_shot"]:
|
||||
cmdline.append("-c")
|
||||
|
||||
additional_params = settings.value("params", Info.additional_params)
|
||||
if additional_params:
|
||||
cmdline.extend(shlex.split(additional_params))
|
||||
|
||||
# Start soapy_power process and close write part of pipe
|
||||
if sys.platform == 'win32':
|
||||
creationflags = subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
else:
|
||||
creationflags = 0
|
||||
|
||||
print('Starting backend:')
|
||||
print(' '.join(cmdline))
|
||||
print()
|
||||
self.process = subprocess.Popen(cmdline, close_fds=False, universal_newlines=False,
|
||||
creationflags=creationflags, console=False)
|
||||
|
||||
os.close(self.pipe_write_fd)
|
||||
if sys.platform == 'win32':
|
||||
self.pipe_write_handle.Close()
|
||||
|
||||
def process_stop(self):
|
||||
"""Stop soapy_power process"""
|
||||
with self._shutdown_lock:
|
||||
if self.process:
|
||||
if self.process.poll() is None:
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
self.process.send_signal(signal.CTRL_BREAK_EVENT)
|
||||
else:
|
||||
self.process.terminate()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
self.process.wait()
|
||||
self.process = None
|
||||
|
||||
# Close pipe used for communication with soapy_power process
|
||||
self.pipe_read.close()
|
||||
|
||||
self.pipe_read = None
|
||||
self.pipe_read_fd = None
|
||||
self.pipe_write_fd = None
|
||||
self.pipe_write_handle = None
|
||||
|
||||
def parse_output(self, data):
|
||||
"""Parse data from soapy_power"""
|
||||
header, y_axis = data
|
||||
|
||||
time_start = header.time_start
|
||||
time_stop = header.time_stop
|
||||
start_freq = header.start
|
||||
stop_freq = header.stop
|
||||
step = header.step
|
||||
samples = header.samples
|
||||
|
||||
x_axis = np.linspace(start_freq, stop_freq, round((stop_freq - start_freq) / step))
|
||||
if len(x_axis) != len(y_axis):
|
||||
print("ERROR: len(x_axis) != len(y_axis)")
|
||||
return
|
||||
|
||||
if self.min_freq is None:
|
||||
self.min_freq = start_freq
|
||||
|
||||
if start_freq == self.min_freq:
|
||||
self.databuffer = {"timestamp": time_stop,
|
||||
"x": list(x_axis),
|
||||
"y": list(y_axis)}
|
||||
else:
|
||||
self.databuffer["x"].extend(x_axis)
|
||||
self.databuffer["y"].extend(y_axis)
|
||||
|
||||
if stop_freq > (self.params["stop_freq"] * 1e6) - step:
|
||||
self.data_storage.update(self.databuffer)
|
||||
|
||||
def run(self):
|
||||
"""soapy_power thread main loop"""
|
||||
if not formatter:
|
||||
return
|
||||
|
||||
self.process_start()
|
||||
self.alive = True
|
||||
self.powerThreadStarted.emit()
|
||||
|
||||
while self.alive:
|
||||
try:
|
||||
data = formatter.read(self.pipe_read)
|
||||
except ValueError as e:
|
||||
print(e, file=sys.stderr)
|
||||
continue
|
||||
|
||||
if data:
|
||||
self.parse_output(data)
|
||||
else:
|
||||
break
|
||||
|
||||
self.process_stop()
|
||||
self.alive = False
|
||||
self.powerThreadStopped.emit()
|
||||
|
||||
|
||||
def read_from_file(f):
|
||||
"""Generator for reading data from soapy_power binary files"""
|
||||
if not formatter:
|
||||
return
|
||||
|
||||
min_freq = None
|
||||
databuffer = None
|
||||
|
||||
while True:
|
||||
try:
|
||||
data = formatter.read(f)
|
||||
except ValueError as e:
|
||||
print(e, file=sys.stderr)
|
||||
continue
|
||||
|
||||
if not data:
|
||||
if min_freq is not None:
|
||||
yield databuffer
|
||||
return
|
||||
|
||||
header, y_axis = data
|
||||
x_axis = np.linspace(header.start, header.stop, round((header.stop - header.start) / header.step))
|
||||
if len(x_axis) != len(y_axis):
|
||||
print("ERROR: len(x_axis) != len(y_axis)")
|
||||
continue
|
||||
|
||||
if min_freq is None:
|
||||
min_freq = header.start
|
||||
elif header.start == min_freq:
|
||||
yield databuffer
|
||||
|
||||
if header.start == min_freq:
|
||||
databuffer = {"timestamp": header.time_stop,
|
||||
"x": list(x_axis),
|
||||
"y": list(y_axis)}
|
||||
else:
|
||||
databuffer["x"].extend(x_axis)
|
||||
databuffer["y"].extend(y_axis)
|
@ -1,28 +0,0 @@
|
||||
from Qt import QtCore, QtWidgets
|
||||
|
||||
from qspectrumanalyzer.ui_qspectrumanalyzer_baseline import Ui_QSpectrumAnalyzerBaseline
|
||||
|
||||
|
||||
class QSpectrumAnalyzerBaseline(QtWidgets.QDialog, Ui_QSpectrumAnalyzerBaseline):
|
||||
"""QSpectrumAnalyzer baseline dialog"""
|
||||
def __init__(self, parent=None):
|
||||
# Initialize UI
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
# Load settings
|
||||
settings = QtCore.QSettings()
|
||||
self.baselineFileEdit.setText(settings.value("baseline_file", ""))
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_baselineFileButton_clicked(self):
|
||||
"""Open file dialog when button is clicked"""
|
||||
filename = QtWidgets.QFileDialog.getOpenFileName(self, self.tr("Select baseline file - QSpectrumAnalyzer"))[0]
|
||||
if filename:
|
||||
self.baselineFileEdit.setText(filename)
|
||||
|
||||
def accept(self):
|
||||
"""Save settings when dialog is accepted"""
|
||||
settings = QtCore.QSettings()
|
||||
settings.setValue("baseline_file", self.baselineFileEdit.text())
|
||||
QtWidgets.QDialog.accept(self)
|
@ -1,33 +0,0 @@
|
||||
from Qt import QtCore, QtWidgets
|
||||
|
||||
from qspectrumanalyzer.utils import color_to_str, str_to_color
|
||||
|
||||
from qspectrumanalyzer.ui_qspectrumanalyzer_colors import Ui_QSpectrumAnalyzerColors
|
||||
|
||||
|
||||
class QSpectrumAnalyzerColors(QtWidgets.QDialog, Ui_QSpectrumAnalyzerColors):
|
||||
"""QSpectrumAnalyzer colors dialog"""
|
||||
def __init__(self, parent=None):
|
||||
# Initialize UI
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
# Load settings
|
||||
settings = QtCore.QSettings()
|
||||
self.mainColorButton.setColor(str_to_color(settings.value("main_color", "255, 255, 0, 255")))
|
||||
self.peakHoldMaxColorButton.setColor(str_to_color(settings.value("peak_hold_max_color", "255, 0, 0, 255")))
|
||||
self.peakHoldMinColorButton.setColor(str_to_color(settings.value("peak_hold_min_color", "0, 0, 255, 255")))
|
||||
self.averageColorButton.setColor(str_to_color(settings.value("average_color", "0, 255, 255, 255")))
|
||||
self.persistenceColorButton.setColor(str_to_color(settings.value("persistence_color", "0, 255, 0, 255")))
|
||||
self.baselineColorButton.setColor(str_to_color(settings.value("baseline_color", "255, 0, 255, 255")))
|
||||
|
||||
def accept(self):
|
||||
"""Save settings when dialog is accepted"""
|
||||
settings = QtCore.QSettings()
|
||||
settings.setValue("main_color", color_to_str(self.mainColorButton.color()))
|
||||
settings.setValue("peak_hold_max_color", color_to_str(self.peakHoldMaxColorButton.color()))
|
||||
settings.setValue("peak_hold_min_color", color_to_str(self.peakHoldMinColorButton.color()))
|
||||
settings.setValue("average_color", color_to_str(self.averageColorButton.color()))
|
||||
settings.setValue("persistence_color", color_to_str(self.persistenceColorButton.color()))
|
||||
settings.setValue("baseline_color", color_to_str(self.baselineColorButton.color()))
|
||||
QtWidgets.QDialog.accept(self)
|
@ -1,296 +0,0 @@
|
||||
import time, sys, os
|
||||
|
||||
from Qt import QtCore
|
||||
import numpy as np
|
||||
|
||||
from qspectrumanalyzer.utils import smooth
|
||||
from qspectrumanalyzer.backends import soapy_power
|
||||
|
||||
|
||||
class HistoryBuffer:
|
||||
"""Fixed-size NumPy array ring buffer"""
|
||||
def __init__(self, data_size, max_history_size, dtype=float):
|
||||
self.data_size = data_size
|
||||
self.max_history_size = max_history_size
|
||||
self.history_size = 0
|
||||
self.counter = 0
|
||||
self.buffer = np.empty(shape=(max_history_size, data_size), dtype=dtype)
|
||||
|
||||
def append(self, data):
|
||||
"""Append new data to ring buffer"""
|
||||
self.counter += 1
|
||||
if self.history_size < self.max_history_size:
|
||||
self.history_size += 1
|
||||
self.buffer = np.roll(self.buffer, -1, axis=0)
|
||||
self.buffer[-1] = data
|
||||
|
||||
def get_buffer(self):
|
||||
"""Return buffer stripped to size of actual data"""
|
||||
if self.history_size < self.max_history_size:
|
||||
return self.buffer[-self.history_size:]
|
||||
else:
|
||||
return self.buffer
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.buffer[key]
|
||||
|
||||
|
||||
class TaskSignals(QtCore.QObject):
|
||||
"""Task signals emitter"""
|
||||
result = QtCore.Signal(object)
|
||||
|
||||
|
||||
class Task(QtCore.QRunnable):
|
||||
"""Threaded task (run it with QThreadPool worker threads)"""
|
||||
def __init__(self, task, *args, **kwargs):
|
||||
super().__init__()
|
||||
self.task = task
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.signals = TaskSignals()
|
||||
|
||||
def run(self):
|
||||
"""Run task in worker thread and emit signal with result"""
|
||||
#print('Running', self.task, 'in thread', QtCore.QThread.currentThreadId())
|
||||
result = self.task(*self.args, **self.kwargs)
|
||||
self.signals.result.emit(result)
|
||||
|
||||
|
||||
class DataStorage(QtCore.QObject):
|
||||
"""Data storage for spectrum measurements"""
|
||||
history_updated = QtCore.Signal(object)
|
||||
data_updated = QtCore.Signal(object)
|
||||
history_recalculated = QtCore.Signal(object)
|
||||
data_recalculated = QtCore.Signal(object)
|
||||
average_updated = QtCore.Signal(object)
|
||||
baseline_updated = QtCore.Signal(object)
|
||||
peak_hold_max_updated = QtCore.Signal(object)
|
||||
peak_hold_min_updated = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, max_history_size=100, parent=None):
|
||||
super().__init__(parent)
|
||||
self.max_history_size = max_history_size
|
||||
self.smooth = False
|
||||
self.smooth_length = 11
|
||||
self.smooth_window = "hanning"
|
||||
self.subtract_baseline = False
|
||||
self.prev_baseline = None
|
||||
self.baseline = None
|
||||
self.baseline_x = None
|
||||
|
||||
# Use only one worker thread because it is not faster
|
||||
# with more threads (and memory consumption is much higher)
|
||||
self.threadpool = QtCore.QThreadPool()
|
||||
self.threadpool.setMaxThreadCount(1)
|
||||
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""Reset all data"""
|
||||
self.wait()
|
||||
self.x = None
|
||||
self.history = None
|
||||
self.reset_data()
|
||||
|
||||
def reset_data(self):
|
||||
"""Reset current data"""
|
||||
self.wait()
|
||||
self.y = None
|
||||
self.average_counter = 0
|
||||
self.average = None
|
||||
self.peak_hold_max = None
|
||||
self.peak_hold_min = None
|
||||
|
||||
def start_task(self, fn, *args, **kwargs):
|
||||
"""Run function asynchronously in worker thread"""
|
||||
task = Task(fn, *args, **kwargs)
|
||||
self.threadpool.start(task)
|
||||
|
||||
def wait(self):
|
||||
"""Wait for worker threads to complete all running tasks"""
|
||||
self.threadpool.waitForDone()
|
||||
|
||||
def update(self, data):
|
||||
"""Update data storage"""
|
||||
if self.y is not None and len(data["y"]) != len(self.y):
|
||||
print("{:d} bins coming from backend, expected {:d}".format(len(data["y"]), len(self.y)))
|
||||
return
|
||||
|
||||
self.average_counter += 1
|
||||
|
||||
if self.x is None:
|
||||
self.x = data["x"]
|
||||
|
||||
# Subtract baseline from data
|
||||
data["y"] = np.asarray(data["y"])
|
||||
if self.subtract_baseline and self.baseline is not None and len(data["y"]) == len(self.baseline):
|
||||
data["y"] -= self.baseline
|
||||
|
||||
self.start_task(self.update_history, data.copy())
|
||||
self.start_task(self.update_data, data)
|
||||
|
||||
def update_data(self, data):
|
||||
"""Update main spectrum data (and possibly apply smoothing)"""
|
||||
if self.smooth:
|
||||
data["y"] = self.smooth_data(data["y"])
|
||||
|
||||
self.y = data["y"]
|
||||
self.data_updated.emit(self)
|
||||
|
||||
self.start_task(self.update_average, data)
|
||||
self.start_task(self.update_peak_hold_max, data)
|
||||
self.start_task(self.update_peak_hold_min, data)
|
||||
|
||||
def update_history(self, data):
|
||||
"""Update spectrum measurements history"""
|
||||
if self.history is None:
|
||||
self.history = HistoryBuffer(len(data["y"]), self.max_history_size)
|
||||
|
||||
self.history.append(data["y"])
|
||||
self.history_updated.emit(self)
|
||||
|
||||
def update_average(self, data):
|
||||
"""Update average data"""
|
||||
if self.average is None:
|
||||
self.average = data["y"].copy()
|
||||
else:
|
||||
self.average = np.average((self.average, data["y"]), axis=0, weights=(self.average_counter - 1, 1))
|
||||
self.average_updated.emit(self)
|
||||
|
||||
def update_peak_hold_max(self, data):
|
||||
"""Update max. peak hold data"""
|
||||
if self.peak_hold_max is None:
|
||||
self.peak_hold_max = data["y"].copy()
|
||||
else:
|
||||
self.peak_hold_max = np.maximum(self.peak_hold_max, data["y"])
|
||||
self.peak_hold_max_updated.emit(self)
|
||||
|
||||
def update_peak_hold_min(self, data):
|
||||
"""Update min. peak hold data"""
|
||||
if self.peak_hold_min is None:
|
||||
self.peak_hold_min = data["y"].copy()
|
||||
else:
|
||||
self.peak_hold_min = np.minimum(self.peak_hold_min, data["y"])
|
||||
self.peak_hold_min_updated.emit(self)
|
||||
|
||||
def smooth_data(self, y):
|
||||
"""Apply smoothing function to data"""
|
||||
return smooth(y, window_len=self.smooth_length, window=self.smooth_window)
|
||||
|
||||
def set_smooth(self, toggle, length=11, window="hanning"):
|
||||
"""Toggle smoothing and set smoothing params"""
|
||||
if toggle != self.smooth or length != self.smooth_length or window != self.smooth_window:
|
||||
self.smooth = toggle
|
||||
self.smooth_length = length
|
||||
self.smooth_window = window
|
||||
self.start_task(self.recalculate_data)
|
||||
|
||||
def set_subtract_baseline(self, toggle, baseline_file=None):
|
||||
"""Toggle baseline subtraction and set baseline"""
|
||||
baseline = None
|
||||
baseline_x = None
|
||||
|
||||
# Load baseline from file (compute average if there are multiple PSD data in file)
|
||||
if baseline_file and os.path.isfile(baseline_file):
|
||||
average_counter = 0
|
||||
with open(baseline_file, 'rb') as f:
|
||||
for data in soapy_power.read_from_file(f):
|
||||
average_counter += 1
|
||||
if baseline is None:
|
||||
baseline = data['y'].copy()
|
||||
baseline_x = data['x'].copy()
|
||||
else:
|
||||
baseline = np.average((baseline, data['y']), axis=0, weights=(average_counter - 1, 1))
|
||||
|
||||
# Don't subtract baseline if number of bins in baseline differs from number of bins in data
|
||||
if self.y is not None and baseline is not None and len(self.y) != len(baseline):
|
||||
print("Can't subtract baseline (expected {:d} bins, but baseline has {:d} bins)".format(
|
||||
len(self.y), len(baseline)
|
||||
))
|
||||
#baseline = None
|
||||
|
||||
if self.subtract_baseline:
|
||||
self.prev_baseline = self.baseline
|
||||
|
||||
#if not np.array_equal(baseline, self.baseline):
|
||||
self.baseline = baseline
|
||||
self.baseline_x = baseline_x
|
||||
self.baseline_updated.emit(self)
|
||||
|
||||
self.subtract_baseline = toggle
|
||||
self.start_task(self.recalculate_history)
|
||||
self.start_task(self.recalculate_data)
|
||||
|
||||
def recalculate_history(self):
|
||||
"""Recalculate spectrum measurements history"""
|
||||
if self.history is None:
|
||||
return
|
||||
|
||||
history = self.history.get_buffer()
|
||||
if self.prev_baseline is not None and len(history[-1]) == len(self.prev_baseline):
|
||||
history += self.prev_baseline
|
||||
self.prev_baseline = None
|
||||
if self.subtract_baseline and self.baseline is not None and len(history[-1]) == len(self.baseline):
|
||||
history -= self.baseline
|
||||
|
||||
self.history_recalculated.emit(self)
|
||||
|
||||
def recalculate_data(self):
|
||||
"""Recalculate current data from history"""
|
||||
if self.history is None:
|
||||
return
|
||||
|
||||
history = self.history.get_buffer()
|
||||
if self.smooth:
|
||||
self.y = self.smooth_data(history[-1])
|
||||
self.average_counter = 0
|
||||
self.average = self.y.copy()
|
||||
self.peak_hold_max = self.y.copy()
|
||||
self.peak_hold_min = self.y.copy()
|
||||
for y in history[:-1]:
|
||||
self.average_counter += 1
|
||||
y = self.smooth_data(y)
|
||||
self.average = np.average((self.average, y), axis=0, weights=(self.average_counter - 1, 1))
|
||||
self.peak_hold_max = np.maximum(self.peak_hold_max, y)
|
||||
self.peak_hold_min = np.minimum(self.peak_hold_min, y)
|
||||
else:
|
||||
self.y = history[-1]
|
||||
self.average_counter = self.history.history_size
|
||||
self.average = np.average(history, axis=0)
|
||||
self.peak_hold_max = history.max(axis=0)
|
||||
self.peak_hold_min = history.min(axis=0)
|
||||
|
||||
self.data_recalculated.emit(self)
|
||||
#self.data_updated.emit({"x": self.x, "y": self.y})
|
||||
#self.average_updated.emit({"x": self.x, "y": self.average})
|
||||
#self.peak_hold_max_updated.emit({"x": self.x, "y": self.peak_hold_max})
|
||||
#self.peak_hold_min_updated.emit({"x": self.x, "y": self.peak_hold_min})
|
||||
|
||||
|
||||
class Test:
|
||||
"""Test data storage performance"""
|
||||
def __init__(self, data_size=100000, max_history_size=100):
|
||||
self.data_size = data_size
|
||||
self.data = {"x": np.arange(data_size),
|
||||
"y": None}
|
||||
self.datastorage = DataStorage(max_history_size)
|
||||
|
||||
def run_one(self):
|
||||
"""Generate random data and update data storage"""
|
||||
self.data["y"] = np.random.normal(size=self.data_size)
|
||||
self.datastorage.update(self.data)
|
||||
|
||||
def run(self, runs=1000):
|
||||
"""Run performance test"""
|
||||
t = time.time()
|
||||
for i in range(runs):
|
||||
self.run_one()
|
||||
self.datastorage.wait()
|
||||
total_time = time.time() - t
|
||||
print("Total time:", total_time)
|
||||
print("FPS:", runs / total_time)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test = Test(int(sys.argv[1]), int(sys.argv[2]))
|
||||
test.run(int(sys.argv[3]))
|
@ -1 +0,0 @@
|
||||
<クdハ<>箆!ソ`。スン
|
@ -1,439 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS><TS version="2.0">
|
||||
<context>
|
||||
<name>QSpectrumAnalyzerBaseline</name>
|
||||
<message>
|
||||
<location filename="../baseline.py" line="20"/>
|
||||
<source>Select baseline file - QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_baseline.py" line="50"/>
|
||||
<source>Baseline - QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_baseline.py" line="51"/>
|
||||
<source>Baseline &file:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_baseline.py" line="52"/>
|
||||
<source>...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QSpectrumAnalyzerColors</name>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_colors.py" line="112"/>
|
||||
<source>Colors - QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_colors.py" line="124"/>
|
||||
<source>...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_colors.py" line="113"/>
|
||||
<source>&Main curve color:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_colors.py" line="115"/>
|
||||
<source>Max. peak &hold color:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_colors.py" line="117"/>
|
||||
<source>M&in. peak hold color:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_colors.py" line="119"/>
|
||||
<source>Average &color:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_colors.py" line="121"/>
|
||||
<source>Persistence co&lor:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_colors.py" line="123"/>
|
||||
<source>&Baseline color:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QSpectrumAnalyzerMainWindow</name>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="317"/>
|
||||
<source>QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../__main__.py" line="496"/>
|
||||
<source>About - QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../__main__.py" line="496"/>
|
||||
<source>QSpectrumAnalyzer {}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="318"/>
|
||||
<source>&File</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="319"/>
|
||||
<source>&Help</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="350"/>
|
||||
<source>&Settings...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="351"/>
|
||||
<source>&Quit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="352"/>
|
||||
<source>Ctrl+Q</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="353"/>
|
||||
<source>&About</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../__main__.py" line="253"/>
|
||||
<source>Frequency hops: {}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../__main__.py" line="255"/>
|
||||
<source>Total time: {} | Sweep time: {:.2f} s ({:.2f} FPS)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="321"/>
|
||||
<source>&Start</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="322"/>
|
||||
<source>S&top</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="323"/>
|
||||
<source>Si&ngle shot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="325"/>
|
||||
<source>Start:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="328"/>
|
||||
<source> MHz</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="327"/>
|
||||
<source>Stop:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="329"/>
|
||||
<source>&Bin size:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="330"/>
|
||||
<source> kHz</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="332"/>
|
||||
<source>&Interval [s]:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="333"/>
|
||||
<source>&Gain [dB]:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="334"/>
|
||||
<source>Corr. [ppm]:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="335"/>
|
||||
<source>Crop [%]:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="336"/>
|
||||
<source>Main curve</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="337"/>
|
||||
<source>Colors...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="338"/>
|
||||
<source>Max. hold</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="339"/>
|
||||
<source>Min. hold</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="340"/>
|
||||
<source>Average</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="341"/>
|
||||
<source>Smoothing</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="347"/>
|
||||
<source>...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="343"/>
|
||||
<source>Persistence</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="345"/>
|
||||
<source>auto</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="348"/>
|
||||
<source>Subtract baseline</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="346"/>
|
||||
<source>Baseline</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="320"/>
|
||||
<source>Controls</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="324"/>
|
||||
<source>Frequency</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="331"/>
|
||||
<source>Settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer.py" line="349"/>
|
||||
<source>Levels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QSpectrumAnalyzerPersistence</name>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_persistence.py" line="55"/>
|
||||
<source>Persistence - QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_persistence.py" line="56"/>
|
||||
<source>Decay function:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_persistence.py" line="57"/>
|
||||
<source>linear</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_persistence.py" line="58"/>
|
||||
<source>exponential</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_persistence.py" line="59"/>
|
||||
<source>Persistence length:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QSpectrumAnalyzerSettings</name>
|
||||
<message>
|
||||
<location filename="../settings.py" line="57"/>
|
||||
<source>Select executable - QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="148"/>
|
||||
<source>Settings - QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="149"/>
|
||||
<source>&Backend:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="156"/>
|
||||
<source>soapy_power</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="151"/>
|
||||
<source>rx_power</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="152"/>
|
||||
<source>rtl_power_fftw</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="153"/>
|
||||
<source>rtl_power</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="154"/>
|
||||
<source>hackrf_sweep</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="155"/>
|
||||
<source>E&xecutable:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="157"/>
|
||||
<source>...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="159"/>
|
||||
<source>Sa&mple rate:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="160"/>
|
||||
<source>&Waterfall history size:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="158"/>
|
||||
<source>&Device:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="161"/>
|
||||
<source>Bandwidt&h:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="163"/>
|
||||
<source>&LNB LO:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="166"/>
|
||||
<source> ? </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="169"/>
|
||||
<source>Negative frequency for upconverters, positive frequency for downconverters.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="164"/>
|
||||
<source>Add&itional parameters:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings.py" line="170"/>
|
||||
<source> MHz</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QSpectrumAnalyzerSettingsHelp</name>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_settings_help.py" line="36"/>
|
||||
<source>Help - QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QSpectrumAnalyzerSmoothing</name>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_smoothing.py" line="60"/>
|
||||
<source>Smoothing - QSpectrumAnalyzer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_smoothing.py" line="61"/>
|
||||
<source>&Window function:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_smoothing.py" line="62"/>
|
||||
<source>rectangular</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_smoothing.py" line="63"/>
|
||||
<source>hanning</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_smoothing.py" line="64"/>
|
||||
<source>hamming</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_smoothing.py" line="65"/>
|
||||
<source>bartlett</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_smoothing.py" line="66"/>
|
||||
<source>blackman</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui_qspectrumanalyzer_smoothing.py" line="67"/>
|
||||
<source>Window len&gth:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
@ -1,29 +0,0 @@
|
||||
from Qt import QtCore, QtWidgets
|
||||
|
||||
from qspectrumanalyzer.ui_qspectrumanalyzer_persistence import Ui_QSpectrumAnalyzerPersistence
|
||||
|
||||
|
||||
class QSpectrumAnalyzerPersistence(QtWidgets.QDialog, Ui_QSpectrumAnalyzerPersistence):
|
||||
"""QSpectrumAnalyzer spectrum persistence dialog"""
|
||||
def __init__(self, parent=None):
|
||||
# Initialize UI
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
# Load settings
|
||||
settings = QtCore.QSettings()
|
||||
self.persistenceLengthSpinBox.setValue(settings.value("persistence_length", 5, int))
|
||||
|
||||
decay_function = settings.value("persistence_decay", "exponential")
|
||||
i = self.decayFunctionComboBox.findText(decay_function)
|
||||
if i == -1:
|
||||
self.decayFunctionComboBox.setCurrentIndex(0)
|
||||
else:
|
||||
self.decayFunctionComboBox.setCurrentIndex(i)
|
||||
|
||||
def accept(self):
|
||||
"""Save settings when dialog is accepted"""
|
||||
settings = QtCore.QSettings()
|
||||
settings.setValue("persistence_length", self.persistenceLengthSpinBox.value())
|
||||
settings.setValue("persistence_decay", self.decayFunctionComboBox.currentText())
|
||||
QtWidgets.QDialog.accept(self)
|
@ -1,348 +0,0 @@
|
||||
import collections, math
|
||||
|
||||
from Qt import QtCore
|
||||
import pyqtgraph as pg
|
||||
|
||||
# Basic PyQtGraph settings
|
||||
pg.setConfigOptions(antialias=True)
|
||||
|
||||
|
||||
class SpectrumPlotWidget:
|
||||
"""Main spectrum plot"""
|
||||
def __init__(self, layout):
|
||||
if not isinstance(layout, pg.GraphicsLayoutWidget):
|
||||
raise ValueError("layout must be instance of pyqtgraph.GraphicsLayoutWidget")
|
||||
|
||||
self.layout = layout
|
||||
|
||||
self.main_curve = True
|
||||
self.main_color = pg.mkColor("y")
|
||||
self.persistence = False
|
||||
self.persistence_length = 5
|
||||
self.persistence_decay = "exponential"
|
||||
self.persistence_color = pg.mkColor("g")
|
||||
self.persistence_data = None
|
||||
self.persistence_curves = None
|
||||
self.peak_hold_max = False
|
||||
self.peak_hold_max_color = pg.mkColor("r")
|
||||
self.peak_hold_min = False
|
||||
self.peak_hold_min_color = pg.mkColor("b")
|
||||
self.average = False
|
||||
self.average_color = pg.mkColor("c")
|
||||
self.baseline = False
|
||||
self.baseline_color = pg.mkColor("m")
|
||||
|
||||
self.create_plot()
|
||||
|
||||
def create_plot(self):
|
||||
"""Create main spectrum plot"""
|
||||
self.posLabel = self.layout.addLabel(row=0, col=0, justify="right")
|
||||
self.plot = self.layout.addPlot(row=1, col=0)
|
||||
self.plot.showGrid(x=True, y=True)
|
||||
self.plot.setLabel("left", "Power", units="dB")
|
||||
self.plot.setLabel("bottom", "Frequency", units="Hz")
|
||||
self.plot.setLimits(xMin=0)
|
||||
self.plot.showButtons()
|
||||
|
||||
#self.plot.setDownsampling(mode="peak")
|
||||
#self.plot.setClipToView(True)
|
||||
|
||||
self.create_baseline_curve()
|
||||
self.create_persistence_curves()
|
||||
self.create_average_curve()
|
||||
self.create_peak_hold_min_curve()
|
||||
self.create_peak_hold_max_curve()
|
||||
self.create_main_curve()
|
||||
|
||||
# Create crosshair
|
||||
self.vLine = pg.InfiniteLine(angle=90, movable=False)
|
||||
self.vLine.setZValue(1000)
|
||||
self.hLine = pg.InfiniteLine(angle=0, movable=False)
|
||||
self.vLine.setZValue(1000)
|
||||
self.plot.addItem(self.vLine, ignoreBounds=True)
|
||||
self.plot.addItem(self.hLine, ignoreBounds=True)
|
||||
self.mouseProxy = pg.SignalProxy(self.plot.scene().sigMouseMoved,
|
||||
rateLimit=60, slot=self.mouse_moved)
|
||||
|
||||
def create_main_curve(self):
|
||||
"""Create main spectrum curve"""
|
||||
self.curve = self.plot.plot(pen=self.main_color)
|
||||
self.curve.setZValue(900)
|
||||
|
||||
def create_peak_hold_max_curve(self):
|
||||
"""Create max. peak hold curve"""
|
||||
self.curve_peak_hold_max = self.plot.plot(pen=self.peak_hold_max_color)
|
||||
self.curve_peak_hold_max.setZValue(800)
|
||||
|
||||
def create_peak_hold_min_curve(self):
|
||||
"""Create min. peak hold curve"""
|
||||
self.curve_peak_hold_min = self.plot.plot(pen=self.peak_hold_min_color)
|
||||
self.curve_peak_hold_min.setZValue(800)
|
||||
|
||||
def create_average_curve(self):
|
||||
"""Create average curve"""
|
||||
self.curve_average = self.plot.plot(pen=self.average_color)
|
||||
self.curve_average.setZValue(700)
|
||||
|
||||
def create_baseline_curve(self):
|
||||
"""Create baseline curve"""
|
||||
self.curve_baseline = self.plot.plot(pen=self.baseline_color)
|
||||
self.curve_baseline.setZValue(500)
|
||||
|
||||
def create_persistence_curves(self):
|
||||
"""Create spectrum persistence curves"""
|
||||
z_index_base = 600
|
||||
decay = self.get_decay()
|
||||
self.persistence_curves = []
|
||||
for i in range(self.persistence_length):
|
||||
alpha = 255 * decay(i + 1, self.persistence_length + 1)
|
||||
color = self.persistence_color
|
||||
curve = self.plot.plot(pen=(color.red(), color.green(), color.blue(), alpha))
|
||||
curve.setZValue(z_index_base - i)
|
||||
self.persistence_curves.append(curve)
|
||||
|
||||
def set_colors(self):
|
||||
"""Set colors of all curves"""
|
||||
self.curve.setPen(self.main_color)
|
||||
self.curve_peak_hold_max.setPen(self.peak_hold_max_color)
|
||||
self.curve_peak_hold_min.setPen(self.peak_hold_min_color)
|
||||
self.curve_average.setPen(self.average_color)
|
||||
self.curve_baseline.setPen(self.baseline_color)
|
||||
|
||||
decay = self.get_decay()
|
||||
for i, curve in enumerate(self.persistence_curves):
|
||||
alpha = 255 * decay(i + 1, self.persistence_length + 1)
|
||||
color = self.persistence_color
|
||||
curve.setPen((color.red(), color.green(), color.blue(), alpha))
|
||||
|
||||
def decay_linear(self, x, length):
|
||||
"""Get alpha value for persistence curve (linear decay)"""
|
||||
return (-x / length) + 1
|
||||
|
||||
def decay_exponential(self, x, length, const=1 / 3):
|
||||
"""Get alpha value for persistence curve (exponential decay)"""
|
||||
return math.e**(-x / (length * const))
|
||||
|
||||
def get_decay(self):
|
||||
"""Get decay function"""
|
||||
if self.persistence_decay == 'exponential':
|
||||
return self.decay_exponential
|
||||
else:
|
||||
return self.decay_linear
|
||||
|
||||
def update_plot(self, data_storage, force=False):
|
||||
"""Update main spectrum curve"""
|
||||
if data_storage.x is None:
|
||||
return
|
||||
|
||||
if self.main_curve or force:
|
||||
self.curve.setData(data_storage.x, data_storage.y)
|
||||
if force:
|
||||
self.curve.setVisible(self.main_curve)
|
||||
|
||||
def update_peak_hold_max(self, data_storage, force=False):
|
||||
"""Update max. peak hold curve"""
|
||||
if data_storage.x is None:
|
||||
return
|
||||
|
||||
if self.peak_hold_max or force:
|
||||
self.curve_peak_hold_max.setData(data_storage.x, data_storage.peak_hold_max)
|
||||
if force:
|
||||
self.curve_peak_hold_max.setVisible(self.peak_hold_max)
|
||||
|
||||
def update_peak_hold_min(self, data_storage, force=False):
|
||||
"""Update min. peak hold curve"""
|
||||
if data_storage.x is None:
|
||||
return
|
||||
|
||||
if self.peak_hold_min or force:
|
||||
self.curve_peak_hold_min.setData(data_storage.x, data_storage.peak_hold_min)
|
||||
if force:
|
||||
self.curve_peak_hold_min.setVisible(self.peak_hold_min)
|
||||
|
||||
def update_average(self, data_storage, force=False):
|
||||
"""Update average curve"""
|
||||
if data_storage.x is None:
|
||||
return
|
||||
|
||||
if self.average or force:
|
||||
self.curve_average.setData(data_storage.x, data_storage.average)
|
||||
if force:
|
||||
self.curve_average.setVisible(self.average)
|
||||
|
||||
def update_baseline(self, data_storage, force=False):
|
||||
"""Update baseline curve"""
|
||||
if data_storage.baseline_x is None or data_storage.baseline is None:
|
||||
self.curve_baseline.clear()
|
||||
return
|
||||
|
||||
if self.baseline or force:
|
||||
self.curve_baseline.setData(data_storage.baseline_x, data_storage.baseline)
|
||||
if force:
|
||||
self.curve_baseline.setVisible(self.baseline)
|
||||
|
||||
def update_persistence(self, data_storage, force=False):
|
||||
"""Update persistence curves"""
|
||||
if data_storage.x is None:
|
||||
return
|
||||
|
||||
if self.persistence or force:
|
||||
if self.persistence_data is None:
|
||||
self.persistence_data = collections.deque(maxlen=self.persistence_length)
|
||||
else:
|
||||
for i, y in enumerate(self.persistence_data):
|
||||
curve = self.persistence_curves[i]
|
||||
curve.setData(data_storage.x, y)
|
||||
if force:
|
||||
curve.setVisible(self.persistence)
|
||||
self.persistence_data.appendleft(data_storage.y)
|
||||
|
||||
def recalculate_plot(self, data_storage):
|
||||
"""Recalculate plot from history"""
|
||||
if data_storage.x is None:
|
||||
return
|
||||
|
||||
QtCore.QTimer.singleShot(0, lambda: self.update_plot(data_storage, force=True))
|
||||
QtCore.QTimer.singleShot(0, lambda: self.update_average(data_storage, force=True))
|
||||
QtCore.QTimer.singleShot(0, lambda: self.update_baseline(data_storage, force=True))
|
||||
QtCore.QTimer.singleShot(0, lambda: self.update_peak_hold_max(data_storage, force=True))
|
||||
QtCore.QTimer.singleShot(0, lambda: self.update_peak_hold_min(data_storage, force=True))
|
||||
|
||||
def recalculate_persistence(self, data_storage):
|
||||
"""Recalculate persistence data and update persistence curves"""
|
||||
if data_storage.x is None:
|
||||
return
|
||||
|
||||
self.clear_persistence()
|
||||
self.persistence_data = collections.deque(maxlen=self.persistence_length)
|
||||
for i in range(min(self.persistence_length, data_storage.history.history_size - 1)):
|
||||
data = data_storage.history[-i - 2]
|
||||
if data_storage.smooth:
|
||||
data = data_storage.smooth_data(data)
|
||||
self.persistence_data.append(data)
|
||||
QtCore.QTimer.singleShot(0, lambda: self.update_persistence(data_storage, force=True))
|
||||
|
||||
def mouse_moved(self, evt):
|
||||
"""Update crosshair when mouse is moved"""
|
||||
pos = evt[0]
|
||||
if self.plot.sceneBoundingRect().contains(pos):
|
||||
mousePoint = self.plot.vb.mapSceneToView(pos)
|
||||
self.posLabel.setText(
|
||||
"<span style='font-size: 12pt'>f={:0.3f} MHz, P={:0.3f} dB</span>".format(
|
||||
mousePoint.x() / 1e6,
|
||||
mousePoint.y()
|
||||
)
|
||||
)
|
||||
self.vLine.setPos(mousePoint.x())
|
||||
self.hLine.setPos(mousePoint.y())
|
||||
|
||||
def clear_plot(self):
|
||||
"""Clear main spectrum curve"""
|
||||
self.curve.clear()
|
||||
|
||||
def clear_peak_hold_max(self):
|
||||
"""Clear max. peak hold curve"""
|
||||
self.curve_peak_hold_max.clear()
|
||||
|
||||
def clear_peak_hold_min(self):
|
||||
"""Clear min. peak hold curve"""
|
||||
self.curve_peak_hold_min.clear()
|
||||
|
||||
def clear_average(self):
|
||||
"""Clear average curve"""
|
||||
self.curve_average.clear()
|
||||
|
||||
def clear_baseline(self):
|
||||
"""Clear baseline curve"""
|
||||
self.curve_baseline.clear()
|
||||
|
||||
def clear_persistence(self):
|
||||
"""Clear spectrum persistence curves"""
|
||||
self.persistence_data = None
|
||||
for curve in self.persistence_curves:
|
||||
curve.clear()
|
||||
self.plot.removeItem(curve)
|
||||
self.create_persistence_curves()
|
||||
|
||||
|
||||
class WaterfallPlotWidget:
|
||||
"""Waterfall plot"""
|
||||
def __init__(self, layout, histogram_layout=None):
|
||||
if not isinstance(layout, pg.GraphicsLayoutWidget):
|
||||
raise ValueError("layout must be instance of pyqtgraph.GraphicsLayoutWidget")
|
||||
|
||||
if histogram_layout and not isinstance(histogram_layout, pg.GraphicsLayoutWidget):
|
||||
raise ValueError("histogram_layout must be instance of pyqtgraph.GraphicsLayoutWidget")
|
||||
|
||||
self.layout = layout
|
||||
self.histogram_layout = histogram_layout
|
||||
|
||||
self.history_size = 100
|
||||
self.counter = 0
|
||||
|
||||
self.create_plot()
|
||||
|
||||
def create_plot(self):
|
||||
"""Create waterfall plot"""
|
||||
self.plot = self.layout.addPlot()
|
||||
self.plot.setLabel("bottom", "Frequency", units="Hz")
|
||||
self.plot.setLabel("left", "Time")
|
||||
|
||||
self.plot.setYRange(-self.history_size, 0)
|
||||
self.plot.setLimits(xMin=0, yMax=0)
|
||||
self.plot.showButtons()
|
||||
#self.plot.setAspectLocked(True)
|
||||
|
||||
#self.plot.setDownsampling(mode="peak")
|
||||
#self.plot.setClipToView(True)
|
||||
|
||||
# Setup histogram widget (for controlling waterfall plot levels and gradients)
|
||||
if self.histogram_layout:
|
||||
self.histogram = pg.HistogramLUTItem()
|
||||
self.histogram_layout.addItem(self.histogram)
|
||||
self.histogram.gradient.loadPreset("flame")
|
||||
#self.histogram.setHistogramRange(-50, 0)
|
||||
#self.histogram.setLevels(-50, 0)
|
||||
|
||||
def update_plot(self, data_storage):
|
||||
"""Update waterfall plot"""
|
||||
self.counter += 1
|
||||
|
||||
# Create waterfall image on first run
|
||||
if self.counter == 1:
|
||||
self.waterfallImg = pg.ImageItem()
|
||||
self.waterfallImg.scale((data_storage.x[-1] - data_storage.x[0]) / len(data_storage.x), 1)
|
||||
self.plot.clear()
|
||||
self.plot.addItem(self.waterfallImg)
|
||||
|
||||
# Roll down one and replace leading edge with new data
|
||||
self.waterfallImg.setImage(data_storage.history.buffer[-self.counter:].T,
|
||||
autoLevels=False, autoRange=False)
|
||||
|
||||
# Move waterfall image to always start at 0
|
||||
self.waterfallImg.setPos(
|
||||
data_storage.x[0],
|
||||
-self.counter if self.counter < self.history_size else -self.history_size
|
||||
)
|
||||
|
||||
# Link histogram widget to waterfall image on first run
|
||||
# (must be done after first data is received or else levels would be wrong)
|
||||
if self.counter == 1 and self.histogram_layout:
|
||||
self.histogram.setImageItem(self.waterfallImg)
|
||||
|
||||
def clear_plot(self):
|
||||
"""Clear waterfall plot"""
|
||||
self.counter = 0
|
||||
|
||||
def recalculate_plot(self, data_storage):
|
||||
"""Recalculate waterfall plot"""
|
||||
if data_storage.x is None:
|
||||
return
|
||||
|
||||
self.waterfallImg.setImage(data_storage.history.buffer[-self.counter:].T,
|
||||
autoLevels=False, autoRange=False)
|
||||
self.waterfallImg.setPos(
|
||||
data_storage.x[0],
|
||||
-self.counter if self.counter < self.history_size else -self.history_size
|
||||
)
|
||||
self.histogram.setImageItem(self.waterfallImg)
|
@ -1,133 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg4290"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
viewBox="0 0 32 32"
|
||||
inkscape:export-filename="qspectrumanalyzer.png"
|
||||
inkscape:export-xdpi="135"
|
||||
inkscape:export-ydpi="135"
|
||||
sodipodi:docname="qspectrumanalyzer.svg">
|
||||
<defs
|
||||
id="defs4292" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6568543"
|
||||
inkscape:cx="0.51171273"
|
||||
inkscape:cy="17.429759"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-bbox="false"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="709"
|
||||
inkscape:window-x="-4"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4298" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4295">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<rect
|
||||
style="fill:#000000;stroke:#666666;stroke-opacity:1"
|
||||
id="rect4300"
|
||||
width="30"
|
||||
height="30"
|
||||
x="1"
|
||||
y="1"
|
||||
rx="3"
|
||||
ry="3" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 16,1 0,30"
|
||||
id="path4302"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 31,16 1,16"
|
||||
id="path4302-5"
|
||||
inkscape:connector-curvature="0" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path4302"
|
||||
id="use4369"
|
||||
transform="translate(-7.5,0)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path4302"
|
||||
id="use4371"
|
||||
transform="translate(7.5,0)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4373"
|
||||
d="M 31,8.5 1,8.5"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path4302-5"
|
||||
id="use4375"
|
||||
transform="translate(0,7.5)"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#00ff00;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 1.0625,25.25 c 0,0 2.5545099,5.432144 4.3470654,0.903939 0.6415968,-1.620748 1.1843693,-10.505075 2.3294368,-10.451145 1.124605,0.05297 1.2928473,9.79388 3.0231028,11.399454 0.836834,0.735662 2.019611,-2.789575 2.96484,-2.691821 1.178304,0.121857 2.125956,3.657855 3.181223,2.383073 2.07617,-2.508052 1.527752,-21.6078848 2.588897,-21.5746877 1.200441,0.037555 1.333727,19.4989257 3.765313,21.7522057 1.533625,1.421166 1.791478,-2.106359 3.115303,-2.461012 0.660576,-0.176968 2.034012,3.420657 2.702589,3.52241 0.772278,0.117536 1.624099,-1.153766 2.01348,-0.813666"
|
||||
id="path4394"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csscssssssc" />
|
||||
<rect
|
||||
style="fill:none;stroke:#666666;stroke-opacity:1"
|
||||
id="rect4300-8"
|
||||
width="30"
|
||||
height="30"
|
||||
x="1"
|
||||
y="1"
|
||||
rx="3"
|
||||
ry="3" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.3 KiB |
@ -1,617 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QSpectrumAnalyzerMainWindow</class>
|
||||
<widget class="QMainWindow" name="QSpectrumAnalyzerMainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1200</width>
|
||||
<height>892</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>QSpectrumAnalyzer</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSplitter" name="plotSplitter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="GraphicsLayoutWidget" name="mainPlotLayout">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="GraphicsLayoutWidget" name="waterfallPlotLayout">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1200</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_File">
|
||||
<property name="title">
|
||||
<string>&File</string>
|
||||
</property>
|
||||
<addaction name="action_Settings"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Quit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Help">
|
||||
<property name="title">
|
||||
<string>&Help</string>
|
||||
</property>
|
||||
<addaction name="action_About"/>
|
||||
</widget>
|
||||
<addaction name="menu_File"/>
|
||||
<addaction name="menu_Help"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<widget class="QDockWidget" name="controlsDockWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>190</width>
|
||||
<height>130</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="features">
|
||||
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Controls</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="controlsDockWidgetContents">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="startButton">
|
||||
<property name="text">
|
||||
<string>&Start</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="stopButton">
|
||||
<property name="text">
|
||||
<string>S&top</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="singleShotButton">
|
||||
<property name="text">
|
||||
<string>Si&ngle shot</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>561</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="frequencyDockWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>208</width>
|
||||
<height>166</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="features">
|
||||
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Frequency</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="frequencyDockWidgetContents">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Start:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>startFreqSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="startFreqSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="showGroupSeparator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MHz</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>2200.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>87.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Stop:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>stopFreqSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="stopFreqSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="showGroupSeparator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MHz</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>2200.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>108.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Bin size:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>binSizeSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="binSizeSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="showGroupSeparator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> kHz</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>10000.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="settingsDockWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="features">
|
||||
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="settingsDockWidgetContents">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>&Interval [s]:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>intervalSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>&Gain [dB]:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>gainSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QDoubleSpinBox" name="intervalSpinBox">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>999.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Corr. [ppm]:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>ppmSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Crop [%]:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>cropSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QSpinBox" name="ppmSpinBox">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-999</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="mainCurveCheckBox">
|
||||
<property name="text">
|
||||
<string>Main curve</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<widget class="QPushButton" name="colorsButton">
|
||||
<property name="text">
|
||||
<string>Colors...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="peakHoldMaxCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. hold</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="peakHoldMinCheckBox">
|
||||
<property name="text">
|
||||
<string>Min. hold</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="averageCheckBox">
|
||||
<property name="text">
|
||||
<string>Average</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="smoothCheckBox">
|
||||
<property name="text">
|
||||
<string>Smoothing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="2">
|
||||
<widget class="QToolButton" name="smoothButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="persistenceCheckBox">
|
||||
<property name="text">
|
||||
<string>Persistence</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<widget class="QToolButton" name="persistenceButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QSpinBox" name="cropSpinBox">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QDoubleSpinBox" name="gainSpinBox">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="specialValueText">
|
||||
<string>auto</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-1.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>999.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>-1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QCheckBox" name="baselineCheckBox">
|
||||
<property name="text">
|
||||
<string>Baseline</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<widget class="QToolButton" name="baselineButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QCheckBox" name="subtractBaselineCheckBox">
|
||||
<property name="text">
|
||||
<string>Subtract baseline</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="levelsDockWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="features">
|
||||
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Levels</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="levelsDockWidgetContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="GraphicsLayoutWidget" name="histogramPlotLayout">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Ignored" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<action name="action_Settings">
|
||||
<property name="text">
|
||||
<string>&Settings...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Quit">
|
||||
<property name="text">
|
||||
<string>&Quit</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Q</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_About">
|
||||
<property name="text">
|
||||
<string>&About</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GraphicsLayoutWidget</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>pyqtgraph</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>startButton</tabstop>
|
||||
<tabstop>stopButton</tabstop>
|
||||
<tabstop>singleShotButton</tabstop>
|
||||
<tabstop>startFreqSpinBox</tabstop>
|
||||
<tabstop>stopFreqSpinBox</tabstop>
|
||||
<tabstop>binSizeSpinBox</tabstop>
|
||||
<tabstop>intervalSpinBox</tabstop>
|
||||
<tabstop>gainSpinBox</tabstop>
|
||||
<tabstop>ppmSpinBox</tabstop>
|
||||
<tabstop>cropSpinBox</tabstop>
|
||||
<tabstop>mainCurveCheckBox</tabstop>
|
||||
<tabstop>colorsButton</tabstop>
|
||||
<tabstop>peakHoldMaxCheckBox</tabstop>
|
||||
<tabstop>peakHoldMinCheckBox</tabstop>
|
||||
<tabstop>averageCheckBox</tabstop>
|
||||
<tabstop>smoothCheckBox</tabstop>
|
||||
<tabstop>smoothButton</tabstop>
|
||||
<tabstop>persistenceCheckBox</tabstop>
|
||||
<tabstop>persistenceButton</tabstop>
|
||||
<tabstop>baselineCheckBox</tabstop>
|
||||
<tabstop>baselineButton</tabstop>
|
||||
<tabstop>subtractBaselineCheckBox</tabstop>
|
||||
<tabstop>histogramPlotLayout</tabstop>
|
||||
<tabstop>mainPlotLayout</tabstop>
|
||||
<tabstop>waterfallPlotLayout</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -1,115 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QSpectrumAnalyzerBaseline</class>
|
||||
<widget class="QDialog" name="QSpectrumAnalyzerBaseline">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>100</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Baseline - QSpectrumAnalyzer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Baseline &file:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>baselineFileEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="baselineFileEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="baselineFileButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>baselineFileEdit</tabstop>
|
||||
<tabstop>baselineFileButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>QSpectrumAnalyzerBaseline</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>224</x>
|
||||
<y>72</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>99</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>QSpectrumAnalyzerBaseline</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>292</x>
|
||||
<y>78</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>99</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1,234 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QSpectrumAnalyzerColors</class>
|
||||
<widget class="QDialog" name="QSpectrumAnalyzerColors">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>253</width>
|
||||
<height>266</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Colors - QSpectrumAnalyzer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Main curve color:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>mainColorButton</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="ColorButton" name="mainColorButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Max. peak &hold color:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>peakHoldMaxColorButton</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="ColorButton" name="peakHoldMaxColorButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>M&in. peak hold color:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>peakHoldMinColorButton</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="ColorButton" name="peakHoldMinColorButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Average &color:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>averageColorButton</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="ColorButton" name="averageColorButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Persistence co&lor:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>persistenceColorButton</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="ColorButton" name="persistenceColorButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Baseline color:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>baselineColorButton</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="ColorButton" name="baselineColorButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ColorButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>pyqtgraph</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>mainColorButton</tabstop>
|
||||
<tabstop>peakHoldMaxColorButton</tabstop>
|
||||
<tabstop>peakHoldMinColorButton</tabstop>
|
||||
<tabstop>averageColorButton</tabstop>
|
||||
<tabstop>persistenceColorButton</tabstop>
|
||||
<tabstop>baselineColorButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>QSpectrumAnalyzerColors</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>246</x>
|
||||
<y>259</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>265</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>QSpectrumAnalyzerColors</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>246</x>
|
||||
<y>259</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>252</x>
|
||||
<y>265</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1,130 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QSpectrumAnalyzerPersistence</class>
|
||||
<widget class="QDialog" name="QSpectrumAnalyzerPersistence">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>250</width>
|
||||
<height>130</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Persistence - QSpectrumAnalyzer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Decay function:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>decayFunctionComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="decayFunctionComboBox">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>linear</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>exponential</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Persistence length:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>persistenceLengthSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="persistenceLengthSpinBox">
|
||||
<property name="value">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>5</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>decayFunctionComboBox</tabstop>
|
||||
<tabstop>persistenceLengthSpinBox</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>QSpectrumAnalyzerPersistence</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>243</x>
|
||||
<y>123</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>129</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>QSpectrumAnalyzerPersistence</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>243</x>
|
||||
<y>123</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>249</x>
|
||||
<y>129</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1,361 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QSpectrumAnalyzerSettings</class>
|
||||
<widget class="QDialog" name="QSpectrumAnalyzerSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>388</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings - QSpectrumAnalyzer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>&Backend:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>backendComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="backendComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>soapy_power</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>rx_power</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>rtl_power_fftw</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>rtl_power</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>hackrf_sweep</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>E&xecutable:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>executableEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="executableEdit">
|
||||
<property name="text">
|
||||
<string>soapy_power</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="executableButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>&Device:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>deviceEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Sa&mple rate:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>sampleRateSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Waterfall history size:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>waterfallHistorySizeSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QSpinBox" name="waterfallHistorySizeSpinBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Bandwidt&h:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>bandwidthSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="toolTip">
|
||||
<string>Negative frequency for upconverters, positive frequency for downconverters.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&LNB LO:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lnbSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Add&itional parameters:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>paramsEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="paramsEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="paramsHelpButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> ? </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="deviceEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="deviceHelpButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> ? </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QDoubleSpinBox" name="sampleRateSpinBox">
|
||||
<property name="showGroupSeparator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MHz</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>999999.989999999990687</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>61.439999999999998</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QDoubleSpinBox" name="bandwidthSpinBox">
|
||||
<property name="showGroupSeparator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MHz</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>999999.989999999990687</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QDoubleSpinBox" name="lnbSpinBox">
|
||||
<property name="toolTip">
|
||||
<string>Negative frequency for upconverters, positive frequency for downconverters.</string>
|
||||
</property>
|
||||
<property name="showGroupSeparator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MHz</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-999999.998999999952503</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>999999.998999999952503</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>21</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>backendComboBox</tabstop>
|
||||
<tabstop>executableEdit</tabstop>
|
||||
<tabstop>executableButton</tabstop>
|
||||
<tabstop>paramsEdit</tabstop>
|
||||
<tabstop>paramsHelpButton</tabstop>
|
||||
<tabstop>deviceEdit</tabstop>
|
||||
<tabstop>deviceHelpButton</tabstop>
|
||||
<tabstop>sampleRateSpinBox</tabstop>
|
||||
<tabstop>bandwidthSpinBox</tabstop>
|
||||
<tabstop>lnbSpinBox</tabstop>
|
||||
<tabstop>waterfallHistorySizeSpinBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>QSpectrumAnalyzerSettings</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>254</x>
|
||||
<y>377</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>QSpectrumAnalyzerSettings</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>322</x>
|
||||
<y>377</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QSpectrumAnalyzerSettingsHelp</class>
|
||||
<widget class="QDialog" name="QSpectrumAnalyzerSettingsHelp">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1200</width>
|
||||
<height>700</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Help - QSpectrumAnalyzer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="helpTextEdit">
|
||||
<property name="undoRedoEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>helpTextEdit</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>QSpectrumAnalyzerSettingsHelp</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>224</x>
|
||||
<y>672</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>QSpectrumAnalyzerSettingsHelp</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>292</x>
|
||||
<y>678</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1,151 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QSpectrumAnalyzerSmoothing</class>
|
||||
<widget class="QDialog" name="QSpectrumAnalyzerSmoothing">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>250</width>
|
||||
<height>130</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Smoothing - QSpectrumAnalyzer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Window function:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>windowFunctionComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="windowFunctionComboBox">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>rectangular</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>hanning</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>hamming</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>bartlett</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>blackman</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Window len&gth:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>windowLengthSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="windowLengthSpinBox">
|
||||
<property name="minimum">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1001</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>11</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>windowFunctionComboBox</tabstop>
|
||||
<tabstop>windowLengthSpinBox</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>QSpectrumAnalyzerSmoothing</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>218</x>
|
||||
<y>104</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>129</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>QSpectrumAnalyzerSmoothing</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>218</x>
|
||||
<y>110</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>224</x>
|
||||
<y>129</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1,139 +0,0 @@
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
from qspectrumanalyzer import backends
|
||||
|
||||
from qspectrumanalyzer.ui_qspectrumanalyzer_settings import Ui_QSpectrumAnalyzerSettings
|
||||
from qspectrumanalyzer.ui_qspectrumanalyzer_settings_help import Ui_QSpectrumAnalyzerSettingsHelp
|
||||
|
||||
|
||||
class QSpectrumAnalyzerSettings(QtWidgets.QDialog, Ui_QSpectrumAnalyzerSettings):
|
||||
"""QSpectrumAnalyzer settings dialog"""
|
||||
def __init__(self, parent=None):
|
||||
# Initialize UI
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.params_help_dialog = None
|
||||
self.device_help_dialog = None
|
||||
|
||||
# Load settings
|
||||
settings = QtCore.QSettings()
|
||||
self.executableEdit.setText(settings.value("executable", "soapy_power"))
|
||||
self.deviceEdit.setText(settings.value("device", ""))
|
||||
self.lnbSpinBox.setValue(settings.value("lnb_lo", 0, float) / 1e6)
|
||||
self.waterfallHistorySizeSpinBox.setValue(settings.value("waterfall_history_size", 100, int))
|
||||
|
||||
backend = settings.value("backend", "soapy_power")
|
||||
try:
|
||||
backend_module = getattr(backends, backend)
|
||||
except AttributeError:
|
||||
backend_module = backends.soapy_power
|
||||
|
||||
self.paramsEdit.setText(settings.value("params", backend_module.Info.additional_params))
|
||||
self.deviceHelpButton.setEnabled(bool(backend_module.Info.help_device))
|
||||
|
||||
self.sampleRateSpinBox.setMinimum(backend_module.Info.sample_rate_min / 1e6)
|
||||
self.sampleRateSpinBox.setMaximum(backend_module.Info.sample_rate_max / 1e6)
|
||||
self.sampleRateSpinBox.setValue(settings.value("sample_rate", backend_module.Info.sample_rate, float) / 1e6)
|
||||
|
||||
self.bandwidthSpinBox.setMinimum(backend_module.Info.bandwidth_min / 1e6)
|
||||
self.bandwidthSpinBox.setMaximum(backend_module.Info.bandwidth_max / 1e6)
|
||||
self.bandwidthSpinBox.setValue(settings.value("bandwidth", backend_module.Info.bandwidth, float) / 1e6)
|
||||
|
||||
self.backendComboBox.blockSignals(True)
|
||||
self.backendComboBox.clear()
|
||||
for b in sorted(backends.__all__):
|
||||
self.backendComboBox.addItem(b)
|
||||
|
||||
i = self.backendComboBox.findText(backend)
|
||||
if i == -1:
|
||||
self.backendComboBox.setCurrentIndex(0)
|
||||
else:
|
||||
self.backendComboBox.setCurrentIndex(i)
|
||||
self.backendComboBox.blockSignals(False)
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_executableButton_clicked(self):
|
||||
"""Open file dialog when button is clicked"""
|
||||
filename = QtWidgets.QFileDialog.getOpenFileName(self, self.tr("Select executable - QSpectrumAnalyzer"))[0]
|
||||
if filename:
|
||||
self.executableEdit.setText(filename)
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_paramsHelpButton_clicked(self):
|
||||
"""Open additional parameters help dialog when button is clicked"""
|
||||
try:
|
||||
backend_module = getattr(backends, self.backendComboBox.currentText())
|
||||
except AttributeError:
|
||||
backend_module = backends.soapy_power
|
||||
|
||||
self.params_help_dialog = QSpectrumAnalyzerSettingsHelp(
|
||||
backend_module.Info.help_params(self.executableEdit.text()),
|
||||
parent=self
|
||||
)
|
||||
|
||||
self.params_help_dialog.show()
|
||||
self.params_help_dialog.raise_()
|
||||
self.params_help_dialog.activateWindow()
|
||||
|
||||
@QtCore.Slot()
|
||||
def on_deviceHelpButton_clicked(self):
|
||||
"""Open device help dialog when button is clicked"""
|
||||
try:
|
||||
backend_module = getattr(backends, self.backendComboBox.currentText())
|
||||
except AttributeError:
|
||||
backend_module = backends.soapy_power
|
||||
|
||||
self.device_help_dialog = QSpectrumAnalyzerSettingsHelp(
|
||||
backend_module.Info.help_device(self.executableEdit.text(), self.deviceEdit.text()),
|
||||
parent=self
|
||||
)
|
||||
|
||||
self.device_help_dialog.show()
|
||||
self.device_help_dialog.raise_()
|
||||
self.device_help_dialog.activateWindow()
|
||||
|
||||
@QtCore.Slot(str)
|
||||
def on_backendComboBox_currentIndexChanged(self, text):
|
||||
"""Change executable when backend is changed"""
|
||||
self.executableEdit.setText(text)
|
||||
self.deviceEdit.setText("")
|
||||
|
||||
try:
|
||||
backend_module = getattr(backends, text)
|
||||
except AttributeError:
|
||||
backend_module = backends.soapy_power
|
||||
|
||||
self.paramsEdit.setText(backend_module.Info.additional_params)
|
||||
self.deviceHelpButton.setEnabled(bool(backend_module.Info.help_device))
|
||||
self.sampleRateSpinBox.setMinimum(backend_module.Info.sample_rate_min / 1e6)
|
||||
self.sampleRateSpinBox.setMaximum(backend_module.Info.sample_rate_max / 1e6)
|
||||
self.sampleRateSpinBox.setValue(backend_module.Info.sample_rate / 1e6)
|
||||
self.bandwidthSpinBox.setMinimum(backend_module.Info.bandwidth_min / 1e6)
|
||||
self.bandwidthSpinBox.setMaximum(backend_module.Info.bandwidth_max / 1e6)
|
||||
self.bandwidthSpinBox.setValue(backend_module.Info.bandwidth / 1e6)
|
||||
|
||||
def accept(self):
|
||||
"""Save settings when dialog is accepted"""
|
||||
settings = QtCore.QSettings()
|
||||
settings.setValue("backend", self.backendComboBox.currentText())
|
||||
settings.setValue("executable", self.executableEdit.text())
|
||||
settings.setValue("params", self.paramsEdit.text())
|
||||
settings.setValue("device", self.deviceEdit.text())
|
||||
settings.setValue("sample_rate", self.sampleRateSpinBox.value() * 1e6)
|
||||
settings.setValue("bandwidth", self.bandwidthSpinBox.value() * 1e6)
|
||||
settings.setValue("lnb_lo", self.lnbSpinBox.value() * 1e6)
|
||||
settings.setValue("waterfall_history_size", self.waterfallHistorySizeSpinBox.value())
|
||||
QtWidgets.QDialog.accept(self)
|
||||
|
||||
|
||||
class QSpectrumAnalyzerSettingsHelp(QtWidgets.QDialog, Ui_QSpectrumAnalyzerSettingsHelp):
|
||||
"""QSpectrumAnalyzer settings help dialog"""
|
||||
def __init__(self, text, parent=None):
|
||||
# Initialize UI
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
monospace_font = QtGui.QFont('monospace')
|
||||
monospace_font.setStyleHint(QtGui.QFont.Monospace)
|
||||
self.helpTextEdit.setFont(monospace_font)
|
||||
self.helpTextEdit.setPlainText(text)
|
@ -1,29 +0,0 @@
|
||||
from Qt import QtCore, QtWidgets
|
||||
|
||||
from qspectrumanalyzer.ui_qspectrumanalyzer_smoothing import Ui_QSpectrumAnalyzerSmoothing
|
||||
|
||||
|
||||
class QSpectrumAnalyzerSmoothing(QtWidgets.QDialog, Ui_QSpectrumAnalyzerSmoothing):
|
||||
"""QSpectrumAnalyzer spectrum smoothing dialog"""
|
||||
def __init__(self, parent=None):
|
||||
# Initialize UI
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
# Load settings
|
||||
settings = QtCore.QSettings()
|
||||
self.windowLengthSpinBox.setValue(settings.value("smooth_length", 11, int))
|
||||
|
||||
window_function = settings.value("smooth_window", "hanning")
|
||||
i = self.windowFunctionComboBox.findText(window_function)
|
||||
if i == -1:
|
||||
self.windowFunctionComboBox.setCurrentIndex(0)
|
||||
else:
|
||||
self.windowFunctionComboBox.setCurrentIndex(i)
|
||||
|
||||
def accept(self):
|
||||
"""Save settings when dialog is accepted"""
|
||||
settings = QtCore.QSettings()
|
||||
settings.setValue("smooth_length", self.windowLengthSpinBox.value())
|
||||
settings.setValue("smooth_window", self.windowFunctionComboBox.currentText())
|
||||
QtWidgets.QDialog.accept(self)
|
@ -1,81 +0,0 @@
|
||||
import sys, subprocess
|
||||
|
||||
# Basic attributes and exceptions
|
||||
PIPE = subprocess.PIPE
|
||||
STDOUT = subprocess.STDOUT
|
||||
DEVNULL = subprocess.DEVNULL
|
||||
SubprocessError = subprocess.SubprocessError
|
||||
TimeoutExpired = subprocess.TimeoutExpired
|
||||
CalledProcessError = subprocess.CalledProcessError
|
||||
|
||||
# Windows-only attributes and functions
|
||||
if sys.platform == 'win32':
|
||||
import msvcrt
|
||||
import _winapi
|
||||
|
||||
# creationflags
|
||||
CREATE_NEW_CONSOLE = subprocess.CREATE_NEW_CONSOLE
|
||||
CREATE_NEW_PROCESS_GROUP = subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
|
||||
# startupinfo
|
||||
STARTUPINFO = subprocess.STARTUPINFO
|
||||
STARTF_USESTDHANDLES = subprocess.STARTF_USESTDHANDLES
|
||||
STARTF_USESHOWWINDOW = subprocess.STARTF_USESHOWWINDOW
|
||||
SW_HIDE = subprocess.SW_HIDE
|
||||
|
||||
# file handles
|
||||
Handle = subprocess.Handle
|
||||
STD_INPUT_HANDLE = subprocess.STD_INPUT_HANDLE
|
||||
STD_OUTPUT_HANDLE = subprocess.STD_OUTPUT_HANDLE
|
||||
STD_ERROR_HANDLE = subprocess.STD_ERROR_HANDLE
|
||||
|
||||
def make_inheritable_handle(fd):
|
||||
"""Create inheritable duplicate of handle from file descriptor"""
|
||||
h = _winapi.DuplicateHandle(
|
||||
_winapi.GetCurrentProcess(),
|
||||
msvcrt.get_osfhandle(fd),
|
||||
_winapi.GetCurrentProcess(), 0, 1,
|
||||
_winapi.DUPLICATE_SAME_ACCESS
|
||||
)
|
||||
return subprocess.Handle(h)
|
||||
|
||||
|
||||
def hide_console_window(startupinfo=None):
|
||||
"""Returns altered startupinfo to hide console window on Windows"""
|
||||
if sys.platform != 'win32':
|
||||
return None
|
||||
|
||||
if not startupinfo:
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
return startupinfo
|
||||
|
||||
|
||||
class Popen(subprocess.Popen):
|
||||
"""subprocess.Popen with ability to hide console window on Windows"""
|
||||
def __init__(self, *pargs, console=True, **kwargs):
|
||||
if not console:
|
||||
kwargs['startupinfo'] = hide_console_window(kwargs.get('startupinfo'))
|
||||
super().__init__(*pargs, **kwargs)
|
||||
|
||||
|
||||
def call(*pargs, console=True, **kwargs):
|
||||
"""subprocess.call with ability to hide console window on Windows"""
|
||||
if not console:
|
||||
kwargs['startupinfo'] = hide_console_window(kwargs.get('startupinfo'))
|
||||
return subprocess.call(*pargs, **kwargs)
|
||||
|
||||
|
||||
def check_call(*pargs, console=True, **kwargs):
|
||||
"""subprocess.check_call with ability to hide console window on Windows"""
|
||||
if not console:
|
||||
kwargs['startupinfo'] = hide_console_window(kwargs.get('startupinfo'))
|
||||
return subprocess.check_call(*pargs, **kwargs)
|
||||
|
||||
|
||||
def check_output(*pargs, console=True, **kwargs):
|
||||
"""subprocess.check_output with ability to hide console window on Windows"""
|
||||
if not console:
|
||||
kwargs['startupinfo'] = hide_console_window(kwargs.get('startupinfo'))
|
||||
return subprocess.check_output(*pargs, **kwargs)
|
@ -1,355 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'qspectrumanalyzer/qspectrumanalyzer.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.8
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_QSpectrumAnalyzerMainWindow(object):
|
||||
def setupUi(self, QSpectrumAnalyzerMainWindow):
|
||||
QSpectrumAnalyzerMainWindow.setObjectName("QSpectrumAnalyzerMainWindow")
|
||||
QSpectrumAnalyzerMainWindow.resize(1200, 892)
|
||||
self.centralwidget = QtWidgets.QWidget(QSpectrumAnalyzerMainWindow)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.plotSplitter = QtWidgets.QSplitter(self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.plotSplitter.sizePolicy().hasHeightForWidth())
|
||||
self.plotSplitter.setSizePolicy(sizePolicy)
|
||||
self.plotSplitter.setOrientation(QtCore.Qt.Vertical)
|
||||
self.plotSplitter.setObjectName("plotSplitter")
|
||||
self.mainPlotLayout = GraphicsLayoutWidget(self.plotSplitter)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.mainPlotLayout.sizePolicy().hasHeightForWidth())
|
||||
self.mainPlotLayout.setSizePolicy(sizePolicy)
|
||||
self.mainPlotLayout.setObjectName("mainPlotLayout")
|
||||
self.waterfallPlotLayout = GraphicsLayoutWidget(self.plotSplitter)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.waterfallPlotLayout.sizePolicy().hasHeightForWidth())
|
||||
self.waterfallPlotLayout.setSizePolicy(sizePolicy)
|
||||
self.waterfallPlotLayout.setObjectName("waterfallPlotLayout")
|
||||
self.horizontalLayout.addWidget(self.plotSplitter)
|
||||
QSpectrumAnalyzerMainWindow.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QtWidgets.QMenuBar(QSpectrumAnalyzerMainWindow)
|
||||
self.menubar.setGeometry(QtCore.QRect(0, 0, 1200, 32))
|
||||
self.menubar.setObjectName("menubar")
|
||||
self.menu_File = QtWidgets.QMenu(self.menubar)
|
||||
self.menu_File.setObjectName("menu_File")
|
||||
self.menu_Help = QtWidgets.QMenu(self.menubar)
|
||||
self.menu_Help.setObjectName("menu_Help")
|
||||
QSpectrumAnalyzerMainWindow.setMenuBar(self.menubar)
|
||||
self.statusbar = QtWidgets.QStatusBar(QSpectrumAnalyzerMainWindow)
|
||||
self.statusbar.setObjectName("statusbar")
|
||||
QSpectrumAnalyzerMainWindow.setStatusBar(self.statusbar)
|
||||
self.controlsDockWidget = QtWidgets.QDockWidget(QSpectrumAnalyzerMainWindow)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.controlsDockWidget.sizePolicy().hasHeightForWidth())
|
||||
self.controlsDockWidget.setSizePolicy(sizePolicy)
|
||||
self.controlsDockWidget.setMinimumSize(QtCore.QSize(190, 130))
|
||||
self.controlsDockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetFloatable|QtWidgets.QDockWidget.DockWidgetMovable)
|
||||
self.controlsDockWidget.setObjectName("controlsDockWidget")
|
||||
self.controlsDockWidgetContents = QtWidgets.QWidget()
|
||||
self.controlsDockWidgetContents.setObjectName("controlsDockWidgetContents")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.controlsDockWidgetContents)
|
||||
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.startButton = QtWidgets.QPushButton(self.controlsDockWidgetContents)
|
||||
self.startButton.setObjectName("startButton")
|
||||
self.gridLayout_2.addWidget(self.startButton, 0, 0, 1, 1)
|
||||
self.stopButton = QtWidgets.QPushButton(self.controlsDockWidgetContents)
|
||||
self.stopButton.setObjectName("stopButton")
|
||||
self.gridLayout_2.addWidget(self.stopButton, 0, 1, 1, 1)
|
||||
self.singleShotButton = QtWidgets.QPushButton(self.controlsDockWidgetContents)
|
||||
self.singleShotButton.setObjectName("singleShotButton")
|
||||
self.gridLayout_2.addWidget(self.singleShotButton, 1, 0, 1, 2)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 561, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_2.addItem(spacerItem, 2, 0, 1, 1)
|
||||
self.controlsDockWidget.setWidget(self.controlsDockWidgetContents)
|
||||
QSpectrumAnalyzerMainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.controlsDockWidget)
|
||||
self.frequencyDockWidget = QtWidgets.QDockWidget(QSpectrumAnalyzerMainWindow)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.frequencyDockWidget.sizePolicy().hasHeightForWidth())
|
||||
self.frequencyDockWidget.setSizePolicy(sizePolicy)
|
||||
self.frequencyDockWidget.setMinimumSize(QtCore.QSize(208, 166))
|
||||
self.frequencyDockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetFloatable|QtWidgets.QDockWidget.DockWidgetMovable)
|
||||
self.frequencyDockWidget.setObjectName("frequencyDockWidget")
|
||||
self.frequencyDockWidgetContents = QtWidgets.QWidget()
|
||||
self.frequencyDockWidgetContents.setObjectName("frequencyDockWidgetContents")
|
||||
self.formLayout = QtWidgets.QFormLayout(self.frequencyDockWidgetContents)
|
||||
self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.ExpandingFieldsGrow)
|
||||
self.formLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.label_2 = QtWidgets.QLabel(self.frequencyDockWidgetContents)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_2)
|
||||
self.startFreqSpinBox = QtWidgets.QDoubleSpinBox(self.frequencyDockWidgetContents)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.startFreqSpinBox.sizePolicy().hasHeightForWidth())
|
||||
self.startFreqSpinBox.setSizePolicy(sizePolicy)
|
||||
self.startFreqSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.startFreqSpinBox.setProperty("showGroupSeparator", True)
|
||||
self.startFreqSpinBox.setDecimals(3)
|
||||
self.startFreqSpinBox.setMinimum(0.0)
|
||||
self.startFreqSpinBox.setMaximum(2200.0)
|
||||
self.startFreqSpinBox.setProperty("value", 87.0)
|
||||
self.startFreqSpinBox.setObjectName("startFreqSpinBox")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.startFreqSpinBox)
|
||||
self.label_3 = QtWidgets.QLabel(self.frequencyDockWidgetContents)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_3)
|
||||
self.stopFreqSpinBox = QtWidgets.QDoubleSpinBox(self.frequencyDockWidgetContents)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.stopFreqSpinBox.sizePolicy().hasHeightForWidth())
|
||||
self.stopFreqSpinBox.setSizePolicy(sizePolicy)
|
||||
self.stopFreqSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.stopFreqSpinBox.setProperty("showGroupSeparator", True)
|
||||
self.stopFreqSpinBox.setDecimals(3)
|
||||
self.stopFreqSpinBox.setMinimum(0.0)
|
||||
self.stopFreqSpinBox.setMaximum(2200.0)
|
||||
self.stopFreqSpinBox.setProperty("value", 108.0)
|
||||
self.stopFreqSpinBox.setObjectName("stopFreqSpinBox")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.stopFreqSpinBox)
|
||||
self.label = QtWidgets.QLabel(self.frequencyDockWidgetContents)
|
||||
self.label.setObjectName("label")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label)
|
||||
self.binSizeSpinBox = QtWidgets.QDoubleSpinBox(self.frequencyDockWidgetContents)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.binSizeSpinBox.sizePolicy().hasHeightForWidth())
|
||||
self.binSizeSpinBox.setSizePolicy(sizePolicy)
|
||||
self.binSizeSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.binSizeSpinBox.setProperty("showGroupSeparator", True)
|
||||
self.binSizeSpinBox.setDecimals(3)
|
||||
self.binSizeSpinBox.setMinimum(0.0)
|
||||
self.binSizeSpinBox.setMaximum(10000.0)
|
||||
self.binSizeSpinBox.setProperty("value", 10.0)
|
||||
self.binSizeSpinBox.setObjectName("binSizeSpinBox")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.binSizeSpinBox)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.formLayout.setItem(3, QtWidgets.QFormLayout.SpanningRole, spacerItem1)
|
||||
self.frequencyDockWidget.setWidget(self.frequencyDockWidgetContents)
|
||||
QSpectrumAnalyzerMainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.frequencyDockWidget)
|
||||
self.settingsDockWidget = QtWidgets.QDockWidget(QSpectrumAnalyzerMainWindow)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.settingsDockWidget.sizePolicy().hasHeightForWidth())
|
||||
self.settingsDockWidget.setSizePolicy(sizePolicy)
|
||||
self.settingsDockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetFloatable|QtWidgets.QDockWidget.DockWidgetMovable)
|
||||
self.settingsDockWidget.setObjectName("settingsDockWidget")
|
||||
self.settingsDockWidgetContents = QtWidgets.QWidget()
|
||||
self.settingsDockWidgetContents.setObjectName("settingsDockWidgetContents")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.settingsDockWidgetContents)
|
||||
self.gridLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.label_4 = QtWidgets.QLabel(self.settingsDockWidgetContents)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.gridLayout.addWidget(self.label_4, 0, 0, 1, 1)
|
||||
self.label_6 = QtWidgets.QLabel(self.settingsDockWidgetContents)
|
||||
self.label_6.setObjectName("label_6")
|
||||
self.gridLayout.addWidget(self.label_6, 0, 1, 1, 1)
|
||||
self.intervalSpinBox = QtWidgets.QDoubleSpinBox(self.settingsDockWidgetContents)
|
||||
self.intervalSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.intervalSpinBox.setMaximum(999.0)
|
||||
self.intervalSpinBox.setProperty("value", 1.0)
|
||||
self.intervalSpinBox.setObjectName("intervalSpinBox")
|
||||
self.gridLayout.addWidget(self.intervalSpinBox, 1, 0, 1, 1)
|
||||
self.label_5 = QtWidgets.QLabel(self.settingsDockWidgetContents)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.gridLayout.addWidget(self.label_5, 2, 0, 1, 1)
|
||||
self.label_7 = QtWidgets.QLabel(self.settingsDockWidgetContents)
|
||||
self.label_7.setObjectName("label_7")
|
||||
self.gridLayout.addWidget(self.label_7, 2, 1, 1, 1)
|
||||
self.ppmSpinBox = QtWidgets.QSpinBox(self.settingsDockWidgetContents)
|
||||
self.ppmSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.ppmSpinBox.setMinimum(-999)
|
||||
self.ppmSpinBox.setMaximum(999)
|
||||
self.ppmSpinBox.setObjectName("ppmSpinBox")
|
||||
self.gridLayout.addWidget(self.ppmSpinBox, 3, 0, 1, 1)
|
||||
self.mainCurveCheckBox = QtWidgets.QCheckBox(self.settingsDockWidgetContents)
|
||||
self.mainCurveCheckBox.setChecked(True)
|
||||
self.mainCurveCheckBox.setObjectName("mainCurveCheckBox")
|
||||
self.gridLayout.addWidget(self.mainCurveCheckBox, 4, 0, 1, 1)
|
||||
self.colorsButton = QtWidgets.QPushButton(self.settingsDockWidgetContents)
|
||||
self.colorsButton.setObjectName("colorsButton")
|
||||
self.gridLayout.addWidget(self.colorsButton, 4, 1, 1, 2)
|
||||
self.peakHoldMaxCheckBox = QtWidgets.QCheckBox(self.settingsDockWidgetContents)
|
||||
self.peakHoldMaxCheckBox.setObjectName("peakHoldMaxCheckBox")
|
||||
self.gridLayout.addWidget(self.peakHoldMaxCheckBox, 5, 0, 1, 1)
|
||||
self.peakHoldMinCheckBox = QtWidgets.QCheckBox(self.settingsDockWidgetContents)
|
||||
self.peakHoldMinCheckBox.setObjectName("peakHoldMinCheckBox")
|
||||
self.gridLayout.addWidget(self.peakHoldMinCheckBox, 5, 1, 1, 2)
|
||||
self.averageCheckBox = QtWidgets.QCheckBox(self.settingsDockWidgetContents)
|
||||
self.averageCheckBox.setObjectName("averageCheckBox")
|
||||
self.gridLayout.addWidget(self.averageCheckBox, 6, 0, 1, 1)
|
||||
self.smoothCheckBox = QtWidgets.QCheckBox(self.settingsDockWidgetContents)
|
||||
self.smoothCheckBox.setObjectName("smoothCheckBox")
|
||||
self.gridLayout.addWidget(self.smoothCheckBox, 7, 0, 1, 1)
|
||||
self.smoothButton = QtWidgets.QToolButton(self.settingsDockWidgetContents)
|
||||
self.smoothButton.setAutoRaise(False)
|
||||
self.smoothButton.setObjectName("smoothButton")
|
||||
self.gridLayout.addWidget(self.smoothButton, 7, 2, 1, 1)
|
||||
self.persistenceCheckBox = QtWidgets.QCheckBox(self.settingsDockWidgetContents)
|
||||
self.persistenceCheckBox.setObjectName("persistenceCheckBox")
|
||||
self.gridLayout.addWidget(self.persistenceCheckBox, 8, 0, 1, 1)
|
||||
self.persistenceButton = QtWidgets.QToolButton(self.settingsDockWidgetContents)
|
||||
self.persistenceButton.setAutoRaise(False)
|
||||
self.persistenceButton.setObjectName("persistenceButton")
|
||||
self.gridLayout.addWidget(self.persistenceButton, 8, 2, 1, 1)
|
||||
spacerItem2 = QtWidgets.QSpacerItem(20, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem2, 11, 0, 1, 1)
|
||||
self.cropSpinBox = QtWidgets.QSpinBox(self.settingsDockWidgetContents)
|
||||
self.cropSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.cropSpinBox.setObjectName("cropSpinBox")
|
||||
self.gridLayout.addWidget(self.cropSpinBox, 3, 1, 1, 2)
|
||||
self.gainSpinBox = QtWidgets.QDoubleSpinBox(self.settingsDockWidgetContents)
|
||||
self.gainSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.gainSpinBox.setDecimals(1)
|
||||
self.gainSpinBox.setMinimum(-1.0)
|
||||
self.gainSpinBox.setMaximum(999.0)
|
||||
self.gainSpinBox.setSingleStep(1.0)
|
||||
self.gainSpinBox.setProperty("value", -1.0)
|
||||
self.gainSpinBox.setObjectName("gainSpinBox")
|
||||
self.gridLayout.addWidget(self.gainSpinBox, 1, 1, 1, 2)
|
||||
self.baselineCheckBox = QtWidgets.QCheckBox(self.settingsDockWidgetContents)
|
||||
self.baselineCheckBox.setObjectName("baselineCheckBox")
|
||||
self.gridLayout.addWidget(self.baselineCheckBox, 9, 0, 1, 1)
|
||||
self.baselineButton = QtWidgets.QToolButton(self.settingsDockWidgetContents)
|
||||
self.baselineButton.setAutoRaise(False)
|
||||
self.baselineButton.setObjectName("baselineButton")
|
||||
self.gridLayout.addWidget(self.baselineButton, 9, 2, 1, 1)
|
||||
self.subtractBaselineCheckBox = QtWidgets.QCheckBox(self.settingsDockWidgetContents)
|
||||
self.subtractBaselineCheckBox.setObjectName("subtractBaselineCheckBox")
|
||||
self.gridLayout.addWidget(self.subtractBaselineCheckBox, 10, 0, 1, 1)
|
||||
self.settingsDockWidget.setWidget(self.settingsDockWidgetContents)
|
||||
QSpectrumAnalyzerMainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.settingsDockWidget)
|
||||
self.levelsDockWidget = QtWidgets.QDockWidget(QSpectrumAnalyzerMainWindow)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.levelsDockWidget.sizePolicy().hasHeightForWidth())
|
||||
self.levelsDockWidget.setSizePolicy(sizePolicy)
|
||||
self.levelsDockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetFloatable|QtWidgets.QDockWidget.DockWidgetMovable)
|
||||
self.levelsDockWidget.setObjectName("levelsDockWidget")
|
||||
self.levelsDockWidgetContents = QtWidgets.QWidget()
|
||||
self.levelsDockWidgetContents.setObjectName("levelsDockWidgetContents")
|
||||
self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.levelsDockWidgetContents)
|
||||
self.verticalLayout_6.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout_6.setObjectName("verticalLayout_6")
|
||||
self.histogramPlotLayout = GraphicsLayoutWidget(self.levelsDockWidgetContents)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.histogramPlotLayout.sizePolicy().hasHeightForWidth())
|
||||
self.histogramPlotLayout.setSizePolicy(sizePolicy)
|
||||
self.histogramPlotLayout.setObjectName("histogramPlotLayout")
|
||||
self.verticalLayout_6.addWidget(self.histogramPlotLayout)
|
||||
self.levelsDockWidget.setWidget(self.levelsDockWidgetContents)
|
||||
QSpectrumAnalyzerMainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.levelsDockWidget)
|
||||
self.action_Settings = QtWidgets.QAction(QSpectrumAnalyzerMainWindow)
|
||||
self.action_Settings.setObjectName("action_Settings")
|
||||
self.action_Quit = QtWidgets.QAction(QSpectrumAnalyzerMainWindow)
|
||||
self.action_Quit.setObjectName("action_Quit")
|
||||
self.action_About = QtWidgets.QAction(QSpectrumAnalyzerMainWindow)
|
||||
self.action_About.setObjectName("action_About")
|
||||
self.menu_File.addAction(self.action_Settings)
|
||||
self.menu_File.addSeparator()
|
||||
self.menu_File.addAction(self.action_Quit)
|
||||
self.menu_Help.addAction(self.action_About)
|
||||
self.menubar.addAction(self.menu_File.menuAction())
|
||||
self.menubar.addAction(self.menu_Help.menuAction())
|
||||
self.label_2.setBuddy(self.startFreqSpinBox)
|
||||
self.label_3.setBuddy(self.stopFreqSpinBox)
|
||||
self.label.setBuddy(self.binSizeSpinBox)
|
||||
self.label_4.setBuddy(self.intervalSpinBox)
|
||||
self.label_6.setBuddy(self.gainSpinBox)
|
||||
self.label_5.setBuddy(self.ppmSpinBox)
|
||||
self.label_7.setBuddy(self.cropSpinBox)
|
||||
|
||||
self.retranslateUi(QSpectrumAnalyzerMainWindow)
|
||||
QtCore.QMetaObject.connectSlotsByName(QSpectrumAnalyzerMainWindow)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.startButton, self.stopButton)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.stopButton, self.singleShotButton)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.singleShotButton, self.startFreqSpinBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.startFreqSpinBox, self.stopFreqSpinBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.stopFreqSpinBox, self.binSizeSpinBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.binSizeSpinBox, self.intervalSpinBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.intervalSpinBox, self.gainSpinBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.gainSpinBox, self.ppmSpinBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.ppmSpinBox, self.cropSpinBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.cropSpinBox, self.mainCurveCheckBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.mainCurveCheckBox, self.colorsButton)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.colorsButton, self.peakHoldMaxCheckBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.peakHoldMaxCheckBox, self.peakHoldMinCheckBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.peakHoldMinCheckBox, self.averageCheckBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.averageCheckBox, self.smoothCheckBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.smoothCheckBox, self.smoothButton)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.smoothButton, self.persistenceCheckBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.persistenceCheckBox, self.persistenceButton)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.persistenceButton, self.baselineCheckBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.baselineCheckBox, self.baselineButton)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.baselineButton, self.subtractBaselineCheckBox)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.subtractBaselineCheckBox, self.histogramPlotLayout)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.histogramPlotLayout, self.mainPlotLayout)
|
||||
QSpectrumAnalyzerMainWindow.setTabOrder(self.mainPlotLayout, self.waterfallPlotLayout)
|
||||
|
||||
def retranslateUi(self, QSpectrumAnalyzerMainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
QSpectrumAnalyzerMainWindow.setWindowTitle(_translate("QSpectrumAnalyzerMainWindow", "QSpectrumAnalyzer"))
|
||||
self.menu_File.setTitle(_translate("QSpectrumAnalyzerMainWindow", "&File"))
|
||||
self.menu_Help.setTitle(_translate("QSpectrumAnalyzerMainWindow", "&Help"))
|
||||
self.controlsDockWidget.setWindowTitle(_translate("QSpectrumAnalyzerMainWindow", "Controls"))
|
||||
self.startButton.setText(_translate("QSpectrumAnalyzerMainWindow", "&Start"))
|
||||
self.stopButton.setText(_translate("QSpectrumAnalyzerMainWindow", "S&top"))
|
||||
self.singleShotButton.setText(_translate("QSpectrumAnalyzerMainWindow", "Si&ngle shot"))
|
||||
self.frequencyDockWidget.setWindowTitle(_translate("QSpectrumAnalyzerMainWindow", "Frequency"))
|
||||
self.label_2.setText(_translate("QSpectrumAnalyzerMainWindow", "Start:"))
|
||||
self.startFreqSpinBox.setSuffix(_translate("QSpectrumAnalyzerMainWindow", " MHz"))
|
||||
self.label_3.setText(_translate("QSpectrumAnalyzerMainWindow", "Stop:"))
|
||||
self.stopFreqSpinBox.setSuffix(_translate("QSpectrumAnalyzerMainWindow", " MHz"))
|
||||
self.label.setText(_translate("QSpectrumAnalyzerMainWindow", "&Bin size:"))
|
||||
self.binSizeSpinBox.setSuffix(_translate("QSpectrumAnalyzerMainWindow", " kHz"))
|
||||
self.settingsDockWidget.setWindowTitle(_translate("QSpectrumAnalyzerMainWindow", "Settings"))
|
||||
self.label_4.setText(_translate("QSpectrumAnalyzerMainWindow", "&Interval [s]:"))
|
||||
self.label_6.setText(_translate("QSpectrumAnalyzerMainWindow", "&Gain [dB]:"))
|
||||
self.label_5.setText(_translate("QSpectrumAnalyzerMainWindow", "Corr. [ppm]:"))
|
||||
self.label_7.setText(_translate("QSpectrumAnalyzerMainWindow", "Crop [%]:"))
|
||||
self.mainCurveCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Main curve"))
|
||||
self.colorsButton.setText(_translate("QSpectrumAnalyzerMainWindow", "Colors..."))
|
||||
self.peakHoldMaxCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Max. hold"))
|
||||
self.peakHoldMinCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Min. hold"))
|
||||
self.averageCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Average"))
|
||||
self.smoothCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Smoothing"))
|
||||
self.smoothButton.setText(_translate("QSpectrumAnalyzerMainWindow", "..."))
|
||||
self.persistenceCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Persistence"))
|
||||
self.persistenceButton.setText(_translate("QSpectrumAnalyzerMainWindow", "..."))
|
||||
self.gainSpinBox.setSpecialValueText(_translate("QSpectrumAnalyzerMainWindow", "auto"))
|
||||
self.baselineCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Baseline"))
|
||||
self.baselineButton.setText(_translate("QSpectrumAnalyzerMainWindow", "..."))
|
||||
self.subtractBaselineCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Subtract baseline"))
|
||||
self.levelsDockWidget.setWindowTitle(_translate("QSpectrumAnalyzerMainWindow", "Levels"))
|
||||
self.action_Settings.setText(_translate("QSpectrumAnalyzerMainWindow", "&Settings..."))
|
||||
self.action_Quit.setText(_translate("QSpectrumAnalyzerMainWindow", "&Quit"))
|
||||
self.action_Quit.setShortcut(_translate("QSpectrumAnalyzerMainWindow", "Ctrl+Q"))
|
||||
self.action_About.setText(_translate("QSpectrumAnalyzerMainWindow", "&About"))
|
||||
|
||||
from pyqtgraph import GraphicsLayoutWidget
|
@ -1,53 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'qspectrumanalyzer/qspectrumanalyzer_baseline.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.8
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_QSpectrumAnalyzerBaseline(object):
|
||||
def setupUi(self, QSpectrumAnalyzerBaseline):
|
||||
QSpectrumAnalyzerBaseline.setObjectName("QSpectrumAnalyzerBaseline")
|
||||
QSpectrumAnalyzerBaseline.resize(500, 100)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QSpectrumAnalyzerBaseline)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.formLayout = QtWidgets.QFormLayout()
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.label = QtWidgets.QLabel(QSpectrumAnalyzerBaseline)
|
||||
self.label.setObjectName("label")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.baselineFileEdit = QtWidgets.QLineEdit(QSpectrumAnalyzerBaseline)
|
||||
self.baselineFileEdit.setObjectName("baselineFileEdit")
|
||||
self.horizontalLayout.addWidget(self.baselineFileEdit)
|
||||
self.baselineFileButton = QtWidgets.QToolButton(QSpectrumAnalyzerBaseline)
|
||||
self.baselineFileButton.setMinimumSize(QtCore.QSize(50, 0))
|
||||
self.baselineFileButton.setObjectName("baselineFileButton")
|
||||
self.horizontalLayout.addWidget(self.baselineFileButton)
|
||||
self.formLayout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout)
|
||||
self.verticalLayout.addLayout(self.formLayout)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(QSpectrumAnalyzerBaseline)
|
||||
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.verticalLayout.addWidget(self.buttonBox)
|
||||
self.label.setBuddy(self.baselineFileEdit)
|
||||
|
||||
self.retranslateUi(QSpectrumAnalyzerBaseline)
|
||||
self.buttonBox.accepted.connect(QSpectrumAnalyzerBaseline.accept)
|
||||
self.buttonBox.rejected.connect(QSpectrumAnalyzerBaseline.reject)
|
||||
QtCore.QMetaObject.connectSlotsByName(QSpectrumAnalyzerBaseline)
|
||||
QSpectrumAnalyzerBaseline.setTabOrder(self.baselineFileEdit, self.baselineFileButton)
|
||||
|
||||
def retranslateUi(self, QSpectrumAnalyzerBaseline):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
QSpectrumAnalyzerBaseline.setWindowTitle(_translate("QSpectrumAnalyzerBaseline", "Baseline - QSpectrumAnalyzer"))
|
||||
self.label.setText(_translate("QSpectrumAnalyzerBaseline", "Baseline &file:"))
|
||||
self.baselineFileButton.setText(_translate("QSpectrumAnalyzerBaseline", "..."))
|
||||
|
@ -1,126 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'qspectrumanalyzer/qspectrumanalyzer_colors.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.8
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_QSpectrumAnalyzerColors(object):
|
||||
def setupUi(self, QSpectrumAnalyzerColors):
|
||||
QSpectrumAnalyzerColors.setObjectName("QSpectrumAnalyzerColors")
|
||||
QSpectrumAnalyzerColors.resize(253, 266)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QSpectrumAnalyzerColors)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.formLayout = QtWidgets.QFormLayout()
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.label_2 = QtWidgets.QLabel(QSpectrumAnalyzerColors)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_2)
|
||||
self.mainColorButton = ColorButton(QSpectrumAnalyzerColors)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.mainColorButton.sizePolicy().hasHeightForWidth())
|
||||
self.mainColorButton.setSizePolicy(sizePolicy)
|
||||
self.mainColorButton.setObjectName("mainColorButton")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.mainColorButton)
|
||||
self.label_4 = QtWidgets.QLabel(QSpectrumAnalyzerColors)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_4)
|
||||
self.peakHoldMaxColorButton = ColorButton(QSpectrumAnalyzerColors)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.peakHoldMaxColorButton.sizePolicy().hasHeightForWidth())
|
||||
self.peakHoldMaxColorButton.setSizePolicy(sizePolicy)
|
||||
self.peakHoldMaxColorButton.setObjectName("peakHoldMaxColorButton")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.peakHoldMaxColorButton)
|
||||
self.label_6 = QtWidgets.QLabel(QSpectrumAnalyzerColors)
|
||||
self.label_6.setObjectName("label_6")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_6)
|
||||
self.peakHoldMinColorButton = ColorButton(QSpectrumAnalyzerColors)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.peakHoldMinColorButton.sizePolicy().hasHeightForWidth())
|
||||
self.peakHoldMinColorButton.setSizePolicy(sizePolicy)
|
||||
self.peakHoldMinColorButton.setObjectName("peakHoldMinColorButton")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.peakHoldMinColorButton)
|
||||
self.label_5 = QtWidgets.QLabel(QSpectrumAnalyzerColors)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_5)
|
||||
self.averageColorButton = ColorButton(QSpectrumAnalyzerColors)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.averageColorButton.sizePolicy().hasHeightForWidth())
|
||||
self.averageColorButton.setSizePolicy(sizePolicy)
|
||||
self.averageColorButton.setObjectName("averageColorButton")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.averageColorButton)
|
||||
self.label_3 = QtWidgets.QLabel(QSpectrumAnalyzerColors)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_3)
|
||||
self.persistenceColorButton = ColorButton(QSpectrumAnalyzerColors)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.persistenceColorButton.sizePolicy().hasHeightForWidth())
|
||||
self.persistenceColorButton.setSizePolicy(sizePolicy)
|
||||
self.persistenceColorButton.setObjectName("persistenceColorButton")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.persistenceColorButton)
|
||||
self.label = QtWidgets.QLabel(QSpectrumAnalyzerColors)
|
||||
self.label.setObjectName("label")
|
||||
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label)
|
||||
self.baselineColorButton = ColorButton(QSpectrumAnalyzerColors)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.baselineColorButton.sizePolicy().hasHeightForWidth())
|
||||
self.baselineColorButton.setSizePolicy(sizePolicy)
|
||||
self.baselineColorButton.setObjectName("baselineColorButton")
|
||||
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.baselineColorButton)
|
||||
self.verticalLayout.addLayout(self.formLayout)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 2, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(QSpectrumAnalyzerColors)
|
||||
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.verticalLayout.addWidget(self.buttonBox)
|
||||
self.label_2.setBuddy(self.mainColorButton)
|
||||
self.label_4.setBuddy(self.peakHoldMaxColorButton)
|
||||
self.label_6.setBuddy(self.peakHoldMinColorButton)
|
||||
self.label_5.setBuddy(self.averageColorButton)
|
||||
self.label_3.setBuddy(self.persistenceColorButton)
|
||||
self.label.setBuddy(self.baselineColorButton)
|
||||
|
||||
self.retranslateUi(QSpectrumAnalyzerColors)
|
||||
self.buttonBox.accepted.connect(QSpectrumAnalyzerColors.accept)
|
||||
self.buttonBox.rejected.connect(QSpectrumAnalyzerColors.reject)
|
||||
QtCore.QMetaObject.connectSlotsByName(QSpectrumAnalyzerColors)
|
||||
QSpectrumAnalyzerColors.setTabOrder(self.mainColorButton, self.peakHoldMaxColorButton)
|
||||
QSpectrumAnalyzerColors.setTabOrder(self.peakHoldMaxColorButton, self.peakHoldMinColorButton)
|
||||
QSpectrumAnalyzerColors.setTabOrder(self.peakHoldMinColorButton, self.averageColorButton)
|
||||
QSpectrumAnalyzerColors.setTabOrder(self.averageColorButton, self.persistenceColorButton)
|
||||
QSpectrumAnalyzerColors.setTabOrder(self.persistenceColorButton, self.baselineColorButton)
|
||||
|
||||
def retranslateUi(self, QSpectrumAnalyzerColors):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
QSpectrumAnalyzerColors.setWindowTitle(_translate("QSpectrumAnalyzerColors", "Colors - QSpectrumAnalyzer"))
|
||||
self.label_2.setText(_translate("QSpectrumAnalyzerColors", "&Main curve color:"))
|
||||
self.mainColorButton.setText(_translate("QSpectrumAnalyzerColors", "..."))
|
||||
self.label_4.setText(_translate("QSpectrumAnalyzerColors", "Max. peak &hold color:"))
|
||||
self.peakHoldMaxColorButton.setText(_translate("QSpectrumAnalyzerColors", "..."))
|
||||
self.label_6.setText(_translate("QSpectrumAnalyzerColors", "M&in. peak hold color:"))
|
||||
self.peakHoldMinColorButton.setText(_translate("QSpectrumAnalyzerColors", "..."))
|
||||
self.label_5.setText(_translate("QSpectrumAnalyzerColors", "Average &color:"))
|
||||
self.averageColorButton.setText(_translate("QSpectrumAnalyzerColors", "..."))
|
||||
self.label_3.setText(_translate("QSpectrumAnalyzerColors", "Persistence co&lor:"))
|
||||
self.persistenceColorButton.setText(_translate("QSpectrumAnalyzerColors", "..."))
|
||||
self.label.setText(_translate("QSpectrumAnalyzerColors", "&Baseline color:"))
|
||||
self.baselineColorButton.setText(_translate("QSpectrumAnalyzerColors", "..."))
|
||||
|
||||
from pyqtgraph import ColorButton
|
@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'qspectrumanalyzer/qspectrumanalyzer_persistence.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.8
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_QSpectrumAnalyzerPersistence(object):
|
||||
def setupUi(self, QSpectrumAnalyzerPersistence):
|
||||
QSpectrumAnalyzerPersistence.setObjectName("QSpectrumAnalyzerPersistence")
|
||||
QSpectrumAnalyzerPersistence.resize(250, 130)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QSpectrumAnalyzerPersistence)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.formLayout = QtWidgets.QFormLayout()
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.label_2 = QtWidgets.QLabel(QSpectrumAnalyzerPersistence)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_2)
|
||||
self.decayFunctionComboBox = QtWidgets.QComboBox(QSpectrumAnalyzerPersistence)
|
||||
self.decayFunctionComboBox.setObjectName("decayFunctionComboBox")
|
||||
self.decayFunctionComboBox.addItem("")
|
||||
self.decayFunctionComboBox.addItem("")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.decayFunctionComboBox)
|
||||
self.label = QtWidgets.QLabel(QSpectrumAnalyzerPersistence)
|
||||
self.label.setObjectName("label")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label)
|
||||
self.persistenceLengthSpinBox = QtWidgets.QSpinBox(QSpectrumAnalyzerPersistence)
|
||||
self.persistenceLengthSpinBox.setProperty("value", 5)
|
||||
self.persistenceLengthSpinBox.setObjectName("persistenceLengthSpinBox")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.persistenceLengthSpinBox)
|
||||
self.verticalLayout.addLayout(self.formLayout)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(QSpectrumAnalyzerPersistence)
|
||||
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.verticalLayout.addWidget(self.buttonBox)
|
||||
self.label_2.setBuddy(self.decayFunctionComboBox)
|
||||
self.label.setBuddy(self.persistenceLengthSpinBox)
|
||||
|
||||
self.retranslateUi(QSpectrumAnalyzerPersistence)
|
||||
self.decayFunctionComboBox.setCurrentIndex(1)
|
||||
self.buttonBox.accepted.connect(QSpectrumAnalyzerPersistence.accept)
|
||||
self.buttonBox.rejected.connect(QSpectrumAnalyzerPersistence.reject)
|
||||
QtCore.QMetaObject.connectSlotsByName(QSpectrumAnalyzerPersistence)
|
||||
QSpectrumAnalyzerPersistence.setTabOrder(self.decayFunctionComboBox, self.persistenceLengthSpinBox)
|
||||
QSpectrumAnalyzerPersistence.setTabOrder(self.persistenceLengthSpinBox, self.buttonBox)
|
||||
|
||||
def retranslateUi(self, QSpectrumAnalyzerPersistence):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
QSpectrumAnalyzerPersistence.setWindowTitle(_translate("QSpectrumAnalyzerPersistence", "Persistence - QSpectrumAnalyzer"))
|
||||
self.label_2.setText(_translate("QSpectrumAnalyzerPersistence", "Decay function:"))
|
||||
self.decayFunctionComboBox.setItemText(0, _translate("QSpectrumAnalyzerPersistence", "linear"))
|
||||
self.decayFunctionComboBox.setItemText(1, _translate("QSpectrumAnalyzerPersistence", "exponential"))
|
||||
self.label.setText(_translate("QSpectrumAnalyzerPersistence", "Persistence length:"))
|
||||
|
@ -1,171 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'qspectrumanalyzer/qspectrumanalyzer_settings.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.8
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_QSpectrumAnalyzerSettings(object):
|
||||
def setupUi(self, QSpectrumAnalyzerSettings):
|
||||
QSpectrumAnalyzerSettings.setObjectName("QSpectrumAnalyzerSettings")
|
||||
QSpectrumAnalyzerSettings.resize(600, 388)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QSpectrumAnalyzerSettings)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.formLayout = QtWidgets.QFormLayout()
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.label_3 = QtWidgets.QLabel(QSpectrumAnalyzerSettings)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_3)
|
||||
self.backendComboBox = QtWidgets.QComboBox(QSpectrumAnalyzerSettings)
|
||||
self.backendComboBox.setObjectName("backendComboBox")
|
||||
self.backendComboBox.addItem("")
|
||||
self.backendComboBox.addItem("")
|
||||
self.backendComboBox.addItem("")
|
||||
self.backendComboBox.addItem("")
|
||||
self.backendComboBox.addItem("")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.backendComboBox)
|
||||
self.label = QtWidgets.QLabel(QSpectrumAnalyzerSettings)
|
||||
self.label.setObjectName("label")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.executableEdit = QtWidgets.QLineEdit(QSpectrumAnalyzerSettings)
|
||||
self.executableEdit.setObjectName("executableEdit")
|
||||
self.horizontalLayout.addWidget(self.executableEdit)
|
||||
self.executableButton = QtWidgets.QToolButton(QSpectrumAnalyzerSettings)
|
||||
self.executableButton.setMinimumSize(QtCore.QSize(50, 0))
|
||||
self.executableButton.setObjectName("executableButton")
|
||||
self.horizontalLayout.addWidget(self.executableButton)
|
||||
self.formLayout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout)
|
||||
self.label_5 = QtWidgets.QLabel(QSpectrumAnalyzerSettings)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_5)
|
||||
self.label_4 = QtWidgets.QLabel(QSpectrumAnalyzerSettings)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_4)
|
||||
self.label_2 = QtWidgets.QLabel(QSpectrumAnalyzerSettings)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.label_2)
|
||||
self.waterfallHistorySizeSpinBox = QtWidgets.QSpinBox(QSpectrumAnalyzerSettings)
|
||||
self.waterfallHistorySizeSpinBox.setMinimum(1)
|
||||
self.waterfallHistorySizeSpinBox.setMaximum(10000000)
|
||||
self.waterfallHistorySizeSpinBox.setProperty("value", 100)
|
||||
self.waterfallHistorySizeSpinBox.setObjectName("waterfallHistorySizeSpinBox")
|
||||
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.waterfallHistorySizeSpinBox)
|
||||
self.label_7 = QtWidgets.QLabel(QSpectrumAnalyzerSettings)
|
||||
self.label_7.setObjectName("label_7")
|
||||
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_7)
|
||||
self.label_8 = QtWidgets.QLabel(QSpectrumAnalyzerSettings)
|
||||
self.label_8.setObjectName("label_8")
|
||||
self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_8)
|
||||
self.label_6 = QtWidgets.QLabel(QSpectrumAnalyzerSettings)
|
||||
self.label_6.setObjectName("label_6")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_6)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.paramsEdit = QtWidgets.QLineEdit(QSpectrumAnalyzerSettings)
|
||||
self.paramsEdit.setObjectName("paramsEdit")
|
||||
self.horizontalLayout_2.addWidget(self.paramsEdit)
|
||||
self.paramsHelpButton = QtWidgets.QToolButton(QSpectrumAnalyzerSettings)
|
||||
self.paramsHelpButton.setMinimumSize(QtCore.QSize(50, 0))
|
||||
self.paramsHelpButton.setObjectName("paramsHelpButton")
|
||||
self.horizontalLayout_2.addWidget(self.paramsHelpButton)
|
||||
self.formLayout.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2)
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
self.deviceEdit = QtWidgets.QLineEdit(QSpectrumAnalyzerSettings)
|
||||
self.deviceEdit.setObjectName("deviceEdit")
|
||||
self.horizontalLayout_3.addWidget(self.deviceEdit)
|
||||
self.deviceHelpButton = QtWidgets.QToolButton(QSpectrumAnalyzerSettings)
|
||||
self.deviceHelpButton.setMinimumSize(QtCore.QSize(50, 0))
|
||||
self.deviceHelpButton.setObjectName("deviceHelpButton")
|
||||
self.horizontalLayout_3.addWidget(self.deviceHelpButton)
|
||||
self.formLayout.setLayout(3, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3)
|
||||
self.sampleRateSpinBox = QtWidgets.QDoubleSpinBox(QSpectrumAnalyzerSettings)
|
||||
self.sampleRateSpinBox.setProperty("showGroupSeparator", True)
|
||||
self.sampleRateSpinBox.setDecimals(3)
|
||||
self.sampleRateSpinBox.setMinimum(0.0)
|
||||
self.sampleRateSpinBox.setMaximum(999999.99)
|
||||
self.sampleRateSpinBox.setSingleStep(0.01)
|
||||
self.sampleRateSpinBox.setProperty("value", 61.44)
|
||||
self.sampleRateSpinBox.setObjectName("sampleRateSpinBox")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.sampleRateSpinBox)
|
||||
self.bandwidthSpinBox = QtWidgets.QDoubleSpinBox(QSpectrumAnalyzerSettings)
|
||||
self.bandwidthSpinBox.setProperty("showGroupSeparator", True)
|
||||
self.bandwidthSpinBox.setDecimals(3)
|
||||
self.bandwidthSpinBox.setMinimum(0.0)
|
||||
self.bandwidthSpinBox.setMaximum(999999.99)
|
||||
self.bandwidthSpinBox.setSingleStep(0.01)
|
||||
self.bandwidthSpinBox.setProperty("value", 0.0)
|
||||
self.bandwidthSpinBox.setObjectName("bandwidthSpinBox")
|
||||
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.bandwidthSpinBox)
|
||||
self.lnbSpinBox = QtWidgets.QDoubleSpinBox(QSpectrumAnalyzerSettings)
|
||||
self.lnbSpinBox.setProperty("showGroupSeparator", True)
|
||||
self.lnbSpinBox.setDecimals(3)
|
||||
self.lnbSpinBox.setMinimum(-999999.999)
|
||||
self.lnbSpinBox.setMaximum(999999.999)
|
||||
self.lnbSpinBox.setSingleStep(0.01)
|
||||
self.lnbSpinBox.setProperty("value", 0.0)
|
||||
self.lnbSpinBox.setObjectName("lnbSpinBox")
|
||||
self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.lnbSpinBox)
|
||||
self.verticalLayout.addLayout(self.formLayout)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 21, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(QSpectrumAnalyzerSettings)
|
||||
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.verticalLayout.addWidget(self.buttonBox)
|
||||
self.label_3.setBuddy(self.backendComboBox)
|
||||
self.label.setBuddy(self.executableEdit)
|
||||
self.label_5.setBuddy(self.deviceEdit)
|
||||
self.label_4.setBuddy(self.sampleRateSpinBox)
|
||||
self.label_2.setBuddy(self.waterfallHistorySizeSpinBox)
|
||||
self.label_7.setBuddy(self.bandwidthSpinBox)
|
||||
self.label_8.setBuddy(self.lnbSpinBox)
|
||||
self.label_6.setBuddy(self.paramsEdit)
|
||||
|
||||
self.retranslateUi(QSpectrumAnalyzerSettings)
|
||||
self.buttonBox.accepted.connect(QSpectrumAnalyzerSettings.accept)
|
||||
self.buttonBox.rejected.connect(QSpectrumAnalyzerSettings.reject)
|
||||
QtCore.QMetaObject.connectSlotsByName(QSpectrumAnalyzerSettings)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.backendComboBox, self.executableEdit)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.executableEdit, self.executableButton)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.executableButton, self.paramsEdit)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.paramsEdit, self.paramsHelpButton)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.paramsHelpButton, self.deviceEdit)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.deviceEdit, self.deviceHelpButton)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.deviceHelpButton, self.sampleRateSpinBox)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.sampleRateSpinBox, self.bandwidthSpinBox)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.bandwidthSpinBox, self.lnbSpinBox)
|
||||
QSpectrumAnalyzerSettings.setTabOrder(self.lnbSpinBox, self.waterfallHistorySizeSpinBox)
|
||||
|
||||
def retranslateUi(self, QSpectrumAnalyzerSettings):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
QSpectrumAnalyzerSettings.setWindowTitle(_translate("QSpectrumAnalyzerSettings", "Settings - QSpectrumAnalyzer"))
|
||||
self.label_3.setText(_translate("QSpectrumAnalyzerSettings", "&Backend:"))
|
||||
self.backendComboBox.setItemText(0, _translate("QSpectrumAnalyzerSettings", "soapy_power"))
|
||||
self.backendComboBox.setItemText(1, _translate("QSpectrumAnalyzerSettings", "rx_power"))
|
||||
self.backendComboBox.setItemText(2, _translate("QSpectrumAnalyzerSettings", "rtl_power_fftw"))
|
||||
self.backendComboBox.setItemText(3, _translate("QSpectrumAnalyzerSettings", "rtl_power"))
|
||||
self.backendComboBox.setItemText(4, _translate("QSpectrumAnalyzerSettings", "hackrf_sweep"))
|
||||
self.label.setText(_translate("QSpectrumAnalyzerSettings", "E&xecutable:"))
|
||||
self.executableEdit.setText(_translate("QSpectrumAnalyzerSettings", "soapy_power"))
|
||||
self.executableButton.setText(_translate("QSpectrumAnalyzerSettings", "..."))
|
||||
self.label_5.setText(_translate("QSpectrumAnalyzerSettings", "&Device:"))
|
||||
self.label_4.setText(_translate("QSpectrumAnalyzerSettings", "Sa&mple rate:"))
|
||||
self.label_2.setText(_translate("QSpectrumAnalyzerSettings", "&Waterfall history size:"))
|
||||
self.label_7.setText(_translate("QSpectrumAnalyzerSettings", "Bandwidt&h:"))
|
||||
self.label_8.setToolTip(_translate("QSpectrumAnalyzerSettings", "Negative frequency for upconverters, positive frequency for downconverters."))
|
||||
self.label_8.setText(_translate("QSpectrumAnalyzerSettings", "&LNB LO:"))
|
||||
self.label_6.setText(_translate("QSpectrumAnalyzerSettings", "Add&itional parameters:"))
|
||||
self.paramsHelpButton.setText(_translate("QSpectrumAnalyzerSettings", " ? "))
|
||||
self.deviceHelpButton.setText(_translate("QSpectrumAnalyzerSettings", " ? "))
|
||||
self.sampleRateSpinBox.setSuffix(_translate("QSpectrumAnalyzerSettings", " MHz"))
|
||||
self.bandwidthSpinBox.setSuffix(_translate("QSpectrumAnalyzerSettings", " MHz"))
|
||||
self.lnbSpinBox.setToolTip(_translate("QSpectrumAnalyzerSettings", "Negative frequency for upconverters, positive frequency for downconverters."))
|
||||
self.lnbSpinBox.setSuffix(_translate("QSpectrumAnalyzerSettings", " MHz"))
|
||||
|
@ -1,37 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'qspectrumanalyzer/qspectrumanalyzer_settings_help.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.8
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_QSpectrumAnalyzerSettingsHelp(object):
|
||||
def setupUi(self, QSpectrumAnalyzerSettingsHelp):
|
||||
QSpectrumAnalyzerSettingsHelp.setObjectName("QSpectrumAnalyzerSettingsHelp")
|
||||
QSpectrumAnalyzerSettingsHelp.resize(1200, 700)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QSpectrumAnalyzerSettingsHelp)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.helpTextEdit = QtWidgets.QPlainTextEdit(QSpectrumAnalyzerSettingsHelp)
|
||||
self.helpTextEdit.setUndoRedoEnabled(False)
|
||||
self.helpTextEdit.setTextInteractionFlags(QtCore.Qt.TextSelectableByKeyboard|QtCore.Qt.TextSelectableByMouse)
|
||||
self.helpTextEdit.setObjectName("helpTextEdit")
|
||||
self.verticalLayout.addWidget(self.helpTextEdit)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(QSpectrumAnalyzerSettingsHelp)
|
||||
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Close)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.verticalLayout.addWidget(self.buttonBox)
|
||||
|
||||
self.retranslateUi(QSpectrumAnalyzerSettingsHelp)
|
||||
self.buttonBox.accepted.connect(QSpectrumAnalyzerSettingsHelp.accept)
|
||||
self.buttonBox.rejected.connect(QSpectrumAnalyzerSettingsHelp.reject)
|
||||
QtCore.QMetaObject.connectSlotsByName(QSpectrumAnalyzerSettingsHelp)
|
||||
QSpectrumAnalyzerSettingsHelp.setTabOrder(self.helpTextEdit, self.buttonBox)
|
||||
|
||||
def retranslateUi(self, QSpectrumAnalyzerSettingsHelp):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
QSpectrumAnalyzerSettingsHelp.setWindowTitle(_translate("QSpectrumAnalyzerSettingsHelp", "Help - QSpectrumAnalyzer"))
|
||||
|
@ -1,68 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'qspectrumanalyzer/qspectrumanalyzer_smoothing.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.8
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_QSpectrumAnalyzerSmoothing(object):
|
||||
def setupUi(self, QSpectrumAnalyzerSmoothing):
|
||||
QSpectrumAnalyzerSmoothing.setObjectName("QSpectrumAnalyzerSmoothing")
|
||||
QSpectrumAnalyzerSmoothing.resize(250, 130)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QSpectrumAnalyzerSmoothing)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.formLayout = QtWidgets.QFormLayout()
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.label = QtWidgets.QLabel(QSpectrumAnalyzerSmoothing)
|
||||
self.label.setObjectName("label")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
|
||||
self.windowFunctionComboBox = QtWidgets.QComboBox(QSpectrumAnalyzerSmoothing)
|
||||
self.windowFunctionComboBox.setObjectName("windowFunctionComboBox")
|
||||
self.windowFunctionComboBox.addItem("")
|
||||
self.windowFunctionComboBox.addItem("")
|
||||
self.windowFunctionComboBox.addItem("")
|
||||
self.windowFunctionComboBox.addItem("")
|
||||
self.windowFunctionComboBox.addItem("")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.windowFunctionComboBox)
|
||||
self.label_2 = QtWidgets.QLabel(QSpectrumAnalyzerSmoothing)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_2)
|
||||
self.windowLengthSpinBox = QtWidgets.QSpinBox(QSpectrumAnalyzerSmoothing)
|
||||
self.windowLengthSpinBox.setMinimum(3)
|
||||
self.windowLengthSpinBox.setMaximum(1001)
|
||||
self.windowLengthSpinBox.setProperty("value", 11)
|
||||
self.windowLengthSpinBox.setObjectName("windowLengthSpinBox")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.windowLengthSpinBox)
|
||||
self.verticalLayout.addLayout(self.formLayout)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(QSpectrumAnalyzerSmoothing)
|
||||
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.verticalLayout.addWidget(self.buttonBox)
|
||||
self.label.setBuddy(self.windowFunctionComboBox)
|
||||
self.label_2.setBuddy(self.windowLengthSpinBox)
|
||||
|
||||
self.retranslateUi(QSpectrumAnalyzerSmoothing)
|
||||
self.windowFunctionComboBox.setCurrentIndex(1)
|
||||
self.buttonBox.accepted.connect(QSpectrumAnalyzerSmoothing.accept)
|
||||
self.buttonBox.rejected.connect(QSpectrumAnalyzerSmoothing.reject)
|
||||
QtCore.QMetaObject.connectSlotsByName(QSpectrumAnalyzerSmoothing)
|
||||
QSpectrumAnalyzerSmoothing.setTabOrder(self.windowFunctionComboBox, self.windowLengthSpinBox)
|
||||
QSpectrumAnalyzerSmoothing.setTabOrder(self.windowLengthSpinBox, self.buttonBox)
|
||||
|
||||
def retranslateUi(self, QSpectrumAnalyzerSmoothing):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
QSpectrumAnalyzerSmoothing.setWindowTitle(_translate("QSpectrumAnalyzerSmoothing", "Smoothing - QSpectrumAnalyzer"))
|
||||
self.label.setText(_translate("QSpectrumAnalyzerSmoothing", "&Window function:"))
|
||||
self.windowFunctionComboBox.setItemText(0, _translate("QSpectrumAnalyzerSmoothing", "rectangular"))
|
||||
self.windowFunctionComboBox.setItemText(1, _translate("QSpectrumAnalyzerSmoothing", "hanning"))
|
||||
self.windowFunctionComboBox.setItemText(2, _translate("QSpectrumAnalyzerSmoothing", "hamming"))
|
||||
self.windowFunctionComboBox.setItemText(3, _translate("QSpectrumAnalyzerSmoothing", "bartlett"))
|
||||
self.windowFunctionComboBox.setItemText(4, _translate("QSpectrumAnalyzerSmoothing", "blackman"))
|
||||
self.label_2.setText(_translate("QSpectrumAnalyzerSmoothing", "Window len>h:"))
|
||||
|
@ -1,53 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
from Qt import QtGui
|
||||
|
||||
|
||||
def smooth(x, window_len=11, window='hanning'):
|
||||
"""Smooth 1D signal using specified window with given size"""
|
||||
x = np.array(x)
|
||||
if window_len < 3:
|
||||
return x
|
||||
|
||||
if x.size < window_len:
|
||||
raise ValueError("Input data length must be greater than window size")
|
||||
|
||||
if window not in ['rectangular', 'hanning', 'hamming', 'bartlett', 'blackman']:
|
||||
raise ValueError("Window must be 'rectangular', 'hanning', 'hamming', 'bartlett' or 'blackman'")
|
||||
|
||||
if window == 'rectangular':
|
||||
# Moving average
|
||||
w = np.ones(window_len, 'd')
|
||||
else:
|
||||
w = getattr(np, window)(window_len)
|
||||
|
||||
s = np.r_[2 * x[0] - x[window_len:1:-1], x, 2 * x[-1] - x[-1:-window_len:-1]]
|
||||
y = np.convolve(w / w.sum(), s, mode='same')
|
||||
|
||||
return y[window_len - 1:-window_len + 1]
|
||||
|
||||
|
||||
def str_to_color(color_string):
|
||||
"""Create QColor from comma sepparated RGBA string"""
|
||||
return QtGui.QColor(*[int(c.strip()) for c in color_string.split(',')])
|
||||
|
||||
|
||||
def color_to_str(color):
|
||||
"""Create comma separated RGBA string from QColor"""
|
||||
return ", ".join([str(color.red()), str(color.green()), str(color.blue()), str(color.alpha())])
|
||||
|
||||
|
||||
def human_time(seconds):
|
||||
"""Format time in seconds to human readable form (e.g. 1 h 2 min 3 s)"""
|
||||
seconds = int(seconds)
|
||||
m, s = divmod(seconds, 60)
|
||||
h, m = divmod(m, 60)
|
||||
|
||||
if h > 0:
|
||||
timestr = '{:.0f} h {:.0f} min {:.0f} s'.format(h, m, s)
|
||||
elif m > 0:
|
||||
timestr = '{:.0f} min {:.0f} s'.format(m, s)
|
||||
else:
|
||||
timestr = '{:.0f} s'.format(s)
|
||||
|
||||
return timestr
|
@ -1 +0,0 @@
|
||||
__version__ = "2.2.0"
|
@ -1,51 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © Spyder Project Contributors
|
||||
# Licensed under the terms of the MIT License
|
||||
# (see spyder/__init__.py for details)
|
||||
|
||||
"""Windows-specific utilities"""
|
||||
|
||||
|
||||
from ctypes import windll
|
||||
|
||||
|
||||
# --- Window control ---
|
||||
|
||||
SW_SHOW = 5 # activate and display
|
||||
SW_SHOWNA = 8 # show without activation
|
||||
SW_HIDE = 0
|
||||
|
||||
GetConsoleWindow = windll.kernel32.GetConsoleWindow
|
||||
ShowWindow = windll.user32.ShowWindow
|
||||
IsWindowVisible = windll.user32.IsWindowVisible
|
||||
|
||||
# Handle to console window associated with current Python
|
||||
# interpreter procss, 0 if there is no window
|
||||
console_window_handle = GetConsoleWindow()
|
||||
|
||||
|
||||
def set_attached_console_visible(state):
|
||||
"""Show/hide system console window attached to current process.
|
||||
Return it's previous state.
|
||||
|
||||
Availability: Windows"""
|
||||
flag = {True: SW_SHOW, False: SW_HIDE}
|
||||
return bool(ShowWindow(console_window_handle, flag[state]))
|
||||
|
||||
|
||||
def is_attached_console_visible():
|
||||
"""Return True if attached console window is visible"""
|
||||
return IsWindowVisible(console_window_handle)
|
||||
|
||||
|
||||
def set_windows_appusermodelid():
|
||||
"""Make sure correct icon is used on Windows 7 taskbar"""
|
||||
try:
|
||||
return windll.shell32.SetCurrentProcessExplicitAppUserModelID("spyder.Spyder")
|
||||
except AttributeError:
|
||||
return "SetCurrentProcessExplicitAppUserModelID not found"
|
||||
|
||||
|
||||
# [ ] the console state asks for a storage container
|
||||
# [ ] reopen console on exit - better die open than become a zombie
|
BIN
qspectrumanalyzer_screenshot.png
Normal file
BIN
qspectrumanalyzer_screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 300 KiB |
BIN
qspectrumanalyzer_screenshot2.png
Normal file
BIN
qspectrumanalyzer_screenshot2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 280 KiB |
105
setup.py
105
setup.py
@ -1,105 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import setuptools
|
||||
from setuptools import setup
|
||||
|
||||
from qspectrumanalyzer.version import __version__
|
||||
|
||||
setup_cmdclass = {}
|
||||
setup_entry_points = {
|
||||
"gui_scripts": [
|
||||
"qspectrumanalyzer=qspectrumanalyzer.__main__:main",
|
||||
],
|
||||
}
|
||||
|
||||
# Allow compilation of Qt .qrc, .ui and .ts files (build_qt command)
|
||||
try:
|
||||
from setup_qt import build_qt
|
||||
setup_cmdclass['build_qt'] = build_qt
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Allow building frozen executables with PyInstaller / subzero (build_exe command)
|
||||
try:
|
||||
from subzero import setup, Executable
|
||||
setup_entry_points = {
|
||||
"console_scripts": [
|
||||
Executable('qspectrumanalyzer=qspectrumanalyzer.__main__:main',
|
||||
console=True, icon_file='qspectrumanalyzer.ico'),
|
||||
Executable('soapy_power=soapypower.__main__:main',
|
||||
console=True),
|
||||
],
|
||||
}
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
setup(
|
||||
name="QSpectrumAnalyzer",
|
||||
version=__version__,
|
||||
description=("Spectrum analyzer for multiple SDR platforms "
|
||||
"(PyQtGraph based GUI for soapy_power, hackrf_sweep, rtl_power, rx_power and other backends)"),
|
||||
long_description=open('README.rst').read(),
|
||||
author="Michal Krenek (Mikos)",
|
||||
author_email="m.krenek@gmail.com",
|
||||
url="https://github.com/xmikos/qspectrumanalyzer",
|
||||
license="GNU GPLv3",
|
||||
packages=["qspectrumanalyzer", "qspectrumanalyzer.backends"],
|
||||
package_data={
|
||||
"qspectrumanalyzer": [
|
||||
"qspectrumanalyzer.svg",
|
||||
"*.ui",
|
||||
"languages/*.qm",
|
||||
"languages/*.ts",
|
||||
],
|
||||
},
|
||||
data_files=[
|
||||
("share/applications", ["qspectrumanalyzer.desktop"]),
|
||||
("share/pixmaps", ["qspectrumanalyzer.png"]),
|
||||
],
|
||||
install_requires=[
|
||||
"soapy_power>=1.6.0",
|
||||
"pyqtgraph>=0.10.0",
|
||||
"Qt.py",
|
||||
],
|
||||
options={
|
||||
'build_qt': {
|
||||
'packages': ['qspectrumanalyzer'],
|
||||
'languages': ['cs'],
|
||||
'replacement_bindings': 'Qt',
|
||||
},
|
||||
'build_exe': {
|
||||
'datas': [
|
||||
('qspectrumanalyzer/qspectrumanalyzer.svg', 'qspectrumanalyzer'),
|
||||
('qspectrumanalyzer/*.ui', 'qspectrumanalyzer'),
|
||||
('qspectrumanalyzer/languages/*.ts', 'qspectrumanalyzer/languages'),
|
||||
('qspectrumanalyzer/languages/*.qm', 'qspectrumanalyzer/languages'),
|
||||
('README.rst', '.'),
|
||||
('LICENSE', '.'),
|
||||
],
|
||||
},
|
||||
'bdist_msi': {
|
||||
'upgrade_code': '30740ef4-84e7-4e67-8e4a-12b53492c387',
|
||||
'shortcuts': [
|
||||
'QSpectrumAnalyzer=qspectrumanalyzer',
|
||||
],
|
||||
},
|
||||
},
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: MacOS X",
|
||||
"Environment :: Win32 (MS Windows)",
|
||||
"Environment :: X11 Applications :: Qt",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"Intended Audience :: Science/Research",
|
||||
"Intended Audience :: Telecommunications Industry",
|
||||
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
||||
"Natural Language :: English",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Communications :: Ham Radio",
|
||||
"Topic :: Scientific/Engineering :: Visualization",
|
||||
],
|
||||
entry_points=setup_entry_points,
|
||||
cmdclass=setup_cmdclass,
|
||||
)
|
Loading…
Reference in New Issue
Block a user