The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

PDLx::Mask - Mask multiple piddles with automatic two way feedback

SYNOPSIS

  use 5.10.0;
  use PDLx::Mask;
  use PDLx::MaskedData;

  $pdl = sequence( 9 );

  $mask = PDLx::Mask->new( $pdl->ones );
  say $mask;    # [1 1 1 1 1 1 1 1 1]

  $data1 = PDLx::MaskedData->new( $pdl, $mask );
  say $data1;    # [0 1 2 3 4 5 6 7 8]

  $data2 = PDLx::MaskedData->new( $pdl + 1, $mask );
  say $data2;    # [1 2 3 4 5 6 7 8 9]

  # update the mask
  $mask->set( 3, 0 );
  say $mask;     # [1 1 1 0 1 1 1 1 1]

  # and see it propagate
  say $data1;    # [0 1 2 0 4 5 6 7 8]
  say $data2;    # [1 2 3 0 5 6 7 8 9]

  # use bad values for $data1
  $data1->badflag(1);
  # notice that the invalid element is now bad
  say $data1;    # [0 1 2 BAD 4 5 6 7 8]

  # push invalid values upstream to the shared mask
  $data1->upstream_mask(1);
  $data1->setbadat(0);
  say $data1;    # [BAD 1 2 BAD 4 5 6 7 8]

  # see the mask change
  say $mask;     # [0 1 1 0 1 1 1 1 1]

  # and see the other piddle change
  say $data2;    # [0 2 3 0 5 6 7 8 9]

DESCRIPTION

Typically PDL uses bad values to mark elements in a piddle which contain invalid data. When multiple piddles should have the same elements marked as invalid, a separate mask piddle (whose values are true for valid data and false otherwise) is often used.

PDLx::Mask in concert with PDLx::MaskedData simplifies the management of mutiple piddles sharing the same mask. PDLx::Mask is the shared mask, and PDLx::MaskedData is a specialized piddle which will dynamically respond to changes in the mask, so that they are always up-to-date.

Additionally, invalid elements in the data piddles may automatically be added to the shared mask, so that there is a consistent view of valid elements across all piddles.

Details

PDLx::Mask is a subclass of PDL which manages a mask across on or more piddles. It can be used directly as a piddle, but be careful not to change its contents inadvertently. It should only be manipulated via the provided methods or overloaded operators.

It maintains two views of the mask:

  1. the original base mask; and

  2. the effective mask, which is the base mask combined with additional invalid elements from the data piddles.

The subscribe method is used to register callbacks to be invoked when the mask has been changed. Multiple subscriptions are allowed; each can register two callbacks:

  • A subroutine invoked when the mask has changed. It is passed a piddle containing the mask. It should not alter it.

  • A subroutine which will return a data mask. If the data mask changes, the mask's update method must be called.

INTERFACE

Methods specific to PDLx::Mask

new

  $mask = PDLx::Mask->new( $base_mask );
  # or
  $mask = PDLx::Mask->new( base => $base_mask );

Create a mask using the passed mask as the base mask. It does not copy the passed piddle.

base

  $base = $mask->base;

This returns the base mask. Don't alter the returned piddle!

mask

  $pdl = $mask->mask;
  $pdl = $mask->mask( $new_mask );

Return the effective mask as a plain piddle. Don't alter the returned piddle!

If passed a piddle, it is copied to the base mask and the update method is called.

Note that the $mask object can also be used directly without calling this method.

nvalid

  $nvalid_elements = $mask->nvalid;

The number of valid elements in the effective mask. This is lazily evaluated and cached.

subscribe

  $token = $mask->subscribe( apply_mask => $code_ref, %options );

Register the passed subroutines to be called when the effective mask is changed. The returned token may be used to unsubscribe the callbacks using unsubscribe.

The following options are available:

apply_mask => code reference

This subroutine should expect a single argument (a mask piddle) and apply it. It should not alter the mask piddle. It is optional.

This callback will be invoked no arguments if the mask has been directed to unsubscribe the callbacks. See "unsubscribe"

data_mask => code reference

This subroutine should return a piddle which encodes the intrinsic valid elements of the object's data. It is optional.

The mask object does not monitor this piddle for changes. If the data mask changes, the mask's update method must be called.

token => scalar

Instead of creating a new subscription, update the entry with the given token, which was returned by a previous invocation of subscribe.

is_subscriber

  $bool = $mask->is_subscriber( $token );

Returns true if the passed token refers to an active subscriber.

unsubscribe

  $mask->unsubscribe( $token );

Unsubscribe the callbacks with the given token (returned by subscribe).

If the callbacks for $token include the apply_mask callback, it will be invoked with no arguments, indicating that it is being unsubscribed. At that time $mask->is_subscriber($token) will return false.

update

  $mask->update;

This performs the following:

  1. subscribers with data_mask callbacks are queried for their masks;

  2. the effective mask is constructed from the base mask and the data masks; and

  3. subscribers' apply_mask callbacks are invoked with the effective mask.

Overridden methods

copy

Returns a copy of the effective mask as an ordinary piddle.

inplace

This is a fatal operation.

set_inplace()

This is a fatal operation if the passed value is non-zero.

set

   $mask->set( $pos, $value);

This updates the base mask at position $pos to $value and calls the update method.

Overloaded Operators

Use of assignment operators (but not the underlying PDL methods or subroutines) other than the following should be fatal.

|= &= ^= .=

These operators may be used to update the base mask. The effective mask will automatically be updated.

EXAMPLES

Secondary Masks

Sometimes the primary mask should incorporate a secondary mask that's not associated with a data set. Here's how to do that:

  $pmask = PDLx::Mask->new( pdl( byte, 1, 1, 1 ) );
  $smask = PDLx::MaskedData->new( base => pdl( byte, 0, 1, 0 ),
                                  mask => $pmask,
                                  apply_mask => 0,
                                  data_mask => 1
                                );

The key difference between this and an ordinary dependency on a data mask, is that by turning off apply_mask, changes in the primary mask won't be replicated in the secondary.

  say $smask;       # [ 0 1 0 ]
  say $pmask->base; # [ 1 1 1 ]
  say $pmask;       # [ 0 1 0 ]

  $smask->set( 0, 1 );
  say $smask;       #  [ 1 1 0 ]
  say $pmask->base; #  [ 1 1 1 ]
  say $pmask;       #  [ 1 1 0 ]

  $pmask->set( 0, 0 );
  say $smask;       #  [ 1 1 0 ]
  say $pmask->base; #  [ 0 1 1 ]
  say $pmask;       #  [ 0 1 0 ]

Intermittant Secondary Masks

Building upon the previous example, let's say the secondary mask is used intermittently. For example

  $pmask = PDLx::Mask->new( [ 1, 1, 1 ] );

  $smask = PDLx::MaskedData->new( base => [ 0, 1, 0 ],
                                  mask => $pmask,
                                  apply_mask => 0,
                                  data_mask => 1
                                );

  $data = PDLx::MaskedData->new( [ 33, 22, 44 ], $pmask );

  say $data         #  [ 0, 22, 0 ]

  # now want to ignore secondary mask
  $smask->unsubscribe;

  say $data         #  [ 33, 22, 44 ]

  # and now stop ignoring it
  $smask->subscribe;
  say $data         #  [ 0, 22, 0 ]

BUGS AND LIMITATIONS

Please report any bugs or feature requests to bug-pdlx-mask@rt.cpan.org, or through the web interface at http://rt.cpan.org/Public/Dist/Display.html?Name=PDLx-Mask.

VERSION

Version 0.01

LICENSE AND COPYRIGHT

Copyright (c) 2016 The Smithsonian Astrophysical Observatory

PDLx::Mask 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/>.

AUTHOR

Diab Jerius <djerius@cpan.org>